Import kirigami2_5.107.0.orig.tar.xz
authorAurélien COUDERC <coucouf@debian.org>
Sun, 18 Jun 2023 14:08:41 +0000 (15:08 +0100)
committerAurélien COUDERC <coucouf@debian.org>
Sun, 18 Jun 2023 14:08:41 +0000 (15:08 +0100)
[dgit import orig kirigami2_5.107.0.orig.tar.xz]

431 files changed:
.git-blame-ignore-revs [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.gitlab-ci.yml [new file with mode: 0644]
.kde-ci.yml [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
ExtraDesktop.sh [new file with mode: 0644]
KF5Kirigami2Config.cmake.in [new file with mode: 0644]
KF5Kirigami2Macros.cmake [new file with mode: 0644]
LICENSES/CC0-1.0.txt [new file with mode: 0644]
LICENSES/LGPL-2.0-or-later.txt [new file with mode: 0644]
LICENSES/LGPL-2.1-only.txt [new file with mode: 0644]
LICENSES/LGPL-3.0-only.txt [new file with mode: 0644]
Mainpage.dox [new file with mode: 0644]
README.md [new file with mode: 0644]
autotests/BasicListItem_ToolTip_Test.qml [new file with mode: 0644]
autotests/CMakeLists.txt [new file with mode: 0644]
autotests/pagepool/TestPage.qml [new file with mode: 0644]
autotests/pagepool/tst_layers.qml [new file with mode: 0644]
autotests/pagepool/tst_pagepool.qml [new file with mode: 0644]
autotests/qmltest.cpp [new file with mode: 0644]
autotests/tst_actiontoolbar.qml [new file with mode: 0644]
autotests/tst_avatar.qml [new file with mode: 0644]
autotests/tst_basiclistitem_tooltip.qml [new file with mode: 0644]
autotests/tst_formlayout.qml [new file with mode: 0644]
autotests/tst_globaldrawer.qml [new file with mode: 0644]
autotests/tst_icon.qml [new file with mode: 0644]
autotests/tst_keynavigation.qml [new file with mode: 0644]
autotests/tst_listskeynavigation.qml [new file with mode: 0644]
autotests/tst_mnemonicdata.qml [new file with mode: 0644]
autotests/tst_pagerouter.qml [new file with mode: 0644]
autotests/tst_pagerow.qml [new file with mode: 0644]
autotests/tst_routerwindow.qml [new file with mode: 0644]
autotests/tst_theme.qml [new file with mode: 0644]
autotests/wheelhandler/ContentFlickable.qml [new file with mode: 0644]
autotests/wheelhandler/ScrollableFlickable.qml [new file with mode: 0644]
autotests/wheelhandler/tst_filterMouseEvents.qml [new file with mode: 0644]
autotests/wheelhandler/tst_invokables.qml [new file with mode: 0644]
autotests/wheelhandler/tst_onWheel.qml [new file with mode: 0644]
autotests/wheelhandler/tst_scrolling.qml [new file with mode: 0644]
config-OpenMP.h.cmake [new file with mode: 0644]
docs/pics/BasicListItemReserve.svg [new file with mode: 0644]
docs/pics/BasicListItemTypes.svg [new file with mode: 0644]
docs/pics/MinimalExample.webp [new file with mode: 0644]
docs/pics/PageRouterModel.svg [new file with mode: 0644]
docs/pics/PageRouterNavigate.svg [new file with mode: 0644]
docs/pics/PageRouterPop.svg [new file with mode: 0644]
docs/pics/PageRouterPush.svg [new file with mode: 0644]
docs/pics/icon/active.png [new file with mode: 0644]
docs/pics/icon/selected.png [new file with mode: 0644]
examples/CMakeLists.txt [new file with mode: 0644]
examples/applicationitemapp/CMakeLists.txt [new file with mode: 0644]
examples/applicationitemapp/main.cpp [new file with mode: 0644]
examples/applicationitemapp/main.qml [new file with mode: 0644]
examples/applicationitemapp/resources.qrc [new file with mode: 0644]
examples/flexcolumn/main.qml [new file with mode: 0644]
examples/formlayout.qml [new file with mode: 0644]
examples/hero.qml [new file with mode: 0644]
examples/icon/CustomSource.qml [new file with mode: 0644]
examples/icon/Fallback.qml [new file with mode: 0644]
examples/icon/FilesystemSource.qml [new file with mode: 0644]
examples/icon/IconThemeSource.qml [new file with mode: 0644]
examples/icon/InternetSource.qml [new file with mode: 0644]
examples/icon/ResourceSource.qml [new file with mode: 0644]
examples/imagecolorstest.qml [new file with mode: 0644]
examples/listitemdraghandle.qml [new file with mode: 0644]
examples/multiplatformnotesapp/NotesGeneral.qml [new file with mode: 0644]
examples/multiplatformnotesapp/notesDesktop.qml [new file with mode: 0644]
examples/multiplatformnotesapp/notesMobile.qml [new file with mode: 0644]
examples/overlaydrawer.qml [new file with mode: 0644]
examples/pagerow.qml [new file with mode: 0644]
examples/scenepotitionattached.qml [new file with mode: 0644]
examples/settingscomponents/GeneralSettingsPage.qml [new file with mode: 0644]
examples/settingscomponents/SettingsPage.qml [new file with mode: 0644]
examples/settingscomponents/main.qml [new file with mode: 0644]
examples/settingscomponents/resources.qrc [new file with mode: 0644]
examples/shadowrectangle.qml [new file with mode: 0644]
examples/simpleexamples/AbstractApplicationWindow.qml [new file with mode: 0644]
examples/simpleexamples/FixedSidebar.qml [new file with mode: 0644]
examples/simpleexamples/MultipleColumnsGallery.qml [new file with mode: 0644]
examples/simpleexamples/Sidebar.qml [new file with mode: 0644]
examples/simpleexamples/SimplePage.qml [new file with mode: 0644]
examples/simpleexamples/TabBarHeader.qml [new file with mode: 0644]
examples/simpleexamples/customdrawer.qml [new file with mode: 0644]
examples/simpleexamples/dragPageWidth.qml [new file with mode: 0644]
examples/simpleexamples/footer.qml [new file with mode: 0644]
examples/simpleexamples/minimal.qml [new file with mode: 0644]
examples/simpleexamples/pagePoolDrawer.qml [new file with mode: 0644]
examples/simpleexamples/pagePoolFirstColumn.qml [new file with mode: 0644]
examples/simpleexamples/pushpopclear.qml [new file with mode: 0644]
examples/simpleexamples/simpleChatApp.qml [new file with mode: 0644]
examples/sizegroup.qml [new file with mode: 0644]
examples/staticcmake/3rdparty/CMakeLists.txt [new file with mode: 0644]
examples/staticcmake/3rdparty/README [new file with mode: 0644]
examples/staticcmake/CMakeLists.txt [new file with mode: 0644]
examples/staticcmake/src/CMakeLists.txt [new file with mode: 0644]
examples/staticcmake/src/Page1.qml [new file with mode: 0644]
examples/staticcmake/src/Page1Form.ui.qml [new file with mode: 0644]
examples/staticcmake/src/kirigami-icons.qrc [new file with mode: 0644]
examples/staticcmake/src/main.cpp [new file with mode: 0644]
examples/staticcmake/src/main.qml [new file with mode: 0644]
examples/staticcmake/src/qtquickcontrols2.conf [new file with mode: 0644]
examples/staticcmake/src/resources.qrc [new file with mode: 0644]
examples/wheelhandler/FlickableUsage.qml [new file with mode: 0644]
examples/wheelhandler/ScrollViewUsage.qml [new file with mode: 0644]
logo.png [new file with mode: 0644]
metainfo.yaml [new file with mode: 0644]
poqm/ar/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/az/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/bg/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ca/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ca@valencia/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/cs/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/da/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/de/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/el/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/en_GB/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/es/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/et/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/eu/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/fi/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/fr/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/gl/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/hi/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/hu/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ia/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/id/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/it/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ja/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ka/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ko/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/lt/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ml/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/nl/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/nn/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/pa/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/pl/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/pt/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/pt_BR/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ro/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ru/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sk/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sl/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sr/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sr@ijekavian/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sr@ijekavianlatin/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sr@latin/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/sv/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/ta/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/tg/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/tr/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/uk/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/zh_CN/libkirigami2plugin_qt.po [new file with mode: 0644]
poqm/zh_TW/libkirigami2plugin_qt.po [new file with mode: 0644]
scripts/gen_icons_qrc.sh [new file with mode: 0755]
scripts/gen_qmltypes.sh [new file with mode: 0755]
src/CMakeLists.txt [new file with mode: 0644]
src/KF5Kirigami2-android-dependencies.xml [new file with mode: 0644]
src/Messages.sh [new file with mode: 0644]
src/avatar.cpp [new file with mode: 0644]
src/avatar.h [new file with mode: 0644]
src/colorutils.cpp [new file with mode: 0644]
src/colorutils.h [new file with mode: 0644]
src/columnview.cpp [new file with mode: 0644]
src/columnview.h [new file with mode: 0644]
src/columnview_p.h [new file with mode: 0644]
src/controls/AboutItem.qml [new file with mode: 0644]
src/controls/AboutPage.qml [new file with mode: 0644]
src/controls/AbstractApplicationHeader.qml [new file with mode: 0644]
src/controls/AbstractApplicationItem.qml [new file with mode: 0644]
src/controls/AbstractApplicationWindow.qml [new file with mode: 0644]
src/controls/AbstractCard.qml [new file with mode: 0644]
src/controls/AbstractChip.qml [new file with mode: 0644]
src/controls/AbstractItemViewHeader.qml [new file with mode: 0644]
src/controls/AbstractListItem.qml [new file with mode: 0644]
src/controls/Action.qml [new file with mode: 0644]
src/controls/ActionTextField.qml [new file with mode: 0644]
src/controls/ActionToolBar.qml [new file with mode: 0644]
src/controls/ApplicationHeader.qml [new file with mode: 0644]
src/controls/ApplicationItem.qml [new file with mode: 0644]
src/controls/ApplicationWindow.qml [new file with mode: 0644]
src/controls/Avatar.qml [new file with mode: 0644]
src/controls/BasicListItem.qml [new file with mode: 0644]
src/controls/Card.qml [new file with mode: 0644]
src/controls/CardsGridView.qml [new file with mode: 0644]
src/controls/CardsLayout.qml [new file with mode: 0644]
src/controls/CardsListView.qml [new file with mode: 0644]
src/controls/CheckableListItem.qml [new file with mode: 0644]
src/controls/Chip.qml [new file with mode: 0644]
src/controls/ContextDrawer.qml [new file with mode: 0644]
src/controls/Dialog.qml [new file with mode: 0644]
src/controls/FlexColumn.qml [new file with mode: 0644]
src/controls/FormLayout.qml [new file with mode: 0644]
src/controls/GlobalDrawer.qml [new file with mode: 0644]
src/controls/Heading.qml [new file with mode: 0644]
src/controls/Hero.qml [new file with mode: 0644]
src/controls/InlineMessage.qml [new file with mode: 0644]
src/controls/ItemViewHeader.qml [new file with mode: 0644]
src/controls/Label.qml [new file with mode: 0644]
src/controls/LinkButton.qml [new file with mode: 0644]
src/controls/ListItemDragHandle.qml [new file with mode: 0644]
src/controls/ListSectionHeader.qml [new file with mode: 0644]
src/controls/LoadingPlaceholder.qml [new file with mode: 0644]
src/controls/MenuDialog.qml [new file with mode: 0644]
src/controls/NavigationTabBar.qml [new file with mode: 0644]
src/controls/NavigationTabButton.qml [new file with mode: 0644]
src/controls/OverlayDrawer.qml [new file with mode: 0644]
src/controls/OverlaySheet.qml [new file with mode: 0644]
src/controls/Page.qml [new file with mode: 0644]
src/controls/PagePoolAction.qml [new file with mode: 0644]
src/controls/PageRow.qml [new file with mode: 0644]
src/controls/PasswordField.qml [new file with mode: 0644]
src/controls/PlaceholderMessage.qml [new file with mode: 0644]
src/controls/PromptDialog.qml [new file with mode: 0644]
src/controls/RouterWindow.qml [new file with mode: 0644]
src/controls/ScrollablePage.qml [new file with mode: 0644]
src/controls/SearchField.qml [new file with mode: 0644]
src/controls/SelectableLabel.qml [new file with mode: 0644]
src/controls/Separator.qml [new file with mode: 0644]
src/controls/ShadowedImage.qml [new file with mode: 0644]
src/controls/SwipeListItem.qml [new file with mode: 0644]
src/controls/ToolBarApplicationHeader.qml [new file with mode: 0644]
src/controls/UrlButton.qml [new file with mode: 0644]
src/controls/private/ActionButton.qml [new file with mode: 0644]
src/controls/private/ActionIconGroup.qml [new file with mode: 0644]
src/controls/private/ActionMenuItem.qml [new file with mode: 0644]
src/controls/private/ActionsMenu.qml [new file with mode: 0644]
src/controls/private/BannerImage.qml [new file with mode: 0644]
src/controls/private/CardsGridViewPrivate.qml [new file with mode: 0644]
src/controls/private/ContextDrawerActionItem.qml [new file with mode: 0644]
src/controls/private/CornerShadow.qml [new file with mode: 0644]
src/controls/private/DefaultCardBackground.qml [new file with mode: 0644]
src/controls/private/DefaultChipBackground.qml [new file with mode: 0644]
src/controls/private/DefaultListItemBackground.qml [new file with mode: 0644]
src/controls/private/DefaultPageTitleDelegate.qml [new file with mode: 0644]
src/controls/private/EdgeShadow.qml [new file with mode: 0644]
src/controls/private/GlobalDrawerActionItem.qml [new file with mode: 0644]
src/controls/private/PageActionPropertyGroup.qml [new file with mode: 0644]
src/controls/private/PrivateActionToolButton.qml [new file with mode: 0644]
src/controls/private/SwipeItemEventFilter.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/AbstractPageHeader.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/BreadcrumbControl.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/TabBarControl.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/TitlesPageHeader.qml [new file with mode: 0644]
src/controls/private/globaltoolbar/ToolBarPageHeader.qml [new file with mode: 0644]
src/controls/qmldir [new file with mode: 0644]
src/controls/settingscomponents/CategorizedSettings.qml [new file with mode: 0644]
src/controls/settingscomponents/SettingAction.qml [new file with mode: 0644]
src/controls/swipenavigator/PageTab.qml [new file with mode: 0644]
src/controls/swipenavigator/PrivateSwipeHighlight.qml [new file with mode: 0644]
src/controls/swipenavigator/PrivateSwipeProgress.qml [new file with mode: 0644]
src/controls/swipenavigator/PrivateSwipeStack.qml [new file with mode: 0644]
src/controls/swipenavigator/PrivateSwipeTab.qml [new file with mode: 0644]
src/controls/swipenavigator/PrivateSwipeTabBar.qml [new file with mode: 0644]
src/controls/swipenavigator/SwipeNavigator.qml [new file with mode: 0644]
src/controls/swipenavigator/TabViewLayout.qml [new file with mode: 0644]
src/controls/swipenavigator/templates/PageTab.qml [new file with mode: 0644]
src/controls/templates/AbstractApplicationHeader.qml [new file with mode: 0644]
src/controls/templates/AbstractCard.qml [new file with mode: 0644]
src/controls/templates/AbstractChip.qml [new file with mode: 0644]
src/controls/templates/AbstractListItem.qml [new file with mode: 0644]
src/controls/templates/ApplicationHeader.qml [new file with mode: 0644]
src/controls/templates/InlineMessage.qml [new file with mode: 0644]
src/controls/templates/OverlayDrawer.qml [new file with mode: 0644]
src/controls/templates/OverlaySheet.qml [new file with mode: 0644]
src/controls/templates/SingletonHeaderSizeGroup.qml [new file with mode: 0644]
src/controls/templates/SwipeListItem.qml [new file with mode: 0644]
src/controls/templates/private/BackButton.qml [new file with mode: 0644]
src/controls/templates/private/BorderPropertiesGroup.qml [new file with mode: 0644]
src/controls/templates/private/ContextIcon.qml [new file with mode: 0644]
src/controls/templates/private/ForwardButton.qml [new file with mode: 0644]
src/controls/templates/private/GenericDrawerIcon.qml [new file with mode: 0644]
src/controls/templates/private/IconPropertiesGroup.qml [new file with mode: 0644]
src/controls/templates/private/MenuIcon.qml [new file with mode: 0644]
src/controls/templates/private/PassiveNotificationsManager.qml [new file with mode: 0644]
src/controls/templates/qmldir [new file with mode: 0644]
src/delegaterecycler.cpp [new file with mode: 0644]
src/delegaterecycler.h [new file with mode: 0644]
src/enums.cpp [new file with mode: 0644]
src/enums.h [new file with mode: 0644]
src/formlayoutattached.cpp [new file with mode: 0644]
src/formlayoutattached.h [new file with mode: 0644]
src/icon.cpp [new file with mode: 0644]
src/icon.h [new file with mode: 0644]
src/imagecolors.cpp [new file with mode: 0644]
src/imagecolors.h [new file with mode: 0644]
src/inputmethod.cpp [new file with mode: 0644]
src/inputmethod.h [new file with mode: 0644]
src/kirigamiplugin.cpp [new file with mode: 0644]
src/kirigamiplugin.h [new file with mode: 0644]
src/libkirigami/CMakeLists.txt [new file with mode: 0644]
src/libkirigami/basictheme.cpp [new file with mode: 0644]
src/libkirigami/basictheme_p.h [new file with mode: 0644]
src/libkirigami/compatheader.h.in [new file with mode: 0644]
src/libkirigami/kirigamipluginfactory.cpp [new file with mode: 0644]
src/libkirigami/kirigamipluginfactory.h [new file with mode: 0644]
src/libkirigami/org.kde.KWin.TabletModeManager.xml [new file with mode: 0644]
src/libkirigami/org.kde.KWin.VirtualKeyboard.xml [new file with mode: 0644]
src/libkirigami/platformtheme.cpp [new file with mode: 0644]
src/libkirigami/platformtheme.h [new file with mode: 0644]
src/libkirigami/styleselector.cpp [new file with mode: 0644]
src/libkirigami/styleselector_p.h [new file with mode: 0644]
src/libkirigami/tabletmodewatcher.cpp [new file with mode: 0644]
src/libkirigami/tabletmodewatcher.h [new file with mode: 0644]
src/libkirigami/units.cpp [new file with mode: 0644]
src/libkirigami/units.h [new file with mode: 0644]
src/libkirigami/virtualkeyboardwatcher.cpp [new file with mode: 0644]
src/libkirigami/virtualkeyboardwatcher.h [new file with mode: 0644]
src/mnemonicattached.cpp [new file with mode: 0644]
src/mnemonicattached.h [new file with mode: 0644]
src/pagepool.cpp [new file with mode: 0644]
src/pagepool.h [new file with mode: 0644]
src/pagerouter.cpp [new file with mode: 0644]
src/pagerouter.h [new file with mode: 0644]
src/plugins.qmltypes [new file with mode: 0644]
src/scenegraph/managedtexturenode.cpp [new file with mode: 0644]
src/scenegraph/managedtexturenode.h [new file with mode: 0644]
src/scenegraph/paintedrectangleitem.cpp [new file with mode: 0644]
src/scenegraph/paintedrectangleitem.h [new file with mode: 0644]
src/scenegraph/shaders/header_desktop.glsl [new file with mode: 0644]
src/scenegraph/shaders/header_desktop_core.glsl [new file with mode: 0644]
src/scenegraph/shaders/header_es.glsl [new file with mode: 0644]
src/scenegraph/shaders/sdf.glsl [new file with mode: 0644]
src/scenegraph/shaders/sdf_lowpower.glsl [new file with mode: 0644]
src/scenegraph/shaders/shaders.qrc [new file with mode: 0644]
src/scenegraph/shaders/shadowedborderrectangle.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedborderrectangle_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedbordertexture.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedbordertexture_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedrectangle.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedrectangle.vert [new file with mode: 0644]
src/scenegraph/shaders/shadowedrectangle_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedtexture.frag [new file with mode: 0644]
src/scenegraph/shaders/shadowedtexture_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders6/CMakeLists.txt [new file with mode: 0644]
src/scenegraph/shaders6/sdf.glsl [new file with mode: 0644]
src/scenegraph/shaders6/sdf_lowpower.glsl [new file with mode: 0644]
src/scenegraph/shaders6/shadowedborderrectangle.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedborderrectangle_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedbordertexture.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedbordertexture_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedrectangle.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedrectangle.vert [new file with mode: 0644]
src/scenegraph/shaders6/shadowedrectangle_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedtexture.frag [new file with mode: 0644]
src/scenegraph/shaders6/shadowedtexture_lowpower.frag [new file with mode: 0644]
src/scenegraph/shaders6/uniforms.glsl [new file with mode: 0644]
src/scenegraph/shadowedborderrectanglematerial.cpp [new file with mode: 0644]
src/scenegraph/shadowedborderrectanglematerial.h [new file with mode: 0644]
src/scenegraph/shadowedbordertexturematerial.cpp [new file with mode: 0644]
src/scenegraph/shadowedbordertexturematerial.h [new file with mode: 0644]
src/scenegraph/shadowedrectanglematerial.cpp [new file with mode: 0644]
src/scenegraph/shadowedrectanglematerial.h [new file with mode: 0644]
src/scenegraph/shadowedrectanglenode.cpp [new file with mode: 0644]
src/scenegraph/shadowedrectanglenode.h [new file with mode: 0644]
src/scenegraph/shadowedtexturematerial.cpp [new file with mode: 0644]
src/scenegraph/shadowedtexturematerial.h [new file with mode: 0644]
src/scenegraph/shadowedtexturenode.cpp [new file with mode: 0644]
src/scenegraph/shadowedtexturenode.h [new file with mode: 0644]
src/scenepositionattached.cpp [new file with mode: 0644]
src/scenepositionattached.h [new file with mode: 0644]
src/settings.cpp [new file with mode: 0644]
src/settings.h [new file with mode: 0644]
src/shadowedrectangle.cpp [new file with mode: 0644]
src/shadowedrectangle.h [new file with mode: 0644]
src/shadowedtexture.cpp [new file with mode: 0644]
src/shadowedtexture.h [new file with mode: 0644]
src/sizegroup.cpp [new file with mode: 0644]
src/sizegroup.h [new file with mode: 0644]
src/spellcheckinghint.cpp [new file with mode: 0644]
src/spellcheckinghint.h [new file with mode: 0644]
src/styles/Material/AbstractListItem.qml [new file with mode: 0644]
src/styles/Material/InlineMessage.qml [new file with mode: 0644]
src/styles/Material/Label.qml [new file with mode: 0644]
src/styles/Material/SwipeListItem.qml [new file with mode: 0644]
src/styles/Material/Theme.qml [new file with mode: 0644]
src/styles/org.kde.desktop/AbstractApplicationHeader.qml [new file with mode: 0644]
src/styles/org.kde.desktop/AbstractListItem.qml [new file with mode: 0644]
src/styles/org.kde.desktop/SwipeListItem.qml [new file with mode: 0644]
src/styles/org.kde.desktop/Theme.qml [new file with mode: 0644]
src/toolbarlayout.cpp [new file with mode: 0644]
src/toolbarlayout.h [new file with mode: 0644]
src/toolbarlayoutdelegate.cpp [new file with mode: 0644]
src/toolbarlayoutdelegate.h [new file with mode: 0644]
src/wheelhandler.cpp [new file with mode: 0644]
src/wheelhandler.h [new file with mode: 0644]
templates/CMakeLists.txt [new file with mode: 0644]
templates/kirigami/CMakeLists.txt [new file with mode: 0644]
templates/kirigami/LICENSES/BSD-3-Clause.txt [new file with mode: 0644]
templates/kirigami/LICENSES/CC0-1.0.txt [new file with mode: 0644]
templates/kirigami/LICENSES/FSFAP.txt [new file with mode: 0644]
templates/kirigami/LICENSES/GPL-2.0-or-later.txt [new file with mode: 0644]
templates/kirigami/android/AndroidManifest.xml [new file with mode: 0644]
templates/kirigami/android/build.gradle [new file with mode: 0644]
templates/kirigami/android/res/drawable/logo.png [new file with mode: 0644]
templates/kirigami/android/res/drawable/splash.xml [new file with mode: 0644]
templates/kirigami/android/version.gradle.in [new file with mode: 0644]
templates/kirigami/kirigami-app.png [new file with mode: 0644]
templates/kirigami/kirigami.kdevtemplate [new file with mode: 0644]
templates/kirigami/org.kde.%{APPNAMELC}.desktop [new file with mode: 0644]
templates/kirigami/org.kde.%{APPNAMELC}.json [new file with mode: 0644]
templates/kirigami/org.kde.%{APPNAMELC}.metainfo.xml [new file with mode: 0644]
templates/kirigami/src/%{APPNAMELC}config.kcfg [new file with mode: 0644]
templates/kirigami/src/%{APPNAMELC}config.kcfgc [new file with mode: 0644]
templates/kirigami/src/CMakeLists.txt [new file with mode: 0644]
templates/kirigami/src/about.cpp [new file with mode: 0644]
templates/kirigami/src/about.h [new file with mode: 0644]
templates/kirigami/src/app.cpp [new file with mode: 0644]
templates/kirigami/src/app.h [new file with mode: 0644]
templates/kirigami/src/contents/ui/About.qml [new file with mode: 0644]
templates/kirigami/src/contents/ui/main.qml [new file with mode: 0644]
templates/kirigami/src/main.cpp [new file with mode: 0644]
templates/kirigami/src/resources.qrc [new file with mode: 0644]
tests/BasicListItemTest.qml [new file with mode: 0644]
tests/CardTest.qml [new file with mode: 0644]
tests/KeyboardListTest.qml [new file with mode: 0644]
tests/KeyboardTest.qml [new file with mode: 0644]
tests/NavigationTabBarTest.qml [new file with mode: 0644]
tests/OverlayFocusTest.qml [new file with mode: 0644]
tests/OverlayTest.qml [new file with mode: 0644]
tests/ShadowedImageTest.qml [new file with mode: 0644]
tests/ShadowedRectangleTest.qml [new file with mode: 0644]
tests/actionsMenu.qml [new file with mode: 0644]
tests/cardsList.qml [new file with mode: 0644]
tests/swipeListItemTest.qml [new file with mode: 0644]
tests/wheelhandler/ScrollView.qml [new file with mode: 0644]
tests/wheelhandler/WheelHandlerFlickableTest.qml [new file with mode: 0644]
tests/wheelhandler/WheelHandlerScrollViewTest.qml [new file with mode: 0644]
tests/wheelhandler/WheelHandlerScrollViewTextAreaTest.qml [new file with mode: 0644]
tests/wheelhandler/scrollableqtextedit.ui [new file with mode: 0644]

diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644 (file)
index 0000000..c72c528
--- /dev/null
@@ -0,0 +1,2 @@
+#clang-tidy
+ae47f7f9553bd222123acc77f336753840173c57
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..dcac9c9
--- /dev/null
@@ -0,0 +1,29 @@
+# Ignore the following files
+*~
+*.[oa]
+*.diff
+*.kate-swp
+*.kdev4
+.kdev_include_paths
+*.kdevelop.pcs
+*.moc
+*.moc.cpp
+*.orig
+*.user
+.*.swp
+.swp.*
+Doxyfile
+Makefile
+avail
+random_seed
+/build*/
+CMakeLists.txt.user*
+*.unc-backup*
+.cmake/
+.vscode/
+/.clang-format
+/compile_commands.json
+.clangd
+.idea
+/cmake-build*
+.cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644 (file)
index 0000000..b6f4767
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
+# SPDX-License-Identifier: CC0-1.0
+
+include:
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
+
diff --git a/.kde-ci.yml b/.kde-ci.yml
new file mode 100644 (file)
index 0000000..b0e19f9
--- /dev/null
@@ -0,0 +1,10 @@
+Dependencies:
+- 'on': ['Linux', 'FreeBSD', 'Windows', 'Android', 'iOS']
+  'require':
+    'frameworks/extra-cmake-modules': '@same'
+
+Options:
+  test-before-installing: True
+  require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD/Qt5', 'Windows/Qt5' ]
+  cmake-options: -DBUILD_EXAMPLES=ON
+  cppcheck-ignore-files: ['templates/']
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9579031
--- /dev/null
@@ -0,0 +1,164 @@
+cmake_minimum_required(VERSION 3.16)
+
+set(KF_VERSION "5.107.0") # handled by release scripts
+set(KF_DEP_VERSION "5.107.0") # handled by release scripts
+
+project(kirigami2 VERSION ${KF_VERSION})
+
+set(REQUIRED_QT_VERSION 5.15.2)
+
+################# Disallow in-source build #################
+
+if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
+   message(FATAL_ERROR "kirigami requires an out of source build. Please create a separate build directory and run 'cmake path_to_kirigami [options]' there.")
+endif()
+
+option(BUILD_SHARED_LIBS "Build a shared module" ON)
+option(DESKTOP_ENABLED "Build and install The Desktop style" ON)
+option(BUILD_EXAMPLES "Build and install examples" OFF)
+option(DISABLE_DBUS "Build without D-Bus support" OFF)
+option(UBUNTU_TOUCH "Build for Ubuntu Touch" OFF)
+if(DEFINED STATIC_LIBRARY)
+    message(FATAL_ERROR "Use the BUILD_SHARED_LIBS=OFF option to build a static library, STATIC_LIBRARY is no longer a supported option")
+endif()
+find_package(ECM 5.107.0 NO_MODULE)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
+
+if (NOT ${BUILD_SHARED_LIBS})
+    # Examples are not supported when building a static library, so force them
+    # to OFF.
+    set(BUILD_EXAMPLES OFF)
+endif()
+
+# Make CPack available to easy generate binary packages
+include(CPack)
+include(FeatureSummary)
+include(KDEInstallDirs)
+find_package(Qt${QT_MAJOR_VERSION} ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg QuickControls2 Concurrent)
+if (QT_MAJOR_VERSION EQUAL "6")
+    find_package(Qt6 REQUIRED NO_MODULE COMPONENTS ShaderTools)
+endif()
+if (BUILD_TESTING)
+    find_package(Qt${QT_MAJOR_VERSION}QuickTest ${REQUIRED_QT_VERSION} CONFIG QUIET)
+endif()
+get_target_property(QtGui_Enabled_Features Qt${QT_MAJOR_VERSION}::Gui QT_ENABLED_PUBLIC_FEATURES)
+if(QtGui_Enabled_Features MATCHES "opengl")
+    set(HAVE_QTGUI_OPENGL 1)
+else()
+    set(HAVE_QTGUI_OPENGL 0)
+endif()
+add_feature_info(QtGuiOpenGL HAVE_QTGUI_OPENGL "QtGui built with support for OpenGL")
+set(CMAKE_AUTOMOC ON)
+set(AUTOMOC_MOC_OPTIONS -Muri=org.kde.kirigami)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+if(NOT BUILD_SHARED_LIBS)
+    add_definitions(-DKIRIGAMI_BUILD_TYPE_STATIC)
+    add_definitions(-DQT_PLUGIN)
+    add_definitions(-DQT_STATICPLUGIN=1)
+endif()
+
+################# set KDE specific information #################
+# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked
+set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
+
+include(ECMGenerateExportHeader)
+include(ECMSetupVersion)
+include(ECMGenerateHeaders)
+include(CMakePackageConfigHelpers)
+include(ECMPoQmTools)
+include(ECMFindQmlModule)
+include(KDEInstallDirs)
+include(KDECMakeSettings)
+include(KDEGitCommitHooks)
+include(ECMQtDeclareLoggingCategory)
+include(ECMAddQch)
+include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
+include(KDEPackageAppTemplates)
+include(ECMGenerateQmlTypes)
+include(ECMQmlModule)
+include(ECMDeprecationSettings)
+
+set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Kirigami2")
+
+option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
+add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
+
+configure_package_config_file(
+    "${CMAKE_CURRENT_SOURCE_DIR}/KF5Kirigami2Config.cmake.in"
+    "${CMAKE_CURRENT_BINARY_DIR}/KF5Kirigami2Config.cmake"
+    INSTALL_DESTINATION  ${CMAKECONFIG_INSTALL_DIR}
+    PATH_VARS  CMAKE_INSTALL_PREFIX
+)
+
+install(FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/KF5Kirigami2Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/KF5Kirigami2ConfigVersion.cmake"
+    "${CMAKE_CURRENT_SOURCE_DIR}/KF5Kirigami2Macros.cmake"
+    DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+    COMPONENT Devel
+)
+
+install(EXPORT KF5Kirigami2Targets
+    DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+    FILE KF5Kirigami2Targets.cmake
+    NAMESPACE KF5::
+)
+
+
+ecm_setup_version(${KF_VERSION}
+    VARIABLE_PREFIX KIRIGAMI2
+    VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kirigami_version.h"
+    PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5Kirigami2ConfigVersion.cmake"
+    SOVERSION 5
+)
+
+#use dbus on linux, bsd etc, but not android and apple stuff
+if (UNIX AND NOT ANDROID AND NOT(APPLE) AND NOT(DISABLE_DBUS))
+    find_package(Qt${QT_MAJOR_VERSION}DBus)
+    add_definitions(-DKIRIGAMI_ENABLE_DBUS)
+endif()
+
+find_package(OpenMP)
+set_package_properties(OpenMP
+    PROPERTIES DESCRIPTION "Multi-platform shared-memory parallel programming in C/C++ and Fortran"
+    TYPE OPTIONAL
+    PURPOSE "Accelerates palette generation in Kirigami.ImageColors"
+)
+if(OpenMP_CXX_FOUND)
+    set(HAVE_OpenMP TRUE)
+    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
+endif()
+
+include_directories("${CMAKE_CURRENT_BINARY_DIR}")
+configure_file(config-OpenMP.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-OpenMP.h)
+
+if (UBUNTU_TOUCH)
+    add_definitions(-DUBUNTU_TOUCH)
+endif()
+
+ecm_find_qmlmodule(QtGraphicalEffects 1.0)
+
+ecm_set_disabled_deprecation_versions(
+    QT 5.15.2
+    KF 5.95
+)
+
+add_subdirectory(src)
+if (NOT ANDROID)
+    add_subdirectory(templates)
+endif()
+
+if (BUILD_EXAMPLES)
+    add_subdirectory(examples)
+endif()
+
+if (BUILD_TESTING)
+    add_subdirectory(autotests)
+endif()
+
+ecm_install_po_files_as_qm(poqm)
+
+feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/ExtraDesktop.sh b/ExtraDesktop.sh
new file mode 100644 (file)
index 0000000..0c46ec1
--- /dev/null
@@ -0,0 +1,4 @@
+#! /bin/sh
+#This file outputs in a separate line each file with a .desktop syntax
+#that needs to be translated but has a non .desktop extension
+find -name \*.kdevtemplate -print
diff --git a/KF5Kirigami2Config.cmake.in b/KF5Kirigami2Config.cmake.in
new file mode 100644 (file)
index 0000000..ce42cdf
--- /dev/null
@@ -0,0 +1,15 @@
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+find_dependency(Qt@QT_MAJOR_VERSION@Core @REQUIRED_QT_VERSION@)
+
+# Any changes in this ".cmake" file will be overwritten by CMake, the source is the ".cmake.in" file.
+
+include("${CMAKE_CURRENT_LIST_DIR}/KF5Kirigami2Targets.cmake")
+
+set(Kirigami2_INSTALL_PREFIX "@PACKAGE_CMAKE_INSTALL_PREFIX@")
+
+#set(Kirigami2_LIBRARIES KF5::Kirigami2)
+
+include("${CMAKE_CURRENT_LIST_DIR}/KF5Kirigami2Macros.cmake")
+@PACKAGE_INCLUDE_QCHTARGETS@
diff --git a/KF5Kirigami2Macros.cmake b/KF5Kirigami2Macros.cmake
new file mode 100644 (file)
index 0000000..6c0bbcf
--- /dev/null
@@ -0,0 +1,95 @@
+include(CMakeParseArguments)
+include(ExternalProject)
+
+
+function(kirigami_package_breeze_icons)
+    set(_multiValueArgs ICONS)
+    cmake_parse_arguments(ARG "" "" "${_multiValueArgs}" ${ARGN} )
+
+    if(NOT ARG_ICONS)
+        message(FATAL_ERROR "No ICONS argument given to kirigami_package_breeze_icons")
+    endif()
+
+    #include icons used by Kirigami components themselves
+    set(ARG_ICONS ${ARG_ICONS} go-next go-previous go-up handle-sort overflow-menu-left overflow-menu-right go-next-symbolic go-next-symbolic-rtl go-previous-symbolic go-previous-symbolic-rtl overflow-menu user view-left-new view-right-new view-left-close view-right-close dialog-positive dialog-warning dialog-error dialog-information dialog-close globe mail-sent tools-report-bug)
+
+    function(_find_breeze_icon icon varName)
+        #HACKY
+        SET(path "")
+        file(GLOB_RECURSE path ${_BREEZEICONS_DIR}/icons/*/48/${icon}.svg )
+
+        #search in other sizes as well
+        if (path STREQUAL "")
+            file(GLOB_RECURSE path ${_BREEZEICONS_DIR}/icons/*/32/${icon}.svg )
+            if (path STREQUAL "")
+                file(GLOB_RECURSE path ${_BREEZEICONS_DIR}/icons/*/22/${icon}.svg )
+                if (path STREQUAL "")
+                    file(GLOB_RECURSE path ${_BREEZEICONS_DIR}/icons/*/16/${icon}.svg )
+                endif()
+            endif()
+        endif()
+        if (path STREQUAL "")
+            file(GLOB_RECURSE path ${_BREEZEICONS_DIR}/icons/*/symbolic/${icon}.svg )
+        endif()
+        if (path STREQUAL "")
+            return()
+        endif()
+
+        list(LENGTH path _count_paths)
+        if (_count_paths GREATER 1)
+            message(WARNING "Found more than one version of '${icon}': ${path}")
+        endif()
+        list(GET path 0 path)
+        get_filename_component(path "${path}" REALPATH)
+
+        SET(${varName} ${path} PARENT_SCOPE)
+    endfunction()
+
+    if (BREEZEICONS_DIR AND NOT EXISTS ${BREEZEICONS_DIR})
+        message(FATAL_ERROR "BREEZEICONS_DIR variable does not point to existing dir: \"${BREEZEICONS_DIR}\"")
+    endif()
+
+    set(_BREEZEICONS_DIR "${BREEZEICONS_DIR}")
+
+    #FIXME: this is a terrible hack
+    if(NOT _BREEZEICONS_DIR)
+        set(_BREEZEICONS_DIR "${CMAKE_BINARY_DIR}/breeze-icons/src/breeze-icons")
+
+        # replacement for ExternalProject_Add not yet working
+        # first time config?
+        if (NOT EXISTS ${_BREEZEICONS_DIR})
+            find_package(Git)
+            execute_process(COMMAND ${GIT_EXECUTABLE} clone --depth 1 https://invent.kde.org/frameworks/breeze-icons.git ${_BREEZEICONS_DIR})
+        endif()
+
+        # external projects are only pulled at make time, not configure time
+        # so this is too late to work with the _find_breeze_icon() method
+        # _find_breeze_icon() would need to be turned into a target/command
+        if (FALSE)
+        ExternalProject_Add(
+            breeze-icons
+            PREFIX breeze-icons
+            GIT_REPOSITORY https://invent.kde.org/frameworks/breeze-icons.git
+            CONFIGURE_COMMAND ""
+            BUILD_COMMAND ""
+            INSTALL_COMMAND ""
+            LOG_DOWNLOAD ON
+        )
+        endif()
+    endif()
+
+    message (STATUS "Found external breeze icons:")
+    foreach(_iconName ${ARG_ICONS})
+        set(_iconPath "")
+        _find_breeze_icon(${_iconName} _iconPath)
+        message (STATUS ${_iconPath})
+        if (EXISTS ${_iconPath})
+            install(FILES ${_iconPath} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/icons/ RENAME ${_iconName}.svg)
+        endif()
+    endforeach()
+
+    #generate an index.theme that qiconloader can understand
+    file(WRITE ${CMAKE_BINARY_DIR}/index.theme "[Icon Theme]\nName=Breeze\nDirectories=icons\n[icons]\nSize=32\nType=Scalable")
+    install(FILES ${CMAKE_BINARY_DIR}/index.theme DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/)
+endfunction()
+
diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt
new file mode 100644 (file)
index 0000000..0e259d4
--- /dev/null
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt
new file mode 100644 (file)
index 0000000..5c96471
--- /dev/null
@@ -0,0 +1,446 @@
+GNU LIBRARY GENERAL PUBLIC LICENSE
+
+Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
+
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is numbered 2 because
+it goes with version 2 of the ordinary GPL.]
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public Licenses are intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.
+
+This license, the Library General Public License, applies to some specially
+designated Free Software Foundation software, and to any other libraries whose
+authors decide to use it. You can use it for your libraries, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the library, or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for
+a fee, you must give the recipients all the rights that we gave you. You must
+make sure that they, too, receive or can get the source code. If you link
+a program with the library, you must provide complete object files to the
+recipients so that they can relink them with the library, after making changes
+to the library and recompiling it. And you must show them these terms so they
+know their rights.
+
+Our method of protecting your rights has two steps: (1) copyright the library,
+and (2) offer you this license which gives you legal permission to copy, distribute
+and/or modify the library.
+
+Also, for each distributor's protection, we want to make certain that everyone
+understands that there is no warranty for this free library. If the library
+is modified by someone else and passed on, we want its recipients to know
+that what they have is not the original version, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that companies distributing free software will individually
+obtain patent licenses, thus in effect transforming the program into proprietary
+software. To prevent this, we have made it clear that any patent must be licensed
+for everyone's free use or not licensed at all.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU
+General Public License, which was designed for utility programs. This license,
+the GNU Library General Public License, applies to certain designated libraries.
+This license is quite different from the ordinary one; be sure to read it
+in full, and don't assume that anything in it is the same as in the ordinary
+license.
+
+The reason we have a separate public license for some libraries is that they
+blur the distinction we usually make between modifying or adding to a program
+and simply using it. Linking a program with a library, without changing the
+library, is in some sense simply using the library, and is analogous to running
+a utility program or application program. However, in a textual and legal
+sense, the linked executable is a combined work, a derivative of the original
+library, and the ordinary General Public License treats it as such.
+
+Because of this blurred distinction, using the ordinary General Public License
+for libraries did not effectively promote software sharing, because most developers
+did not use the libraries. We concluded that weaker conditions might promote
+sharing better.
+
+However, unrestricted linking of non-free programs would deprive the users
+of those programs of all benefit from the free status of the libraries themselves.
+This Library General Public License is intended to permit developers of non-free
+programs to use free libraries, while preserving your freedom as a user of
+such programs to change the free libraries that are incorporated in them.
+(We have not seen how to achieve this as regards changes in header files,
+but we have achieved it as regards changes in the actual functions of the
+Library.) The hope is that this will lead to faster development of free libraries.
+
+The precise terms and conditions for copying, distribution and modification
+follow. Pay close attention to the difference between a "work based on the
+library" and a "work that uses the library". The former contains code derived
+from the library, while the latter only works together with the library.
+
+Note that it is possible for a library to be covered by the ordinary General
+Public License rather than by this special one.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library which contains a
+notice placed by the copyright holder or other authorized party saying it
+may be distributed under the terms of this Library General Public License
+(also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared
+so as to be conveniently linked with application programs (which use some
+of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has
+been distributed under these terms. A "work based on the Library" means either
+the Library or any derivative work under copyright law: that is to say, a
+work containing the Library or a portion of it, either verbatim or with modifications
+and/or translated straightforwardly into another language. (Hereinafter, translation
+is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications
+to it. For a library, complete source code means all the source code for all
+modules it contains, plus any associated interface definition files, plus
+the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running a program
+using the Library is not restricted, and output from such a program is covered
+only if its contents constitute a work based on the Library (independent of
+the use of the Library in a tool for writing it). Whether that is true depends
+on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and disclaimer
+of warranty; keep intact all the notices that refer to this License and to
+the absence of any warranty; and distribute a copy of this License along with
+the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it,
+thus forming a work based on the Library, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+      a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that
+you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all
+third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of
+data to be supplied by an application program that uses the facility, other
+than as an argument passed when the facility is invoked, then you must make
+a good faith effort to ensure that, in the event an application does not supply
+such function or table, the facility still operates, and performs whatever
+part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose
+that is entirely well-defined independent of the application. Therefore, Subsection
+2d requires that any application-supplied function or table used by this function
+must be optional: if the application does not supply it, the square root function
+must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Library, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Library.
+
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License
+instead of this License to a given copy of the Library. To do this, you must
+alter all the notices that refer to this License, so that they refer to the
+ordinary GNU General Public License, version 2, instead of to this License.
+(If a newer version than version 2 of the ordinary GNU General Public License
+has appeared, then you can specify that version instead if you wish.) Do not
+make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy,
+so the ordinary GNU General Public License applies to all subsequent copies
+and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library
+into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of
+it, under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you accompany it with the complete corresponding
+machine-readable source code, which must be distributed under the terms of
+Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated
+place, then offering equivalent access to copy the source code from the same
+place satisfies the requirement to distribute the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but
+is designed to work with the Library by being compiled or linked with it,
+is called a "work that uses the Library". Such a work, in isolation, is not
+a derivative work of the Library, and therefore falls outside the scope of
+this License.
+
+However, linking a "work that uses the Library" with the Library creates an
+executable that is a derivative of the Library (because it contains portions
+of the Library), rather than a "work that uses the library". The executable
+is therefore covered by this License. Section 6 states terms for distribution
+of such executables.
+
+When a "work that uses the Library" uses material from a header file that
+is part of the Library, the object code for the work may be a derivative work
+of the Library even though the source code is not. Whether this is true is
+especially significant if the work can be linked without the Library, or if
+the work is itself a library. The threshold for this to be true is not precisely
+defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts
+and accessors, and small macros and small inline functions (ten lines or less
+in length), then the use of the object file is unrestricted, regardless of
+whether it is legally a derivative work. (Executables containing this object
+code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute
+the object code for the work under the terms of Section 6. Any executables
+containing that work also fall under Section 6, whether or not they are linked
+directly with the Library itself.
+
+6. As an exception to the Sections above, you may also compile or link a "work
+that uses the Library" with the Library to produce a work containing portions
+of the Library, and distribute that work under terms of your choice, provided
+that the terms permit modification of the work for the customer's own use
+and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library
+is used in it and that the Library and its use are covered by this License.
+You must supply a copy of this License. If the work during execution displays
+copyright notices, you must include the copyright notice for the Library among
+them, as well as a reference directing the user to the copy of this License.
+Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source
+code for the Library including whatever changes were used in the work (which
+must be distributed under Sections 1 and 2 above); and, if the work is an
+executable linked with the Library, with the complete machine-readable "work
+that uses the Library", as object code and/or source code, so that the user
+can modify the Library and then relink to produce a modified executable containing
+the modified Library. (It is understood that the user who changes the contents
+of definitions files in the Library will not necessarily be able to recompile
+the application to use the modified definitions.)
+
+b) Accompany the work with a written offer, valid for at least three years,
+to give the same user the materials specified in Subsection 6a, above, for
+a charge no more than the cost of performing this distribution.
+
+c) If distribution of the work is made by offering access to copy from a designated
+place, offer equivalent access to copy the above specified materials from
+the same place.
+
+d) Verify that the user has already received a copy of these materials or
+that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must
+include any data and utility programs needed for reproducing the executable
+from it. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the operating
+system on which the executable runs, unless that component itself accompanies
+the executable.
+
+It may happen that this requirement contradicts the license restrictions of
+other proprietary libraries that do not normally accompany the operating system.
+Such a contradiction means you cannot use both them and the Library together
+in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side
+in a single library together with other library facilities not covered by
+this License, and distribute such a combined library, provided that the separate
+distribution of the work based on the Library and of the other library facilities
+is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities. This must be distributed
+under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of
+it is a work based on the Library, and explaining where to find the accompanying
+uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library
+except as expressly provided under this License. Any attempt otherwise to
+copy, modify, sublicense, link with, or distribute the Library is void, and
+will automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will not
+have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Library or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Library
+(or any work based on the Library), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library),
+the recipient automatically receives a license from the original licensor
+to copy, distribute, link with or modify the Library subject to these terms
+and conditions. You may not impose any further restrictions on the recipients'
+exercise of the rights granted herein. You are not responsible for enforcing
+compliance by third parties to this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Library at all. For example, if a
+patent license would not permit royalty-free redistribution of the Library
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Library under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of
+the Library General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Library does not specify a license version number, you may choose any version
+ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs
+whose distribution conditions are incompatible with these, write to the author
+to ask for permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make exceptions
+for this. Our decision will be guided by the two goals of preserving the free
+status of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+   NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Libraries
+
+If you develop a new library, and you want it to be of the greatest possible
+use to the public, we recommend making it free software that everyone can
+redistribute and change. You can do so by permitting redistribution under
+these terms (or, alternatively, under the terms of the ordinary General Public
+License).
+
+To apply these terms, attach the following notices to the library. It is safest
+to attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+one line to give the library's name and an idea of what it does.
+
+Copyright (C) year name of author
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Library General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your option)
+any later version.
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
+details.
+
+You should have received a copy of the GNU Library General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the library, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in
+
+the library `Frob' (a library for tweaking knobs) written
+
+by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt
new file mode 100644 (file)
index 0000000..130dffb
--- /dev/null
@@ -0,0 +1,467 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts as the
+successor of the GNU Library Public License, version 2, hence the version
+number 2.1.]
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public Licenses are intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially
+designated software packages--typically libraries--of the Free Software Foundation
+and other authors who decide to use it. You can use it too, but we suggest
+you first think carefully about whether this license or the ordinary General
+Public License is the better strategy to use in any particular case, based
+on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price.
+Our General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish); that you receive source code or can get it if you want it; that you
+can change the software and use pieces of it in new free programs; and that
+you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors
+to deny you these rights or to ask you to surrender these rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for
+a fee, you must give the recipients all the rights that we gave you. You must
+make sure that they, too, receive or can get the source code. If you link
+other code with the library, you must provide complete object files to the
+recipients, so that they can relink them with the library after making changes
+to the library and recompiling it. And you must show them these terms so they
+know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library,
+and (2) we offer you this license, which gives you legal permission to copy,
+distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no
+warranty for the free library. Also, if the library is modified by someone
+else and passed on, the recipients should know that what they have is not
+the original version, so that the original author's reputation will not be
+affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free
+program. We wish to make sure that a company cannot effectively restrict the
+users of a free program by obtaining a restrictive license from a patent holder.
+Therefore, we insist that any patent license obtained for a version of the
+library must be consistent with the full freedom of use specified in this
+license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU
+General Public License. This license, the GNU Lesser General Public License,
+applies to certain designated libraries, and is quite different from the ordinary
+General Public License. We use this license for certain libraries in order
+to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared
+library, the combination of the two is legally speaking a combined work, a
+derivative of the original library. The ordinary General Public License therefore
+permits such linking only if the entire combination fits its criteria of freedom.
+The Lesser General Public License permits more lax criteria for linking other
+code with the library.
+
+We call this license the "Lesser" General Public License because it does Less
+to protect the user's freedom than the ordinary General Public License. It
+also provides other free software developers Less of an advantage over competing
+non-free programs. These disadvantages are the reason we use the ordinary
+General Public License for many libraries. However, the Lesser license provides
+advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the
+widest possible use of a certain library, so that it becomes a de-facto standard.
+To achieve this, non-free programs must be allowed to use the library. A more
+frequent case is that a free library does the same job as widely used non-free
+libraries. In this case, there is little to gain by limiting the free library
+to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs
+enables a greater number of people to use a large body of free software. For
+example, permission to use the GNU C Library in non-free programs enables
+many more people to use the whole GNU operating system, as well as its variant,
+the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users'
+freedom, it does ensure that the user of a program that is linked with the
+Library has the freedom and the wherewithal to run that program using a modified
+version of the Library.
+
+The precise terms and conditions for copying, distribution and modification
+follow. Pay close attention to the difference between a "work based on the
+library" and a "work that uses the library". The former contains code derived
+from the library, whereas the latter must be combined with the library in
+order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library or other program
+which contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Lesser General
+Public License (also called "this License"). Each licensee is addressed as
+"you".
+
+A "library" means a collection of software functions and/or data prepared
+so as to be conveniently linked with application programs (which use some
+of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has
+been distributed under these terms. A "work based on the Library" means either
+the Library or any derivative work under copyright law: that is to say, a
+work containing the Library or a portion of it, either verbatim or with modifications
+and/or translated straightforwardly into another language. (Hereinafter, translation
+is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications
+to it. For a library, complete source code means all the source code for all
+modules it contains, plus any associated interface definition files, plus
+the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running a program
+using the Library is not restricted, and output from such a program is covered
+only if its contents constitute a work based on the Library (independent of
+the use of the Library in a tool for writing it). Whether that is true depends
+on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and disclaimer
+of warranty; keep intact all the notices that refer to this License and to
+the absence of any warranty; and distribute a copy of this License along with
+the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it,
+thus forming a work based on the Library, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+      a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that
+you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all
+third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of
+data to be supplied by an application program that uses the facility, other
+than as an argument passed when the facility is invoked, then you must make
+a good faith effort to ensure that, in the event an application does not supply
+such function or table, the facility still operates, and performs whatever
+part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose
+that is entirely well-defined independent of the application. Therefore, Subsection
+2d requires that any application-supplied function or table used by this function
+must be optional: if the application does not supply it, the square root function
+must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Library, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Library.
+
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License
+instead of this License to a given copy of the Library. To do this, you must
+alter all the notices that refer to this License, so that they refer to the
+ordinary GNU General Public License, version 2, instead of to this License.
+(If a newer version than version 2 of the ordinary GNU General Public License
+has appeared, then you can specify that version instead if you wish.) Do not
+make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy,
+so the ordinary GNU General Public License applies to all subsequent copies
+and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library
+into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of
+it, under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you accompany it with the complete corresponding
+machine-readable source code, which must be distributed under the terms of
+Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated
+place, then offering equivalent access to copy the source code from the same
+place satisfies the requirement to distribute the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but
+is designed to work with the Library by being compiled or linked with it,
+is called a "work that uses the Library". Such a work, in isolation, is not
+a derivative work of the Library, and therefore falls outside the scope of
+this License.
+
+However, linking a "work that uses the Library" with the Library creates an
+executable that is a derivative of the Library (because it contains portions
+of the Library), rather than a "work that uses the library". The executable
+is therefore covered by this License. Section 6 states terms for distribution
+of such executables.
+
+When a "work that uses the Library" uses material from a header file that
+is part of the Library, the object code for the work may be a derivative work
+of the Library even though the source code is not. Whether this is true is
+especially significant if the work can be linked without the Library, or if
+the work is itself a library. The threshold for this to be true is not precisely
+defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts
+and accessors, and small macros and small inline functions (ten lines or less
+in length), then the use of the object file is unrestricted, regardless of
+whether it is legally a derivative work. (Executables containing this object
+code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute
+the object code for the work under the terms of Section 6. Any executables
+containing that work also fall under Section 6, whether or not they are linked
+directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work
+that uses the Library" with the Library to produce a work containing portions
+of the Library, and distribute that work under terms of your choice, provided
+that the terms permit modification of the work for the customer's own use
+and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library
+is used in it and that the Library and its use are covered by this License.
+You must supply a copy of this License. If the work during execution displays
+copyright notices, you must include the copyright notice for the Library among
+them, as well as a reference directing the user to the copy of this License.
+Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source
+code for the Library including whatever changes were used in the work (which
+must be distributed under Sections 1 and 2 above); and, if the work is an
+executable linked with the Library, with the complete machine-readable "work
+that uses the Library", as object code and/or source code, so that the user
+can modify the Library and then relink to produce a modified executable containing
+the modified Library. (It is understood that the user who changes the contents
+of definitions files in the Library will not necessarily be able to recompile
+the application to use the modified definitions.)
+
+b) Use a suitable shared library mechanism for linking with the Library. A
+suitable mechanism is one that (1) uses at run time a copy of the library
+already present on the user's computer system, rather than copying library
+functions into the executable, and (2) will operate properly with a modified
+version of the library, if the user installs one, as long as the modified
+version is interface-compatible with the version that the work was made with.
+
+c) Accompany the work with a written offer, valid for at least three years,
+to give the same user the materials specified in Subsection 6a, above, for
+a charge no more than the cost of performing this distribution.
+
+d) If distribution of the work is made by offering access to copy from a designated
+place, offer equivalent access to copy the above specified materials from
+the same place.
+
+e) Verify that the user has already received a copy of these materials or
+that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must
+include any data and utility programs needed for reproducing the executable
+from it. However, as a special exception, the materials to be distributed
+need not include anything that is normally distributed (in either source or
+binary form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component itself
+accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of
+other proprietary libraries that do not normally accompany the operating system.
+Such a contradiction means you cannot use both them and the Library together
+in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side
+in a single library together with other library facilities not covered by
+this License, and distribute such a combined library, provided that the separate
+distribution of the work based on the Library and of the other library facilities
+is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities. This must be distributed
+under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of
+it is a work based on the Library, and explaining where to find the accompanying
+uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library
+except as expressly provided under this License. Any attempt otherwise to
+copy, modify, sublicense, link with, or distribute the Library is void, and
+will automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will not
+have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Library or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Library
+(or any work based on the Library), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library),
+the recipient automatically receives a license from the original licensor
+to copy, distribute, link with or modify the Library subject to these terms
+and conditions. You may not impose any further restrictions on the recipients'
+exercise of the rights granted herein. You are not responsible for enforcing
+compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Library at all. For example, if a
+patent license would not permit royalty-free redistribution of the Library
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Library under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of
+the Lesser General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Library does not specify a license version number, you may choose any version
+ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs
+whose distribution conditions are incompatible with these, write to the author
+to ask for permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make exceptions
+for this. Our decision will be guided by the two goals of preserving the free
+status of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+   NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Libraries
+
+If you develop a new library, and you want it to be of the greatest possible
+use to the public, we recommend making it free software that everyone can
+redistribute and change. You can do so by permitting redistribution under
+these terms (or, alternatively, under the terms of the ordinary General Public
+License).
+
+To apply these terms, attach the following notices to the library. It is safest
+to attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+< one line to give the library's name and an idea of what it does. >
+
+Copyright (C) < year > < name of author >
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to the Free Software Foundation, Inc., 51
+Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information
+on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the library, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in
+
+the library `Frob' (a library for tweaking knobs) written
+
+by James Random Hacker.
+
+< signature of Ty Coon > , 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt
new file mode 100644 (file)
index 0000000..bd405af
--- /dev/null
@@ -0,0 +1,163 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the terms
+and conditions of version 3 of the GNU General Public License, supplemented
+by the additional permissions listed below.
+
+   0. Additional Definitions.
+
+      
+
+As used herein, "this License" refers to version 3 of the GNU Lesser General
+Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
+License.
+
+      
+
+"The Library" refers to a covered work governed by this License, other than
+an Application or a Combined Work as defined below.
+
+      
+
+An "Application" is any work that makes use of an interface provided by the
+Library, but which is not otherwise based on the Library. Defining a subclass
+of a class defined by the Library is deemed a mode of using an interface provided
+by the Library.
+
+      
+
+A "Combined Work" is a work produced by combining or linking an Application
+with the Library. The particular version of the Library with which the Combined
+Work was made is also called the "Linked Version".
+
+      
+
+The "Minimal Corresponding Source" for a Combined Work means the Corresponding
+Source for the Combined Work, excluding any source code for portions of the
+Combined Work that, considered in isolation, are based on the Application,
+and not on the Linked Version.
+
+      
+
+The "Corresponding Application Code" for a Combined Work means the object
+code and/or source code for the Application, including any data and utility
+programs needed for reproducing the Combined Work from the Application, but
+excluding the System Libraries of the Combined Work.
+
+   1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License without
+being bound by section 3 of the GNU GPL.
+
+   2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a facility
+refers to a function or data to be supplied by an Application that uses the
+facility (other than as an argument passed when the facility is invoked),
+then you may convey a copy of the modified version:
+
+a) under this License, provided that you make a good faith effort to ensure
+that, in the event an Application does not supply the function or data, the
+facility still operates, and performs whatever part of its purpose remains
+meaningful, or
+
+b) under the GNU GPL, with none of the additional permissions of this License
+applicable to that copy.
+
+   3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a header
+file that is part of the Library. You may convey such object code under terms
+of your choice, provided that, if the incorporated material is not limited
+to numerical parameters, data structure layouts and accessors, or small macros,
+inline functions and templates (ten or fewer lines in length), you do both
+of the following:
+
+a) Give prominent notice with each copy of the object code that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the object code with a copy of the GNU GPL and this license document.
+
+   4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken together,
+effectively do not restrict modification of the portions of the Library contained
+in the Combined Work and reverse engineering for debugging such modifications,
+if you also do each of the following:
+
+a) Give prominent notice with each copy of the Combined Work that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the Combined Work with a copy of the GNU GPL and this license
+document.
+
+c) For a Combined Work that displays copyright notices during execution, include
+the copyright notice for the Library among these notices, as well as a reference
+directing the user to the copies of the GNU GPL and this license document.
+
+      d) Do one of the following:
+
+0) Convey the Minimal Corresponding Source under the terms of this License,
+and the Corresponding Application Code in a form suitable for, and under terms
+that permit, the user to recombine or relink the Application with a modified
+version of the Linked Version to produce a modified Combined Work, in the
+manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
+
+1) Use a suitable shared library mechanism for linking with the Library. A
+suitable mechanism is one that (a) uses at run time a copy of the Library
+already present on the user's computer system, and (b) will operate properly
+with a modified version of the Library that is interface-compatible with the
+Linked Version.
+
+e) Provide Installation Information, but only if you would otherwise be required
+to provide such information under section 6 of the GNU GPL, and only to the
+extent that such information is necessary to install and execute a modified
+version of the Combined Work produced by recombining or relinking the Application
+with a modified version of the Linked Version. (If you use option 4d0, the
+Installation Information must accompany the Minimal Corresponding Source and
+Corresponding Application Code. If you use option 4d1, you must provide the
+Installation Information in the manner specified by section 6 of the GNU GPL
+for conveying Corresponding Source.)
+
+   5. Combined Libraries.
+
+You may place library facilities that are a work based on the Library side
+by side in a single library together with other library facilities that are
+not Applications and are not covered by this License, and convey such a combined
+library under terms of your choice, if you do both of the following:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities, conveyed under the
+terms of this License.
+
+b) Give prominent notice with the combined library that part of it is a work
+based on the Library, and explaining where to find the accompanying uncombined
+form of the same work.
+
+   6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU Lesser General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library as you
+received it specifies that a certain numbered version of the GNU Lesser General
+Public License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that published version or of
+any later version published by the Free Software Foundation. If the Library
+as you received it does not specify a version number of the GNU Lesser General
+Public License, you may choose any version of the GNU Lesser General Public
+License ever published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide whether
+future versions of the GNU Lesser General Public License shall apply, that
+proxy's public statement of acceptance of any version is permanent authorization
+for you to choose that version for the Library.
diff --git a/Mainpage.dox b/Mainpage.dox
new file mode 100644 (file)
index 0000000..537ae41
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ *  This file is part of Kirigami
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+
+/** \mainpage kirigami
+
+
+\subsection overview Introduction
+Kirigami is a set of <a href="https://doc.qt.io/qt-5/qtquick-index.html">QtQuick</a> components for building adaptable UIs based on <a href="https://doc.qt.io/qt-5/qtquickcontrols-index.html">QtQuick Controls 2</a>.
+
+Its goal is to enable creation of convergent applications that look and feel great on mobile as well as desktop devices and follow the <a href="https://develop.kde.org/hig">KDE Human Interface Guidelines</a> while being easy to use and not adding many dependencies.
+
+Kirigami works on a variety of platforms, such as <a href="https://plasma-mobile.org/">Plasma Mobile</a>, Desktop Linux, Android, MacOS, and Windows.
+
+It was introduced in <a href="https://kde.org/announcements/frameworks/5/5.37.0/">KDE Frameworks 5.37</a> as a <a href="https://api.kde.org/frameworks/#sg-tier_1">Tier-1 KDE Framework</a>.
+
+\subsection tutorial Tutorial
+A tutorial for Kirigami is available on <a href="https://develop.kde.org/docs/getting-started/kirigami/">our developer platform</a>.
+
+It is possible to make short mockups using QtQuick and Kirigami in the <a href="https://qmlonline.kde.org/">QML Online website</a> and briefly test individual QML files using <a href="https://doc.qt.io/qt-5/qtquick-qmlscene.html">qmlscene</a> or the <a href="https://doc.qt.io/qt-5/qtquick-qml-runtime.html">qml tool</a>.
+
+A list of additional QML learning resources is available in the <a href="https://community.kde.org/Get_Involved/development/Learn#QtQuick/QML">Community Wiki</a>. If you are not familiar with QML at all, the <a href="https://www.qt.io/product/qt6/qml-book">QML book</a> should be a good head start.
+
+If you have any questions about Kirigami, feel free to drop by the <a href="https://go.kde.org/matrix/#/#kirigami:kde.org">Kirigami group on Matrix</a>.
+
+\subsection components Main Window Components
+- \link org::kde::kirigami::ApplicationWindow  ApplicationWindow \endlink
+- \link org::kde::kirigami::Action Action  \endlink
+- \link org::kde::kirigami::GlobalDrawer GlobalDrawer  \endlink
+- \link org::kde::kirigami::ContextDrawer ContextDrawer  \endlink
+- \link org::kde::kirigami::OverlayDrawer OverlayDrawer  \endlink
+- \link org::kde::kirigami::Page Page  \endlink
+- \link org::kde::kirigami::ScrollablePage ScrollablePage  \endlink
+- \link org::kde::kirigami::AboutPage AboutPage \endlink
+- \link org::kde::kirigami::PageRow PageRow \endlink
+- \link org::kde::kirigami::FormLayout FormLayout \endlink
+- \link org::kde::kirigami::CardsLayout CardsLayout \endlink
+- \link SizeGroup SizeGroup \endlink
+- \link Kirigami::PlatformTheme Theme  \endlink
+- \link Kirigami::Units  Units \endlink
+
+\subsection controls Common Kirigami Controls
+
+- \link org::kde::kirigami::Card Card \endlink
+- \link org::kde::kirigami::templates::OverlaySheet  OverlaySheet \endlink
+- \link org::kde::kirigami::BasicListItem BasicListItem  \endlink
+- \link org::kde::kirigami::SwipeListItem  SwipeListItem \endlink
+- \link org::kde::kirigami::Heading Heading  \endlink
+- \link org::kde::kirigami::PlaceholderMessage PlaceholderMessage  \endlink
+- \link org::kde::kirigami::SearchField SearchField \endlink
+- \link org::kde::kirigami::Dialog Dialog \endlink
+- \link org::kde::kirigami::NavigationTabBar NavigationTabBar \endlink
+- \link Icon Icon  \endlink
+
+\subsection example Minimal Example
+
+@code
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as Controls
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    width: 500
+    height: 400
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "action 3"
+            },
+            Kirigami.Action {
+                text: "action 4"
+            }
+        ]
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+    pageStack.initialPage: mainPageComponent
+    Component {
+        id: mainPageComponent
+        Kirigami.ScrollablePage {
+            id: page
+            title: "Hello"
+            actions {
+                main: Kirigami.Action {
+                    icon.name: sheet.sheetOpen ? "dialog-cancel" : "document-edit"
+                    onTriggered: {
+                        print("Action button in buttons page clicked");
+                        sheet.sheetOpen = !sheet.sheetOpen
+                    }
+                }
+                left: Kirigami.Action {
+                    icon.name: "go-previous"
+                    onTriggered: {
+                        print("Left action triggered")
+                    }
+                }
+                right: Kirigami.Action {
+                    icon.name: "go-next"
+                    onTriggered: {
+                        print("Right action triggered")
+                    }
+                }
+                contextualActions: [
+                    Kirigami.Action {
+                        text:"Action for buttons"
+                        icon.name: "bookmarks"
+                        onTriggered: print("Action 1 clicked")
+                    },
+                    Kirigami.Action {
+                        text:"Action 2"
+                        icon.name: "folder"
+                        enabled: false
+                    },
+                    Kirigami.Action {
+                        text: "Action for Sheet"
+                        visible: sheet.sheetOpen
+                    }
+                ]
+            }
+            Kirigami.OverlaySheet {
+                id: sheet
+                onSheetOpenChanged: page.actions.main.checked = sheetOpen
+                Controls.Label {
+                    wrapMode: Text.WordWrap
+                    text: "Lorem ipsum dolor sit amet"
+                }
+            }
+            //Page contents...
+            Rectangle {
+                anchors.fill: parent
+                color: "lightblue"
+            }
+        }
+    }
+}
+@endcode
+
+@image html MinimalExample.webp
+
+\subsection deployment Deployment
+CMake is used for both building Kirigami and projects using it, allowing for several configurations depending on how the deployment needs to be done.
+
+Kirigami can be built in two ways: both as a module or statically linked to the application, leading to four combinations:
+
+- Kirigami built as a module with CMake
+- Kirigami statically built with CMake (needed to link statically from applications built with CMake)
+
+The simplest and recommended way to use Kirigami is to just use the packages provided by your Linux distribution, or build it as a module and deploy it together with the main application.
+
+For example, when building an application on Android with CMake, if Kirigami for Android is built and installed in the same temporary directory before the application, the create-apk step of the application will include the Kirigami files as well in the APK.
+
+Statically linked Kirigami will be used only on Android, while on desktop systems it will use the version provided by the distribution. Which platforms use the static version and which use the dynamic one can be freely adjusted.
+
+The application needs to have a folder called "3rdparty" containing clones of two KDE repositories: kirigami and breeze-icons (available at git://anongit.kde.org/kirigami.git and git://anongit.kde.org/breeze-icons.git).
+
+The main.cpp file should then have something like:
+
+@code
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#ifdef Q_OS_ANDROID
+#include "./3rdparty/kirigami/src/kirigamiplugin.h"
+#endif
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    QGuiApplication app(argc, argv);
+
+    QQmlApplicationEngine engine;
+
+#ifdef Q_OS_ANDROID
+    KirigamiPlugin::getInstance().registerTypes(&engine);
+#endif
+    ...
+}
+@endcode
+
+@authors
+Marco Martin \<notmart@gmail.com\><br>
+Sebastian Kuegler \<sebas@kde.org\><br>
+Aleix Pol Gonzalez \<aleixpol@kde.org\><br>
+Dirk Hohndel \<dirk@hohndel.org\><br>
+
+@maintainers
+Marco Martin \<notmart@gmail.com\>
+
+@licenses
+@lgpl
+
+*/
+
+
+// DOXYGEN_SET_RECURSIVE = YES
+// DOXYGEN_SET_EXCLUDE_PATTERNS += *_p.h */private/* */examples/* */doc/* */styles/*
+// DOXYGEN_SET_PROJECT_NAME = Kirigami
+// vim:ts=4:sw=4:expandtab:filetype=doxygen
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..339258b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+# Kirigami
+
+QtQuick plugins to build user interfaces based on the KDE UX guidelines
+
+## Introduction
+
+Kirigami is a set of QtQuick components at the moment targeted for mobile use (in the future desktop as well) targeting both Plasma Mobile and Android. It’s not a whole set of components, all the “Primitive” ones like buttons and textboxes are a job for QtQuickControls (soon QtQuickControls2) but it’s a set of high level components to make the creation of applications that look and feel great on mobile as well as desktop devices and follow the Kirigami Human Interface Guidelines.
+
+## Build examples to desktop
+
+Build all examples available
+
+```sh
+mkdir build
+cd build
+cmake .. -DBUILD_EXAMPLES=ON
+make
+```
+
+Then, you can run:
+
+```sh
+./examples/applicationitemapp/applicationitemapp
+# or
+./examples/galleryapp/kirigami2gallery
+```
+
+## Build the gallery example app on Android
+
+Make sure to install **android-sdk**, **android-ndk** and **android-qt5-arch**, where **arch** should be the same architecture that you aim to deploy.
+
+```sh
+mkdir build
+cd build
+cmake .. \
+    -DQTANDROID_EXPORTED_TARGET=kirigami2gallery \
+    -DBUILD_EXAMPLES=on \
+    -DANDROID_APK_DIR=../examples/galleryapp \
+    -DECM_DIR=/path/to/share/ECM/cmake \
+    -DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
+    -DECM_ADDITIONAL_FIND_ROOT_PATH=/path/to/Qt5.7.0/5.7/{arch} \
+    -DCMAKE_PREFIX_PATH=/path/to/Qt5.7.0/5.7/{arch}/path/to/Qt5Core \
+    -DANDROID_NDK=/path/to/Android/Sdk/ndk-bundle \
+    -DANDROID_SDK_ROOT=/path/to/Android/Sdk/ \
+    -DANDROID_SDK_BUILD_TOOLS_REVISION=26.0.2 \
+    -DCMAKE_INSTALL_PREFIX=/path/to/dummy/install/prefix
+```
+
+You need a `-DCMAKE_INSTALL_PREFIX` to somewhere in your home, but using an absolute path.
+
+If you have a local checkout of the breeze-icons repo, you can avoid the cloning of the build dir
+by passing also `-DBREEZEICONS_DIR=/path/to/existing/sources/of/breeze-icons`
+
+```sh
+make create-apk-kirigami2gallery
+```
+
+Apk will be generated at `./kirigami2gallery_build_apk/build/outputs/apk/kirigami2gallery_build_apk-debug.apk`.
+
+To directly install on a phone:
+
+```sh
+adb install -r ./kirigami2gallery_build_apk/build/outputs/apk/kirigami2gallery_build_apk-debug.apk
+```
+
+To perform this, your device need to be configureted with `USB debugging` and `install via USB` in `Developer options`.
+
+> Some ambient variables must be set before the process: `ANDROID_NDK`, `ANDROID_SDK_ROOT`, `Qt5_android` and `JAVA_HOME`
+
+```sh
+export ANDROID_NDK=/path/to/android-ndk
+export ANDROID_SDK_ROOT=/path/to/android-sdk
+export Qt5_android=/path/to/android-qt5/5.7.0/{arch}
+export PATH=$ANDROID_SDK_ROOT/platform-tools/:$PATH
+# adapt the following path to your ant installation
+export ANT=/usr/bin/ant
+export JAVA_HOME=/path/to/lib/jvm/java-8-openjdk/
+```
+
+# Build on your application Android, ship it together Kirigami
+
+1) Build kirigami
+
+   Use the same procedure mentioned above (but without `BUILD_EXAMPLES` switch):
+    - `cd` into kirigami sources directory;
+    - Execute build script:
+        ```sh
+        mkdir build
+        cd build
+        
+        cmake ..  \
+            -DCMAKE_TOOLCHAIN_FILE=/path/to/share/ECM/toolchain/Android.cmake\
+            -DCMAKE_PREFIX_PATH=/path/to/Qt5.7.0/5.7/android_armv7/\
+            -DCMAKE_INSTALL_PREFIX=/path/to/dummy/install/prefix\
+            -DECM_DIR=/path/to/share/ECM/cmake
+        
+        make
+        make install
+        ```
+    - Note: omit the `make create-apk-kirigami2gallery` step.
+
+2) Build your application
+
+   This guide assumes that you build your application with CMake and use [Extra CMake Modules (ECM)](https://api.kde.org/ecm/) from KDE frameworks.
+    - `cd` into your application sources directory;
+    - Replace `$yourapp` with the actual name of your application;
+    - Execute build script:
+        ```sh
+        mkdir build
+        cd build
+        
+        cmake .. \
+            -DCMAKE_TOOLCHAIN_FILE=/path/to/share/ECM/toolchain/Android.cmake \
+            -DQTANDROID_EXPORTED_TARGET=$yourapp \
+            -DANDROID_APK_DIR=../examples/galleryapp/ \
+            -DCMAKE_PREFIX_PATH=/path/to/Qt5.7.0/5.7/android_armv7/ \
+            -DCMAKE_INSTALL_PREFIX=/path/to/dummy/install/prefix
+            
+        make
+        make install
+        make create-apk-$yourapp
+        ```
+    - Note: `-DCMAKE_INSTALL_PREFIX` directory will be the same as where Kirigami was installed,
+    since you need to create an apk package that contains both the kirigami build and the
+    build of your application.
+
+# Build an application with qmake
+
+* Use `examples/minimalqmake` example as a template.
+* It links statically for Android, but on desktop systems it links to the shared library provided by your distribution. However, static linking mode may be useful for other systems such as iOS or Windows.
+* Static linking only: clone `kirigami` and `breeze-icons` git repositories under the 3rdparty folder.
+* Android only: in your `main()` call `KirigamiPlugin::getInstance().registerTypes();` to register QML types.
+* QtCreator should be able to deploy on Android out of the box via auto-detected Android Kit, provided that SDK, NDK and other relevant tools are installed.
diff --git a/autotests/BasicListItem_ToolTip_Test.qml b/autotests/BasicListItem_ToolTip_Test.qml
new file mode 100644 (file)
index 0000000..e100a1a
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+// Implemented as a separate file, so it can be viewed and tweaked outside of testing framework.
+Rectangle {
+    id: root
+
+    property alias itemBothNotElided:  itemBothNotElided
+    property alias itemLabelElided:    itemLabelElided
+    property alias itemSubtitleElided: itemSubtitleElided
+    property alias itemBothElided:     itemBothElided
+    property alias itemHtmlElided:     itemHtmlElided
+
+    implicitWidth: referenceItem.implicitWidth
+    implicitHeight: column.implicitHeight
+
+    width: implicitWidth
+    height: implicitHeight
+
+    color: Kirigami.Theme.backgroundColor
+
+    Kirigami.BasicListItem {
+        id: referenceItem
+        visible: false
+        label: "Lorem ipsum dolor sit amet"
+        subtitle: "tempor incididunt ut labore"
+    }
+
+    ColumnLayout {
+        id: column
+        width: parent.width
+        spacing: 0
+
+        Kirigami.BasicListItem {
+            id: itemBothNotElided
+            Layout.fillWidth: true
+            label: "Lorem ipsum dolor"
+            subtitle: "tempor incididunt"
+        }
+        Kirigami.BasicListItem {
+            id: itemLabelElided
+            Layout.fillWidth: true
+            label: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod"
+            subtitle: "tempor incididunt"
+            toolTipVisible: true
+        }
+        Kirigami.BasicListItem {
+            id: itemSubtitleElided
+            Layout.fillWidth: true
+            label: "Lorem ipsum dolor"
+            subtitle: "tempor incididunt ut labore et dolore magna aliqua."
+            toolTipVisible: true
+        }
+        Kirigami.BasicListItem {
+            id: itemBothElided
+            Layout.fillWidth: true
+            label: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod"
+            subtitle: "tempor incididunt ut labore et dolore magna aliqua."
+            toolTipVisible: true
+        }
+        Kirigami.BasicListItem {
+            id: itemHtmlElided
+            Layout.fillWidth: true
+            label: "HTML is <i>supported</i> inside <b>tooltips</b> as well…"
+            subtitle: "…as line breaks between <sup>label</sup> &amp; <sub>subtitle</sub>"
+            toolTipVisible: true
+        }
+        QQC2.Label {
+            Layout.fillWidth: true
+            padding: Kirigami.Units.smallSpacing
+            text: `QQC2.ToolTip.toolTip.visible:\n${QQC2.ToolTip.toolTip.visible}`
+            color: Kirigami.Theme.textColor
+            toolTipVisible: true
+        }
+        QQC2.Label {
+            Layout.fillWidth: true
+            Layout.preferredHeight: Kirigami.Units.gridUnit * 10
+            padding: Kirigami.Units.smallSpacing
+            text: `QQC2.ToolTip.toolTip.text:\n"${QQC2.ToolTip.toolTip.text}"`
+            wrapMode: Text.Wrap
+            color: Kirigami.Theme.textColor
+            toolTipVisible: true
+        }
+    }
+}
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3c225cc
--- /dev/null
@@ -0,0 +1,56 @@
+if(NOT TARGET Qt${QT_MAJOR_VERSION}::QuickTest)
+    message(STATUS "Qt${QT_MAJOR_VERSION}QuickTest not found, autotests will not be built.")
+    return()
+endif()
+
+add_executable(qmltest qmltest.cpp)
+target_link_libraries(qmltest PRIVATE Qt::Qml Qt${QT_MAJOR_VERSION}::QuickTest)
+
+if (NOT BUILD_SHARED_LIBS)
+    target_compile_definitions(qmltest PRIVATE STATIC_MODULE)
+    target_link_libraries(qmltest PRIVATE KirigamiPlugin)
+endif()
+
+macro(kirigami_add_tests)
+    if (WIN32)
+        set(_extra_args -platform offscreen)
+    endif()
+
+    foreach(test ${ARGV})
+        add_test(NAME ${test}
+                 COMMAND qmltest
+                        ${_extra_args}
+                        -import ${CMAKE_BINARY_DIR}/bin
+                        -input ${test}
+                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+        )
+    endforeach()
+endmacro()
+
+kirigami_add_tests(
+    tst_actiontoolbar.qml
+    tst_avatar.qml
+    tst_basiclistitem_tooltip.qml
+    tst_formlayout.qml
+    tst_globaldrawer.qml
+    tst_icon.qml
+    tst_keynavigation.qml
+    tst_listskeynavigation.qml
+    tst_mnemonicdata.qml
+    tst_pagerouter.qml
+    tst_pagerow.qml
+    tst_routerwindow.qml
+    tst_theme.qml
+
+    pagepool/tst_layers.qml
+    pagepool/tst_pagepool.qml
+
+    wheelhandler/tst_filterMouseEvents.qml
+    wheelhandler/tst_invokables.qml
+    wheelhandler/tst_onWheel.qml
+    wheelhandler/tst_scrolling.qml
+)
+
+set_tests_properties(tst_theme.qml PROPERTIES
+    ENVIRONMENT "QT_QUICK_CONTROLS_STYLE=default;KIRIGAMI_FORCE_STYLE=1"
+)
diff --git a/autotests/pagepool/TestPage.qml b/autotests/pagepool/TestPage.qml
new file mode 100644 (file)
index 0000000..d455405
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Mason McParlane <mtmcp@outlook.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import org.kde.kirigami 2.11 as Kirigami
+
+Kirigami.Page {
+    title: qsTr("INITIAL TITLE")
+}
diff --git a/autotests/pagepool/tst_layers.qml b/autotests/pagepool/tst_layers.qml
new file mode 100644 (file)
index 0000000..598f94c
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Mason McParlane <mtmcp@outlook.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.7
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.11 as Kirigami
+import QtTest 1.0
+
+TestCase {
+    id: testCase
+    width: 400
+    height: 400
+    name: "PagePoolWithLayers"
+    when: windowShown
+
+    function initTestCase() {
+        mainWindow.show()
+    }
+
+    function cleanupTestCase() {
+        mainWindow.close()
+    }
+
+    Kirigami.ApplicationWindow {
+        id: mainWindow
+        width: 480
+        height: 360
+    }
+
+    Kirigami.PagePool {
+        id: pool
+    }
+
+    SignalSpy {
+        id: stackSpy
+        target: mainWindow.pageStack
+        signalName: "onCurrentItemChanged"
+    }
+
+    SignalSpy {
+        id: layerSpy
+        target: mainWindow.pageStack.layers
+        signalName: "onCurrentItemChanged"
+    }
+
+
+    function init() {
+        pool.clear()
+        mainWindow.pageStack.layers.clear()
+        compare(mainWindow.pageStack.layers.depth, 1)
+        mainWindow.pageStack.clear()
+
+        for (var spy of [stackSpy, layerSpy, checkSpy_A, checkSpy_B, checkSpy_C, checkSpy_D, checkSpy_E]) {
+            spy.clear()
+        }
+
+        // Give mainWindow a bit of room to breathe so it can process item
+        // deletion and other delayed signals.
+        wait(50)
+    }
+
+    ActionGroup {
+        id: group
+        exclusive: false
+
+        Kirigami.PagePoolAction {
+            id: stackPageA
+            objectName: "stackPageA"
+            pagePool: pool
+            pageStack: mainWindow.pageStack
+            page: "TestPage.qml?page=A"
+            initialProperties: { return {title: "A", objectName: "Page A" } }
+        }
+
+        Kirigami.PagePoolAction {
+            id: stackPageB
+            objectName: "stackPageB"
+            pagePool: pool
+            pageStack: mainWindow.pageStack
+            page: "TestPage.qml?page=B"
+            initialProperties: { return {title: "B", objectName: "Page B" } }
+        }
+
+        Kirigami.PagePoolAction {
+            id: layerPageC
+            objectName: "layerPageC"
+            pagePool: pool
+            pageStack: mainWindow.pageStack
+            useLayers: true
+            page: "TestPage.qml?page=C"
+            initialProperties: { return {title: "C", objectName: "Page C" } }
+        }
+
+        Kirigami.PagePoolAction {
+            id: layerPageD
+            objectName: "layerPageD"
+            pagePool: pool
+            pageStack: mainWindow.pageStack
+            useLayers: true
+            page: "TestPage.qml?page=D"
+            initialProperties: { return {title: "D", objectName: "Page D" } }
+        }
+
+        Kirigami.PagePoolAction {
+            id: stackPageE
+            objectName: "stackPageE"
+            pagePool: pool
+            pageStack: mainWindow.pageStack
+            page: "TestPage.qml?page=E"
+            initialProperties: { return {title: "E", objectName: "Page E" } }
+        }
+    }
+
+    function tapBack () {
+        mouseClick(mainWindow, 10, 10)
+    }
+
+    function test_pushLayerBackButtonPushAgain() {
+        var stack = mainWindow.pageStack
+        var layers = stack.layers
+
+        function pushA() {
+            stackPageA.trigger()
+            compare(stack.currentItem, pool.lastLoadedItem)
+        }
+
+        function pushC () {
+            layerPageC.trigger()
+            compare(layers.currentItem, pool.lastLoadedItem)
+        }
+
+        function pushD () {
+            layerPageD.trigger()
+            compare(layers.currentItem, pool.lastLoadedItem)
+        }
+
+        compare(stackSpy.count, 0)
+        pushA()
+        compare(stackSpy.count, 1)
+        compare(layerSpy.count, 0)
+        pushC()
+        compare(layerSpy.count, 1)
+        pushD()
+        compare(layerSpy.count, 2)
+        compare(stackSpy.count, 1)
+        tapBack()
+        compare(layerSpy.count, 3)
+        pushD()
+        compare(layerSpy.count, 4)
+    }
+
+    SignalSpy {
+        id: checkSpy_A
+        target: stackPageA
+        signalName: "onCheckedChanged"
+    }
+
+    SignalSpy {
+        id: checkSpy_B
+        target: stackPageB
+        signalName: "onCheckedChanged"
+    }
+
+    SignalSpy {
+        id: checkSpy_C
+        target: layerPageC
+        signalName: "onCheckedChanged"
+    }
+
+    SignalSpy {
+        id: checkSpy_D
+        target: layerPageD
+        signalName: "onCheckedChanged"
+    }
+
+    SignalSpy {
+        id: checkSpy_E
+        target: stackPageE
+        signalName: "onCheckedChanged"
+    }
+    
+    function dump_layers(msg = "") {
+        for (var i = 0; i < mainWindow.pageStack.layers.depth; ++i) {
+            console.debug(`${msg} ${i}: ${mainWindow.pageStack.layers.get(i)}`)
+        }
+    }
+
+    function test_checked() {
+        var stack = mainWindow.pageStack
+        var layers = stack.layers
+
+        function testCheck(expected = {}) {
+            let defaults = {
+                a: false, b: false, c: false, d: false, e: false
+            }
+            let actual = Object.assign({}, defaults, expected)
+            let pages = {a: stackPageA, b: stackPageB, c: layerPageC, d: layerPageD, e: stackPageE}
+            
+            for (const prop in actual) {
+                compare(pages[prop].checked, actual[prop],
+                    `${pages[prop]} should ${actual[prop] ? 'be checked' : 'not be checked'}`)
+            }
+        }
+
+        testCheck()
+        
+        compare(stackSpy.count, 0)
+        compare(layerSpy.count, 0)
+        compare(checkSpy_A.count, 0)
+        compare(checkSpy_B.count, 0)
+        compare(checkSpy_C.count, 0)
+        compare(checkSpy_D.count, 0)
+        compare(checkSpy_E.count, 0)
+
+        stackPageA.trigger()
+        compare(checkSpy_A.count, 1)
+        testCheck({a:true})
+        compare(stack.currentItem, stackPageA.pageItem())
+
+        stackPageB.trigger()
+        compare(checkSpy_A.count, 2)
+        compare(checkSpy_B.count, 3)
+        testCheck({b:true})
+        compare(stack.currentItem, stackPageB.pageItem())
+
+        layerPageC.trigger()
+        testCheck({b:true, c:true})
+        compare(checkSpy_C.count, 1)
+        compare(stack.currentItem, stackPageB.pageItem())
+        compare(layers.currentItem, layerPageC.pageItem())
+        compare(layerPageC.layerContainsPage(), true)
+
+        layerPageD.trigger()
+        compare(stack.currentItem, stackPageB.pageItem())
+        compare(layers.currentItem, layerPageD.pageItem())
+        testCheck({b:true, c:true, d:true})
+
+        stackPageE.basePage = stack.currentItem
+        stackPageE.trigger()
+        testCheck({b:true, e:true})
+        compare(stack.currentItem, stackPageE.pageItem())
+        verify(!(layers.currentItem instanceof Page),
+                    `Current item ${layers.currentItem} is a page but all pages should be popped`)
+
+        stackPageA.trigger()
+        testCheck({a:true})
+        compare(stack.currentItem, stackPageA.pageItem())
+        verify(!(layers.currentItem instanceof Page),
+                    `Current item ${layers.currentItem} is a page but all pages should be popped`)
+
+        compare(checkSpy_A.count, 5)
+        compare(checkSpy_B.count, 4)
+        compare(checkSpy_C.count, 2)
+        compare(checkSpy_D.count, 2)
+        compare(checkSpy_E.count, 2)
+    }
+
+    function test_push_A_C_D_A_popsLayers() {
+        var stack = mainWindow.pageStack
+        var layers = stack.layers
+
+        stackPageA.trigger()
+        compare(stack.currentItem, stackPageA.pageItem())
+
+        layerPageC.trigger()
+        compare(layers.currentItem, layerPageC.pageItem())
+
+        layerPageD.trigger()
+        compare(layers.currentItem, layerPageD.pageItem())
+
+        stackPageA.trigger()
+        compare(stack.currentItem, stackPageA.pageItem())
+        verify(!(layers.currentItem instanceof Page),
+                    `Current item ${layers.currentItem} is a page but all pages should be popped`)
+    }
+
+    function test_push_A_C_D_back_back_C_back_C() {
+        var stack = mainWindow.pageStack
+        var layers = stack.layers
+
+        stackPageA.trigger()
+        layerPageC.trigger()
+        layerPageD.trigger()
+        tapBack()
+        tapBack()
+        layerPageC.trigger()
+        tapBack()
+        layerPageC.trigger()
+        compare(layers.currentItem, layerPageC.pageItem())
+    }
+
+    function test_exclusive_group() {
+        var stack = mainWindow.pageStack
+        var layers = stack.layers
+
+        group.exclusive = true
+        stackPageA.trigger()
+        compare(stackPageA.checked, true)
+        compare(layerPageC.checked, false)
+        layerPageC.trigger()
+        compare(stackPageA.checked, false)
+        compare(layerPageC.checked, true)
+        tapBack()
+        compare(stackPageA.checked, true)
+        compare(layerPageC.checked, false)
+    }
+}
diff --git a/autotests/pagepool/tst_pagepool.qml b/autotests/pagepool/tst_pagepool.qml
new file mode 100644 (file)
index 0000000..c0378f9
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Mason McParlane <mtmcp@outlook.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.11 as Kirigami
+import QtTest 1.0
+
+TestCase {
+    id: testCase
+    width: 400
+    height: 400
+    name: "PagePool"
+
+    function initTestCase() {
+        mainWindow.show()
+    }
+
+    function cleanupTestCase() {
+        mainWindow.close()
+    }
+
+    function applicationWindow() { return mainWindow; }
+
+    Kirigami.ApplicationWindow {
+        id: mainWindow
+        width: 480
+        height: 360
+    }
+
+    Kirigami.PagePool {
+        id: pool
+    }
+
+    function init() {
+        mainWindow.pageStack.clear()
+        pool.clear()
+    }
+
+    // Queries added to page URLs ensure the PagePool can
+    // have multiple instances of TestPage.qml
+
+    Kirigami.PagePoolAction {
+        id: loadPageAction
+        pagePool: pool
+        pageStack: mainWindow.pageStack
+        page: "TestPage.qml?action=loadPageAction"
+    }
+
+    function test_loadPage () {
+        var expectedUrl = "TestPage.qml?action=loadPageAction"
+        compare(mainWindow.pageStack.depth, 0)
+        loadPageAction.trigger()
+        compare(mainWindow.pageStack.depth, 1)
+        verify(pool.lastLoadedUrl.toString().endsWith(expectedUrl))
+        compare(mainWindow.pageStack.currentItem.title, "INITIAL TITLE")
+    }
+
+    Kirigami.PagePoolAction {
+        id: loadPageActionWithProps
+        pagePool: pool
+        pageStack: mainWindow.pageStack
+        page: "TestPage.qml?action=loadPageActionWithProps"
+        initialProperties: {
+            return {title: "NEW TITLE" }
+        }
+    }
+
+    function test_loadPageInitialPropertyOverride () {
+        var expectedUrl = "TestPage.qml?action=loadPageActionWithProps"
+        compare(mainWindow.pageStack.depth, 0)
+        loadPageActionWithProps.trigger()
+        compare(mainWindow.pageStack.depth, 1)
+        verify(pool.lastLoadedUrl.toString().endsWith(expectedUrl))
+        compare(mainWindow.pageStack.currentItem.title, "NEW TITLE")
+        compare(pool.lastLoadedItem.title, "NEW TITLE")
+    }
+
+    Kirigami.PagePoolAction {
+        id: loadPageActionPropsNotObject
+        pagePool: pool
+        pageStack: mainWindow.pageStack
+        page: "TestPage.qml?action=loadPageActionPropsNotObject"
+        initialProperties: "This is a string not an object..."
+    }
+
+    function test_loadPageInitialPropertiesWrongType () {
+        var expectedUrl = "TestPage.qml?action=loadPageAction"
+        compare(mainWindow.pageStack.depth, 0)
+        loadPageAction.trigger()
+        loadPageActionPropsNotObject.trigger()
+        compare(mainWindow.pageStack.depth, 1)
+        verify(pool.lastLoadedUrl.toString().endsWith(expectedUrl))
+    }
+
+    Kirigami.PagePoolAction {
+        id: loadPageActionPropDoesNotExist
+        pagePool: pool
+        pageStack: mainWindow.pageStack
+        page: "TestPage.qml?action=loadPageActionPropDoesNotExist"
+        initialProperties: {
+            return { propDoesNotExist: "PROP-NON-EXISTENT" }
+        }
+    }
+
+    function test_loadPageInitialPropertyNotExistFails () {
+        var expectedUrl = "TestPage.qml?action=loadPageActionPropDoesNotExist"
+        loadPageActionPropDoesNotExist.trigger()
+        verify(!pool.lastLoadedUrl.toString().endsWith(expectedUrl))
+    }
+    
+    function test_contains () {
+        const page = "TestPage.qml?action=contains"
+        let item = pool.loadPage(page)
+        verify(item !== null, "valid item returned from loadPage")
+        verify(pool.contains(page), "pool contains page")
+        verify(pool.contains(item), "pool contains item")
+    }
+    
+    function test_deletePageByUrl () {
+        const urlPage = "TestPage.qml?action=deletePageByUrl"
+        pool.loadPage(urlPage)
+        verify(pool.contains(urlPage), "pool contains page before deletion")
+        pool.deletePage(urlPage)
+        verify(!pool.contains(urlPage), "pool does not contain page after deletion")
+    }
+    
+    function test_deletePageByItem () {
+        const itemPage = "TestPage.qml?action=deletePageByItem"
+        let item = pool.loadPage(itemPage)
+        verify(pool.contains(item), "pool contains item before deletion")
+        pool.deletePage(item)
+        verify(!pool.contains(itemPage), "pool does not contain page after deletion")
+    }
+    
+    function test_iterateAndDeleteByItem () {
+        const pages = []
+        for (let i = 1; i <= 5; ++i) {
+            const page = "TestPage.qml?page=" + i
+            pool.loadPage(page)
+            verify(pool.contains(page), "pool contains page " + page)
+            pages.push(page)
+        }
+        verify(pool.items.length == 5, "pool contains 5 items")
+        for (const item of pool.items) {
+            const url = pool.urlForPage(item)
+            const found = pages.find(page => url.toString().endsWith(page))
+            verify(found, "pool.items contains page " + found)
+            pool.deletePage(item)
+        }
+        verify(pool.items.length == 0, "all items have been deleted")
+    }
+    
+    function test_iterateAndDeleteByUrl () {
+        const pages = []
+        for (let i = 1; i <= 5; ++i) {
+            const page = "TestPage.qml?page=" + i
+            pool.loadPage(page)
+            verify(pool.contains(page), "pool contains page " + page)
+            pages.push(page)
+        }
+        verify(pool.urls.length == 5, "pool contains 5 urls")
+        for (const url of pool.urls) {
+            const found = pages.find(page => url.toString().endsWith(page))
+            verify(found, "pool.urls contains page " + found)
+        }
+        for (const page of pages) {
+            pool.deletePage(page)
+        }
+        verify(pool.urls.length == 0, "all urls have been deleted")
+    }
+}
diff --git a/autotests/qmltest.cpp b/autotests/qmltest.cpp
new file mode 100644 (file)
index 0000000..85cf196
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include <QQmlEngine>
+#include <QtQuickTest>
+
+#ifdef STATIC_MODULE
+#include "kirigamiplugin.h"
+Q_IMPORT_PLUGIN(KirigamiPlugin)
+#endif
+
+class KirigamiSetup : public QObject
+{
+    Q_OBJECT
+
+public:
+    KirigamiSetup()
+    {
+    }
+
+public Q_SLOTS:
+    void qmlEngineAvailable(QQmlEngine *engine)
+    {
+#ifdef STATIC_MODULE
+        KirigamiPlugin::getInstance().registerTypes(engine);
+#else
+        Q_UNUSED(engine)
+#endif
+    }
+};
+
+QUICK_TEST_MAIN_WITH_SETUP(Kirigami, KirigamiSetup)
+
+#include "qmltest.moc"
diff --git a/autotests/tst_actiontoolbar.qml b/autotests/tst_actiontoolbar.qml
new file mode 100644 (file)
index 0000000..8cb8e74
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtTest 1.0
+import org.kde.kirigami 2.14 as Kirigami
+
+// TODO: Find a nicer way to handle this
+import "../src/controls/private" as KirigamiPrivate
+
+TestCase {
+    id: testCase
+    name: "ActionToolBarTest"
+
+    width: 800
+    height: 400
+    visible: true
+
+    when: windowShown
+
+    // These buttons are required for getting the right metrics.
+    // Since ActionToolBar bases all sizing on button sizes, we need to be able
+    // to verify that layouting does the right thing.
+    property ToolButton iconButton: KirigamiPrivate.PrivateActionToolButton {
+        display: Button.IconOnly
+        action: Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+        font.pointSize: 10
+    }
+    property ToolButton textButton: KirigamiPrivate.PrivateActionToolButton {
+        display: Button.TextOnly
+        action: Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+        font.pointSize: 10
+    }
+    property ToolButton textIconButton: KirigamiPrivate.PrivateActionToolButton {
+        action: Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+        font.pointSize: 10
+    }
+    property TextField textField: TextField { font.pointSize: 10 }
+
+    Component {
+        id: single;
+        Kirigami.ActionToolBar {
+            font.pointSize: 10
+            actions: [
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+            ]
+        }
+    }
+
+    Component {
+        id: multiple
+        Kirigami.ActionToolBar {
+            font.pointSize: 10
+            actions: [
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+            ]
+        }
+    }
+
+    Component {
+        id: iconOnly
+        Kirigami.ActionToolBar {
+            display: Button.IconOnly
+            font.pointSize: 10
+            actions: [
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" }
+            ]
+        }
+    }
+
+    Component {
+        id: qtActions
+        Kirigami.ActionToolBar {
+            font.pointSize: 10
+            actions: [
+                Action { icon.name: "document-new"; text: "Test Action" },
+                Action { icon.name: "document-new"; text: "Test Action" },
+                Action { icon.name: "document-new"; text: "Test Action" }
+            ]
+        }
+    }
+
+    Component {
+        id: mixed
+        Kirigami.ActionToolBar {
+            font.pointSize: 10
+            actions: [
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action"; displayHint: Kirigami.DisplayHint.IconOnly },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action" },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action"; displayComponent: TextField { } },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action"; displayHint: Kirigami.DisplayHint.AlwaysHide },
+                Kirigami.Action { icon.name: "document-new"; text: "Test Action"; displayHint: Kirigami.DisplayHint.KeepVisible }
+            ]
+        }
+    }
+
+    function test_layout_data() {
+        return [
+            // One action
+            // Full window width, should just display a toolbutton
+            { tag: "single_full", component: single, width: testCase.width, expected: testCase.textIconButton.width },
+            // Small width, should display the overflow button
+            { tag: "single_min", component: single, width: 50, expected: testCase.iconButton.width },
+            // Half window width, should display a single toolbutton
+            { tag: "single_half", component: single, width: testCase.width / 2, expected: testCase.textIconButton.width },
+            // Multiple actions
+            // Full window width, should display as many buttons as there are actions
+            { tag: "multi_full", component: multiple, width: testCase.width,
+                expected: testCase.textIconButton.width * 3 + Kirigami.Units.smallSpacing * 2 },
+            // Small width, should display just the overflow button
+            { tag: "multi_min", component: multiple, width: 50, expected: testCase.iconButton.width },
+            // Half window width, should display one action and overflow button
+            { tag: "multi_small", component: multiple,
+                width: testCase.textIconButton.width * 2 + testCase.iconButton.width + Kirigami.Units.smallSpacing * 3,
+                expected: testCase.textIconButton.width * 2 + testCase.iconButton.width + Kirigami.Units.smallSpacing * 2 },
+            // Multiple actions, display set to icon only
+            // Full window width, should display as many icon-only buttons as there are actions
+            { tag: "icon_full", component: iconOnly, width: testCase.width,
+                expected: testCase.iconButton.width * 3 + Kirigami.Units.smallSpacing * 2 },
+            // Small width, should display just the overflow button
+            { tag: "icon_min", component: iconOnly, width: 50, expected: testCase.iconButton.width },
+            // Quarter window width, should display one icon-only button and the overflow button
+            { tag: "icon_small", component: iconOnly, width: testCase.iconButton.width * 4,
+                expected: testCase.iconButton.width * 3 + Kirigami.Units.smallSpacing * 2 },
+            // QtQuick Controls actions
+            // Full window width, should display as many buttons as there are actions
+            { tag: "qt_full", component: qtActions, width: testCase.width,
+                expected: testCase.textIconButton.width * 3 + Kirigami.Units.smallSpacing * 2 },
+            // Small width, should display just the overflow button
+            { tag: "qt_min", component: qtActions, width: 50, expected: testCase.iconButton.width },
+            // Half window width, should display one action and overflow button
+            { tag: "qt_small", component: qtActions,
+                width: testCase.textIconButton.width * 2 + testCase.iconButton.width + Kirigami.Units.smallSpacing * 3,
+                expected: testCase.textIconButton.width * 2 + testCase.iconButton.width + Kirigami.Units.smallSpacing * 2 },
+            // Mix of different display hints, displayComponent and normal actions.
+            // Full window width, should display everything, but one action is collapsed to icon
+            { tag: "mixed", component: mixed, width: testCase.width,
+                expected: testCase.textIconButton.width * 2 + testCase.iconButton.width * 2 + testCase.textField.width + Kirigami.Units.smallSpacing * 4 }
+        ]
+    }
+
+    // Test layouting of ActionToolBar
+    //
+    // ActionToolBar has some pretty complex behaviour, which generally boils down to it trying
+    // to fit as many visible actions as possible and placing the hidden ones in an overflow menu.
+    // This test, along with the data above, verifies that that this behaviour is correct.
+    function test_layout(data) {
+        var toolbar = createTemporaryObject(data.component, testCase, {width: data.width})
+
+        verify(toolbar)
+        verify(waitForRendering(toolbar))
+
+        while (toolbar.visibleWidth == 0) {
+            // The toolbar creates its delegates asynchronously during "idle
+            // time", this means we need to wait for a bit so the toolbar has
+            // the time to do that. As long as it has not finished creation, the
+            // toolbar will have a visibleWidth of 0, so we can use that to
+            // determine when it is done.
+            wait(50)
+        }
+
+        compare(toolbar.visibleWidth, data.expected)
+    }
+}
diff --git a/autotests/tst_avatar.qml b/autotests/tst_avatar.qml
new file mode 100644 (file)
index 0000000..8a1c9c1
--- /dev/null
@@ -0,0 +1,157 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+import QtTest 1.0
+
+Item {
+    id: root
+
+    width: 110
+    height: 110 * 3
+
+    TestCase {
+        name: "AvatarTests"
+        function test_latin_name() {
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("Nate Martin"), false)
+            compare(Kirigami.NameUtils.initialsFromString("Nate Martin"), "NM")
+
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("Kalanoka"), false)
+            compare(Kirigami.NameUtils.initialsFromString("Kalanoka"), "K")
+
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("Why would anyone use such a long not name in the field of the Name"), false)
+            compare(Kirigami.NameUtils.initialsFromString("Why would anyone use such a long not name in the field of the Name"), "WN")
+
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("Live-CD User"), false)
+            compare(Kirigami.NameUtils.initialsFromString("Live-CD User"), "LU")
+        }
+        // these are just randomly sampled names from internet pages in the
+        // source languages of the name
+        function test_jp_name() {
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("北里 柴三郎"), false)
+            compare(Kirigami.NameUtils.initialsFromString("北里 柴三郎"), "北")
+
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("小野田 寛郎"), false)
+            compare(Kirigami.NameUtils.initialsFromString("小野田 寛郎"), "小")
+        }
+        function test_cn_name() {
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("蔣經國"), false)
+            compare(Kirigami.NameUtils.initialsFromString("蔣經國"), "蔣")
+        }
+        function test_bad_names() {
+            compare(Kirigami.NameUtils.isStringUnsuitableForInitials("151231023"), true)
+        }
+    }
+    TestCase {
+        name: "AvatarActions"
+
+        width: 110
+        height: 110 * 3
+        visible: true
+        when: windowShown
+
+        Kirigami.Avatar {
+            id: avatarWithNullAction
+
+            x: 5
+            y: 5
+            width: 100
+            height: 100
+
+            actions.main: null
+            activeFocusOnTab: true
+        }
+
+        Kirigami.Avatar {
+            id: avatarWithKirigamiAction
+
+            x: 5
+            y: 110 + 5
+            width: 100
+            height: 100
+
+            actions.main: Kirigami.Action {
+                onTriggered: {
+                    signalProxyForKirigamiAction.triggered();
+                }
+            }
+
+            QtObject {
+                id: signalProxyForKirigamiAction
+                signal triggered()
+            }
+
+            SignalSpy {
+                id: spyKirigamiAction
+                target: signalProxyForKirigamiAction
+                signalName: "triggered"
+            }
+        }
+
+        Kirigami.Avatar {
+            id: avatarWithImpostorAction
+
+            x: 5
+            y: 110 * 2 + 5
+            width: 100
+            height: 100
+
+            // Ideally, custom objects should not be assignable here
+            actions.main: QtObject {
+                function trigger() {
+                    signalProxyForImpostorAction.triggered();
+                    return true;
+                }
+            }
+
+            QtObject {
+                id: signalProxyForImpostorAction
+                signal triggered()
+            }
+
+            SignalSpy {
+                id: spyImpostorAction
+                target: signalProxyForImpostorAction
+                signalName: "triggered"
+            }
+        }
+
+        function test_null_action() {
+            mouseClick(avatarWithNullAction);
+            avatarWithNullAction.forceActiveFocus(Qt.TabFocusReason);
+            compare(avatarWithNullAction.activeFocus, true);
+            keyClick(Qt.Key_Space);
+            keyClick(Qt.Key_Return);
+            keyClick(Qt.Key_Enter);
+            // Should not print any TypeError warnings, but there's no way to
+            // test it, except that it should not abort execution of this
+            // test script.
+            // TODO KF6: Use failOnWarning available from Qt 6.3
+        }
+
+        function test_kirigami_action() {
+            spyKirigamiAction.clear();
+            mouseClick(avatarWithKirigamiAction);
+            compare(spyKirigamiAction.count, 1);
+            avatarWithKirigamiAction.forceActiveFocus(Qt.TabFocusReason);
+            keyClick(Qt.Key_Space);
+            compare(spyKirigamiAction.count, 2);
+            keyClick(Qt.Key_Return);
+            compare(spyKirigamiAction.count, 3);
+            keyClick(Qt.Key_Enter);
+            compare(spyKirigamiAction.count, 4);
+        }
+
+        function test_impostor_action() {
+            spyImpostorAction.clear();
+            mouseClick(avatarWithImpostorAction);
+            compare(spyImpostorAction.count, 1);
+            avatarWithImpostorAction.forceActiveFocus(Qt.TabFocusReason);
+            keyClick(Qt.Key_Space);
+            compare(spyImpostorAction.count, 2);
+            keyClick(Qt.Key_Return);
+            compare(spyImpostorAction.count, 3);
+            keyClick(Qt.Key_Enter);
+            compare(spyImpostorAction.count, 4);
+        }
+    }
+}
diff --git a/autotests/tst_basiclistitem_tooltip.qml b/autotests/tst_basiclistitem_tooltip.qml
new file mode 100644 (file)
index 0000000..84cb516
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+import QtTest 1.15
+
+// Note about tooltips: they set `visible: true` as soon as they start enter
+// animation, and stay that way until exit animation completes. Actual
+// tooltip instance's visible state is not the same as QQC2.ToolTip.visible
+// attached property value.
+// Delays (timeouts) in tests are slightly increased to mitigate possible races.
+TestCase {
+    name: "BasicListItemToolTip"
+    visible: true
+    when: windowShown
+
+    width: ui.implicitWidth
+    height: ui.implicitHeight
+
+    BasicListItem_ToolTip_Test {
+        id: ui
+        anchors.fill: parent
+    }
+
+    function test_bothNotElided() {
+        mouseMove(ui.itemBothNotElided, ui.itemBothNotElided.width / 2, ui.itemBothNotElided.height / 2);
+        wait(QQC2.ToolTip.toolTip.delay * 1.1);
+        compare(QQC2.ToolTip.toolTip.visible, false);
+    }
+
+    function test_labelElided() {
+        mouseMove(ui.itemLabelElided, ui.itemLabelElided.width / 2, ui.itemLabelElided.height / 2);
+        tryCompare(QQC2.ToolTip.toolTip, "visible", true, QQC2.ToolTip.toolTip.delay * 1.1);
+        compare(QQC2.ToolTip.toolTip.text, ui.itemLabelElided.label);
+    }
+
+    function test_subtitleElided() {
+        mouseMove(ui.itemSubtitleElided, ui.itemSubtitleElided.width / 2, ui.itemSubtitleElided.height / 2);
+        tryCompare(QQC2.ToolTip.toolTip, "visible", true, QQC2.ToolTip.toolTip.delay * 1.1);
+        compare(QQC2.ToolTip.toolTip.text, ui.itemSubtitleElided.subtitle);
+    }
+
+    function test_bothElided() {
+        mouseMove(ui.itemBothElided, ui.itemBothElided.width / 2, ui.itemBothElided.height / 2);
+        tryCompare(QQC2.ToolTip.toolTip, "visible", true, QQC2.ToolTip.toolTip.delay * 1.1);
+        compare(QQC2.ToolTip.toolTip.text, `${ui.itemBothElided.label}<br/><br/>${ui.itemBothElided.subtitle}`);
+    }
+
+    function test_htmlElided() {
+        mouseMove(ui.itemHtmlElided, ui.itemHtmlElided.width / 2, ui.itemHtmlElided.height / 2);
+        tryCompare(QQC2.ToolTip.toolTip, "visible", true, QQC2.ToolTip.toolTip.delay * 1.1);
+        compare(QQC2.ToolTip.toolTip.text, `${ui.itemHtmlElided.label}<br/><br/>${ui.itemHtmlElided.subtitle}`);
+    }
+}
diff --git a/autotests/tst_formlayout.qml b/autotests/tst_formlayout.qml
new file mode 100644 (file)
index 0000000..2d5a52f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  SPDX-FileCopyrightText: 2022 Connor Carney <hello@connorcarney.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Window 2.0
+import QtQuick.Layouts 1.3
+import org.kde.kirigami 2.19 as Kirigami
+import QtTest 1.0
+
+TestCase {
+    id: testCase
+    name: "FormLayout"
+
+    Component {
+        id: fractionalSizeRoundingComponent
+        Window {
+            property var item: fractionalSizeItem
+            width: 600
+            height: 400
+            Kirigami.FormLayout {
+                anchors.fill: parent
+                Item {
+                    id: fractionalSizeItem
+                    implicitWidth: 160.375
+                    implicitHeight: 17.001
+                    Layout.fillWidth: true
+                }
+            }
+        }
+    }
+
+    function test_fractional_width_rounding() {
+        let window = fractionalSizeRoundingComponent.createObject();
+        let item = window.item;
+        window.show();
+
+        verify(item.width >= item.implicitWidth, "implicit width should not be rounded down");
+        fuzzyCompare(item.width, item.implicitWidth, 1);
+
+        window.close();
+    }
+
+    function test_fractional_height_rounding() {
+        let window = fractionalSizeRoundingComponent.createObject();
+        let item = window.item;
+        window.show();
+
+        verify(item.height >= item.implicitHeight, "implicit height should not be rounded down");
+        fuzzyCompare(item.height, item.implicitHeight, 1);
+
+        window.close();
+    }
+}
diff --git a/autotests/tst_globaldrawer.qml b/autotests/tst_globaldrawer.qml
new file mode 100644 (file)
index 0000000..4be820a
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+import QtTest 1.15
+
+Kirigami.ApplicationItem {
+    id: root
+
+    width: 500
+    height: 500
+    visible: true
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        id: drawer
+
+        drawerOpen: true
+
+        header: Rectangle {
+            id: headerItem
+            implicitHeight: 50
+            implicitWidth: 50
+            color: "red"
+            radius: 20 // to see its bounds
+        }
+
+        // Create some item which we can use to measure actual header height
+        Rectangle {
+            id: topItem
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            color: "green"
+            radius: 20 // to see its bounds
+        }
+    }
+
+    TestCase {
+        name: "GlobalDrawerHeader"
+        when: windowShown
+
+        function test_headerItemVisibility() {
+            const overlay = QQC2.Overlay.overlay;
+            verify(headerItem.height !== 0);
+
+            // Due to margins, position won't be exactly zero...
+            let position = topItem.mapToItem(overlay, 0, 0);
+            verify(position.y > 0);
+            const oldY = position.y;
+
+            // ...but with visible header it would be greater than with invisible.
+            headerItem.visible = false;
+            waitForRendering(overlay);
+            position = topItem.mapToItem(overlay, 0, 0);
+            verify(position.y < oldY);
+
+            // And now return it back to where we started.
+            headerItem.visible = true;
+            waitForRendering(overlay);
+            position = topItem.mapToItem(overlay, 0, 0);
+            verify(position.y === oldY);
+        }
+    }
+}
diff --git a/autotests/tst_icon.qml b/autotests/tst_icon.qml
new file mode 100644 (file)
index 0000000..2de2409
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtTest 1.0
+import org.kde.kirigami 2.11 as Kirigami
+
+TestCase {
+    id: testCase
+    name: "IconTests"
+
+    width: 400
+    height: 400
+    visible: true
+
+    when: windowShown
+
+    Component { id: emptyIcon; Kirigami.Icon { } }
+    Component { id: sourceOnlyIcon; Kirigami.Icon { source: "document-new" } }
+    Component { id: sizeOnlyIcon; Kirigami.Icon { width: 50; height: 50 } }
+    Component { id: sizeSourceIcon; Kirigami.Icon { width: 50; height: 50; source: "document-new" } }
+    Component { id: minimalSizeIcon; Kirigami.Icon { width: 1; height: 1; source: "document-new" } }
+
+    function test_create_data() {
+        return [
+            { tag: "Empty", component: emptyIcon },
+            { tag: "Source Only", component: sourceOnlyIcon },
+            { tag: "Size Only", component: sizeOnlyIcon },
+            { tag: "Size & Source", component: sizeSourceIcon },
+            { tag: "Minimal Size", component: minimalSizeIcon }
+        ]
+    }
+
+    // Test creation of Icon objects.
+    // It should not crash when certain properties are not specified and also
+    // should still work when they are.
+    function test_create(data) {
+        var icon = createTemporaryObject(data.component, testCase)
+        verify(icon)
+        verify(waitForRendering(icon))
+    }
+}
diff --git a/autotests/tst_keynavigation.qml b/autotests/tst_keynavigation.qml
new file mode 100644 (file)
index 0000000..6fc539f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.4 as Kirigami
+import QtTest 1.0
+import "../tests"
+
+TestCase {
+    id: testCase
+    width: 400
+    height: 400
+    name: "KeyboardNavigation"
+
+    KeyboardTest {
+        id: mainWindow
+        width: 480
+        height: 360
+    }
+
+    SignalSpy {
+        id: spyActive
+        target: mainWindow
+        signalName: "activeChanged"
+    }
+    SignalSpy {
+        id: spyLastKey
+        target: mainWindow.pageStack.currentItem
+        signalName: "lastKeyChanged"
+    }
+
+    function initTestCase() {
+        mainWindow.show()
+    }
+
+    function cleanupTestCase() {
+        mainWindow.close()
+    }
+
+    function test_press() {
+        compare(mainWindow.pageStack.depth, 2)
+        compare(mainWindow.pageStack.currentIndex, 1)
+        if (!mainWindow.active)
+            spyActive.wait(5000)
+        verify(mainWindow.active)
+        keyClick("A")
+        spyLastKey.wait()
+        compare(mainWindow.pageStack.currentItem.lastKey, "A")
+        keyClick(Qt.Key_Left, Qt.AltModifier)
+        compare(mainWindow.pageStack.currentIndex, 0)
+        compare(mainWindow.pageStack.currentItem.lastKey, "")
+        keyClick("B")
+        spyLastKey.wait()
+        compare(mainWindow.pageStack.currentItem.lastKey, "B")
+    }
+}
diff --git a/autotests/tst_listskeynavigation.qml b/autotests/tst_listskeynavigation.qml
new file mode 100644 (file)
index 0000000..b964505
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.4 as Kirigami
+import QtTest 1.0
+import "../tests"
+
+TestCase {
+    id: testCase
+    width: 400
+    height: 400
+    name: "KeyboardListsNavigation"
+
+    KeyboardListTest {
+        id: mainWindow
+        width: 480
+        height: 360
+    }
+
+    SignalSpy {
+        id: spyActive
+        target: mainWindow
+        signalName: "activeChanged"
+    }
+    SignalSpy {
+        id: spyCurrentIndex
+        target: mainWindow.pageStack.currentItem.flickable
+        signalName: "currentIndexChanged"
+    }
+
+    function initTestCase() {
+        mainWindow.show()
+    }
+
+    function cleanupTestCase() {
+        mainWindow.close()
+    }
+
+    function test_press() {
+        compare(mainWindow.pageStack.depth, 1)
+        compare(mainWindow.pageStack.currentIndex, 0)
+        if (!mainWindow.active)
+            spyActive.wait(5000)
+        verify(mainWindow.active)
+        compare(mainWindow.pageStack.currentItem.flickable.currentIndex, 0)
+        keyClick(Qt.Key_Down)
+        spyCurrentIndex.wait()
+        compare(mainWindow.pageStack.currentItem.flickable.currentIndex, 1)
+    }
+}
diff --git a/autotests/tst_mnemonicdata.qml b/autotests/tst_mnemonicdata.qml
new file mode 100644 (file)
index 0000000..16debe6
--- /dev/null
@@ -0,0 +1,20 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.4 as Kirigami
+import QtTest 1.0
+import "../tests"
+
+TestCase {
+    id: testCase
+
+    Kirigami.MnemonicData.enabled: true
+    Kirigami.MnemonicData.label: "设置(&S)"
+
+    width: 400
+    height: 400
+
+    function test_press() {
+        compare(Kirigami.MnemonicData.richTextLabel, "设置")
+    }
+}
diff --git a/autotests/tst_pagerouter.qml b/autotests/tst_pagerouter.qml
new file mode 100644 (file)
index 0000000..bc77ef8
--- /dev/null
@@ -0,0 +1,110 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import QtTest 1.0
+
+Kirigami.PageRow {
+    id: root
+    TestCase {
+        name: "PageRouterGeneralTests"
+        function test_a_init() {
+            compare(router.currentRoutes().length, 1)
+        }
+        function test_b_navigate() {
+            router.navigateToRoute(["home", "login"])
+            compare(router.currentRoutes().length, 2)
+        }
+        function test_c_data() {
+            router.navigateToRoute(["home", {"route": "login", "data": "red"}])
+            compare(router.routeActive(["home", {"route": "login", "data": "red"}]), true)
+            compare(router.routeActive(["home", {"route": "login", "data": "blue"}]), false)
+        }
+        function test_d_cache_works() {
+            router.navigateToRoute(["home", {"route": "login", "data": "red"}, {"route": "login", "data": "blue"}])
+            compare(router.currentRoutes().length, 3)
+        }
+        function test_e_push() {
+            router.pushRoute("home")
+            compare(router.currentRoutes().length, 4)
+        }
+        function test_f_pop() {
+            router.popRoute()
+            compare(router.currentRoutes().length, 3)
+        }
+        function test_g_bring_to_view() {
+            router.bringToView("home")
+            compare(root.columnView.currentIndex, 0)
+            router.bringToView({"route": "login", "data": "red"})
+            compare(root.columnView.currentIndex, 1)
+            router.bringToView({"route": "login", "data": "blue"})
+            compare(root.columnView.currentIndex, 2)
+        }
+        function test_h_routeactive() {
+            compare(router.routeActive(["home"]), true)
+            compare(router.routeActive(["home", "login"]), true)
+            compare(router.routeActive(["home", {"route": "login", "data": "red"}]), true)
+            compare(router.routeActive(["home", {"route": "login", "data": "blue"}]), false)
+        }
+        function test_i_initial_route() {
+            router.initialRoute = "login"
+            compare(router.routeActive(["login"]), false)
+            compare(router.currentRoutes().length, 3)
+        }
+        function test_j_navigation_two() {
+            router.navigateToRoute(["home", {"route": "login", "data": "red"}, {"route": "login", "data": "blue"}])
+            compare(router.currentRoutes().length, 3)
+            router.navigateToRoute(["home"])
+            compare(router.currentRoutes().length, 1)
+            compare(router.pageStack.count, 1)
+        }
+    }
+    Kirigami.PageRouter {
+        id: router
+        initialRoute: "home"
+        pageStack: root.columnView
+
+        Kirigami.PageRoute {
+            name: "home"
+            cache: false
+            Component {
+                Kirigami.Page {
+                    Column {
+                        Kirigami.Heading {
+                            text: "Welcome"
+                        }
+                        QQC2.Button {
+                            text: "Red Login"
+                            onClicked: Kirigami.PageRouter.navigateToRoute(["home", {"route": "login", "data": "red"}])
+                        }
+                        QQC2.Button {
+                            text: "Blue Login"
+                            onClicked: Kirigami.PageRouter.navigateToRoute(["home", {"route": "login", "data": "blue"}])
+                        }
+                    }
+                }
+            }
+        }
+        Kirigami.PageRoute {
+            name: "login"
+            cache: true
+            Component {
+                Kirigami.Page {
+                    Column {
+                        Kirigami.Heading {
+                            text: "Login"
+                        }
+                        Rectangle {
+                            height: 50
+                            width: 50
+                            color: Kirigami.PageRouter.data
+                        }
+                        QQC2.Button {
+                            text: "Back to Home"
+                            onClicked: Kirigami.PageRouter.navigateToRoute("home")
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/autotests/tst_pagerow.qml b/autotests/tst_pagerow.qml
new file mode 100644 (file)
index 0000000..b3a972f
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Window 2.1
+import org.kde.kirigami 2.4 as Kirigami
+import QtTest 1.0
+
+TestCase {
+    id: testCase
+    width: 400
+    height: 400
+    name: "GoBack"
+
+    function applicationWindow() { return mainWindow; }
+
+    Kirigami.ApplicationWindow {
+        id: mainWindow
+        width: 480
+        height: 360
+        pageStack.initialPage: Kirigami.Page {
+            Rectangle {
+                anchors.fill: parent
+                color: "green"
+            }
+        }
+    }
+
+    Component {
+        id: randomPage
+        Kirigami.Page {
+            Rectangle {
+                anchors.fill: parent
+                color: "red"
+            }
+        }
+    }
+
+    SignalSpy {
+        id: spyCurrentIndex
+        target: mainWindow.pageStack
+        signalName: "currentIndexChanged"
+    }
+
+    SignalSpy {
+        id: spyActive
+        target: mainWindow
+        signalName: "activeChanged"
+    }
+
+    function initTestCase() {
+        mainWindow.show()
+    }
+
+    function cleanupTestCase() {
+        mainWindow.close()
+    }
+
+    function init() {
+        mainWindow.pageStack.clear()
+        spyActive.clear()
+        spyCurrentIndex.clear()
+    }
+
+    function test_pop() {
+        compare(mainWindow.pageStack.depth, 0)
+        mainWindow.pageStack.push(randomPage)
+        compare(mainWindow.pageStack.depth, 1)
+        mainWindow.pageStack.pop()
+        compare(mainWindow.pageStack.depth, 0)
+    }
+
+    function test_goBack() {
+        compare(mainWindow.pageStack.depth, 0)
+        mainWindow.pageStack.push(randomPage)
+        mainWindow.pageStack.push(randomPage)
+        compare(mainWindow.pageStack.depth, 2)
+        compare(mainWindow.pageStack.currentIndex, 1)
+        compare(spyCurrentIndex.count, 3)
+        spyActive.clear()
+        mainWindow.requestActivate()
+        spyCurrentIndex.clear()
+        if (!mainWindow.active)
+            spyActive.wait()
+        verify(mainWindow.active)
+        keyClick(Qt.Key_Left, Qt.AltModifier)
+
+        spyCurrentIndex.wait()
+
+        compare(mainWindow.pageStack.depth, 2)
+        compare(mainWindow.pageStack.currentIndex, 0)
+        compare(spyCurrentIndex.count, 1)
+        mainWindow.pageStack.pop()
+        compare(mainWindow.pageStack.depth, 1)
+    }
+
+    property int destructions: 0
+    Component {
+        id: destroyedPage
+        Kirigami.Page {
+            id: page
+            Rectangle {
+                anchors.fill: parent
+                color: "blue"
+                Component.onDestruction: {
+                    testCase.destructions++
+                }
+            }
+        }
+    }
+    SignalSpy {
+        id: spyDestructions
+        target: testCase
+        signalName: "destructionsChanged"
+    }
+    function test_clearPages() {
+        mainWindow.pageStack.push(destroyedPage)
+        mainWindow.pageStack.push(destroyedPage)
+        mainWindow.pageStack.push(destroyedPage)
+        compare(mainWindow.pageStack.depth, 3)
+        mainWindow.pageStack.clear()
+
+        compare(mainWindow.pageStack.depth, 0)
+        spyDestructions.wait()
+        compare(testCase.destructions, 2)
+    }
+}
diff --git a/autotests/tst_routerwindow.qml b/autotests/tst_routerwindow.qml
new file mode 100644 (file)
index 0000000..e9ee373
--- /dev/null
@@ -0,0 +1,109 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import QtTest 1.0
+
+TestCase {
+    name: "RouterWindowTests"
+    property alias router: root.router
+    when: windowShown
+
+    function test_a_init() {
+        compare(router.currentRoutes().length, 1)
+    }
+    function test_b_navigate() {
+        router.navigateToRoute(["home", "login"])
+        compare(router.currentRoutes().length, 2)
+    }
+    function test_c_data() {
+        router.navigateToRoute(["home", {"route": "login", "data": "red"}])
+        compare(router.routeActive(["home", {"route": "login", "data": "red"}]), true)
+        compare(router.routeActive(["home", {"route": "login", "data": "blue"}]), false)
+    }
+    function test_d_cache_works() {
+        router.navigateToRoute(["home", {"route": "login", "data": "red"}, {"route": "login", "data": "blue"}])
+        compare(router.currentRoutes().length, 3)
+    }
+    function test_e_push() {
+        router.pushRoute("home")
+        compare(router.currentRoutes().length, 4)
+    }
+    function test_f_pop() {
+        router.popRoute()
+        compare(router.currentRoutes().length, 3)
+    }
+    function test_g_bring_to_view() {
+        router.bringToView("home")
+        compare(root.pageStack.currentIndex, 0)
+        router.bringToView({"route": "login", "data": "red"})
+        compare(root.pageStack.currentIndex, 1)
+        router.bringToView({"route": "login", "data": "blue"})
+        compare(root.pageStack.currentIndex, 2)
+    }
+    function test_h_routeactive() {
+        compare(router.routeActive(["home"]), true)
+        compare(router.routeActive(["home", "login"]), true)
+        compare(router.routeActive(["home", {"route": "login", "data": "red"}]), true)
+        compare(router.routeActive(["home", {"route": "login", "data": "blue"}]), false)
+    }
+    function test_i_initial_route() {
+        router.initialRoute = "login"
+        compare(router.routeActive(["login"]), false)
+        compare(router.currentRoutes().length, 3)
+    }
+    function test_j_navigation_two() {
+        router.navigateToRoute(["home", {"route": "login", "data": "red"}, {"route": "login", "data": "blue"}])
+        compare(router.currentRoutes().length, 3)
+        router.navigateToRoute(["home"])
+        compare(router.currentRoutes().length, 1)
+        compare(router.pageStack.count, 1)
+    }
+
+    Kirigami.RouterWindow {
+        id: root
+        initialRoute: "home"
+        Kirigami.PageRoute {
+            name: "home"
+            cache: false
+            Component {
+                Kirigami.Page {
+                    Column {
+                        Kirigami.Heading {
+                            text: "Welcome"
+                        }
+                        QQC2.Button {
+                            text: "Red Login"
+                            onClicked: Kirigami.PageRouter.navigateToRoute(["home", {"route": "login", "data": "red"}])
+                        }
+                        QQC2.Button {
+                            text: "Blue Login"
+                            onClicked: Kirigami.PageRouter.navigateToRoute(["home", {"route": "login", "data": "blue"}])
+                        }
+                    }
+                }
+            }
+        }
+        Kirigami.PageRoute {
+            name: "login"
+            cache: true
+            Component {
+                Kirigami.Page {
+                    Column {
+                        Kirigami.Heading {
+                            text: "Login"
+                        }
+                        Rectangle {
+                            height: 50
+                            width: 50
+                            color: Kirigami.PageRouter.data
+                        }
+                        QQC2.Button {
+                            text: "Back to Home"
+                            onClicked: Kirigami.PageRouter.navigateToRoute("home")
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/autotests/tst_theme.qml b/autotests/tst_theme.qml
new file mode 100644 (file)
index 0000000..ac1c516
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtTest 1.0
+import org.kde.kirigami 2.11 as Kirigami
+
+TestCase {
+    id: testCase
+    name: "ThemeTest"
+
+    width: 400
+    height: 400
+    visible: true
+
+    when: windowShown
+
+    TextMetrics {
+        id: textMetrics
+    }
+
+    // Not all properties are updated immediately to avoid having massive storms
+    // of duplicated signals and to prevent changes from retriggering code that
+    // changed it. To deal with that, we need to wait a bit before continuiing
+    // when we change properties. This time shouldn't be too short because on
+    // some machines it may take a bit longer for things to properly be updated.
+    function waitForEvents()
+    {
+        wait(20)
+    }
+
+    Component {
+        id: basic
+
+        Rectangle {
+            color: Kirigami.Theme.backgroundColor
+
+            property alias text: textItem
+
+            Text {
+                id: textItem
+                color: Kirigami.Theme.textColor
+                font: Kirigami.Theme.defaultFont
+            }
+        }
+    }
+
+    function test_basic() {
+        var item = createTemporaryObject(basic, testCase)
+        verify(item)
+
+        compare(item.Kirigami.Theme.colorSet, Kirigami.Theme.Window)
+        compare(item.Kirigami.Theme.colorGroup, Kirigami.Theme.Active)
+        verify(item.Kirigami.Theme.inherit)
+
+        compare(item.color, "#eff0f1")
+        compare(item.text.color, "#31363b")
+        compare(item.text.font.family, textMetrics.font.family)
+    }
+
+    Component {
+        id: override
+
+        Rectangle {
+            Kirigami.Theme.backgroundColor: "#ff0000"
+            color: Kirigami.Theme.backgroundColor
+        }
+    }
+
+    function test_override() {
+        var item = createTemporaryObject(override, testCase)
+        verify(item)
+
+        compare(item.color, "#ff0000")
+
+        item.Kirigami.Theme.backgroundColor = "#00ff00"
+
+        // Changes to Theme are not immediately propagated, so give it a few
+        // moments.
+        waitForEvents()
+
+        compare(item.color, "#00ff00")
+
+        // Changing colorSet or colorGroup does not affect local overrides
+        item.Kirigami.Theme.colorSet = Kirigami.Theme.Complementary
+        item.Kirigami.Theme.colorGroup = Kirigami.Theme.Disabled
+
+        waitForEvents()
+
+        compare(item.color, "#00ff00")
+    }
+
+    Component {
+        id: inherit
+
+        Rectangle {
+            color: Kirigami.Theme.backgroundColor
+
+            property alias child1: rect1
+            property alias child2: rect2
+
+            Rectangle {
+                id: rect1
+                color: Kirigami.Theme.backgroundColor
+            }
+            Rectangle {
+                id: rect2
+                Kirigami.Theme.inherit: false
+                color: Kirigami.Theme.backgroundColor
+            }
+        }
+    }
+
+    function test_inherit() {
+        var item = createTemporaryObject(inherit, testCase)
+        verify(item)
+
+        // Default values are all the same
+        compare(item.color, "#eff0f1")
+        compare(item.child1.color, "#eff0f1")
+        compare(item.child2.color, "#eff0f1")
+
+        // If we change the colorSet, the item that inherits gets updated, but
+        // the item that does not stays the same.
+        item.Kirigami.Theme.colorSet = Kirigami.Theme.View
+
+        waitForEvents()
+
+        compare(item.color, "#fcfcfc")
+        compare(item.child1.color, "#fcfcfc")
+        compare(item.child2.color, "#eff0f1")
+
+        // If we override a color, the item that inherits gets that color, while
+        // the item that does not ignores it.
+        item.Kirigami.Theme.backgroundColor = "#ff0000"
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#eff0f1")
+
+        // If we change the color set again, the overridden color remains the
+        // same for both the original object and the inherited object.
+        item.Kirigami.Theme.colorSet = Kirigami.Theme.View
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#eff0f1")
+
+        // If we override a color of the item that inherits, it will stay the
+        // same even if that color changes in the parent.
+        item.child1.Kirigami.Theme.backgroundColor = "#00ff00"
+        item.Kirigami.Theme.backgroundColor = "#0000ff"
+
+        waitForEvents()
+
+        compare(item.color, "#0000ff")
+        compare(item.child1.color, "#00ff00")
+        compare(item.child2.color, "#eff0f1")
+    }
+
+    Component {
+        id: deepInherit
+
+        Rectangle {
+            color: Kirigami.Theme.backgroundColor
+
+            property alias child1: rect1
+            property alias child2: rect2
+            property alias child3: rect3
+
+            Rectangle {
+                id: rect1
+                color: Kirigami.Theme.backgroundColor
+
+                Rectangle {
+                    id: rect2
+                    color: Kirigami.Theme.backgroundColor
+
+                    Rectangle {
+                        id: rect3
+                        color: Kirigami.Theme.backgroundColor
+                    }
+                }
+            }
+        }
+    }
+
+    function test_inherit_deep() {
+        var item = createTemporaryObject(deepInherit, testCase)
+        verify(item)
+
+        waitForEvents()
+
+        compare(item.color, "#eff0f1")
+        compare(item.child1.color, "#eff0f1")
+        compare(item.child2.color, "#eff0f1")
+        compare(item.child3.color, "#eff0f1")
+
+        item.Kirigami.Theme.backgroundColor = "#ff0000"
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#ff0000")
+        compare(item.child3.color, "#ff0000")
+
+        item.child2.Kirigami.Theme.inherit = false
+        item.child2.Kirigami.Theme.backgroundColor = "#00ff00"
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#00ff00")
+        compare(item.child3.color, "#00ff00")
+
+        item.child2.Kirigami.Theme.inherit = true
+        item.child2.Kirigami.Theme.backgroundColor = undefined
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#ff0000")
+        compare(item.child3.color, "#ff0000")
+
+        item.child2.Kirigami.Theme.colorSet = Kirigami.Theme.Complementary
+        item.child2.Kirigami.Theme.inherit = false
+
+        waitForEvents()
+
+        compare(item.color, "#ff0000")
+        compare(item.child1.color, "#ff0000")
+        compare(item.child2.color, "#31363b")
+        compare(item.child3.color, "#31363b")
+    }
+
+    Component {
+        id: colorSet
+
+        Rectangle {
+            Kirigami.Theme.colorSet: Kirigami.Theme.View
+            color: Kirigami.Theme.backgroundColor
+        }
+    }
+
+    function test_colorset() {
+        var item = createTemporaryObject(colorSet, testCase)
+        verify(item)
+
+        waitForEvents()
+
+        compare(item.color, "#fcfcfc")
+
+        item.Kirigami.Theme.colorSet = Kirigami.Theme.Complementary
+
+        waitForEvents()
+
+        compare(item.color, "#31363b")
+    }
+
+    Component {
+        id: colorGroup
+
+        Rectangle {
+            Kirigami.Theme.colorGroup: Kirigami.Theme.Disabled
+            color: Kirigami.Theme.backgroundColor
+        }
+    }
+
+    function test_colorGroup() {
+        var item = createTemporaryObject(colorGroup, testCase)
+        verify(item)
+
+        waitForEvents()
+
+        var color = Qt.tint("#eff0f1", "transparent")
+
+        compare(item.color, Qt.hsva(color.hsvHue, color.hsvSaturation * 0.5, color.hsvValue * 0.8))
+
+        item.Kirigami.Theme.colorGroup = Kirigami.Theme.Inactive
+
+        waitForEvents()
+
+        compare(item.color, Qt.hsva(color.hsvHue, color.hsvSaturation * 0.5, color.hsvValue))
+    }
+
+    Component {
+        id: palette
+
+        Rectangle {
+            color: Kirigami.Theme.backgroundColor
+
+            property alias child: button
+
+            Button {
+                id: button
+                palette: Kirigami.Theme.palette
+            }
+        }
+    }
+
+    function test_palette() {
+        var item = createTemporaryObject(palette, testCase)
+        verify(item)
+
+        compare(item.child.background.color, "#eff0f1")
+        compare(item.child.contentItem.color, "#31363b")
+
+        item.Kirigami.Theme.backgroundColor = "#ff0000"
+
+        waitForEvents()
+
+        compare(item.child.background.color, "#ff0000")
+    }
+}
diff --git a/autotests/wheelhandler/ContentFlickable.qml b/autotests/wheelhandler/ContentFlickable.qml
new file mode 100644 (file)
index 0000000..0b83159
--- /dev/null
@@ -0,0 +1,64 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+
+Flickable {
+    id: flickable
+    property real cellWidth: 60
+    property real cellHeight: 60
+    readonly property QQC2.Button enableSliderButton: enableSliderButton
+    readonly property QQC2.Slider slider: slider
+    implicitWidth: cellWidth * 10 + leftMargin + rightMargin
+    implicitHeight: cellHeight * 10 + topMargin + bottomMargin
+    contentWidth: contentItem.childrenRect.width
+    contentHeight: contentItem.childrenRect.height
+    Grid {
+        id: grid
+        columns: Math.sqrt(visibleChildren.length)
+        Repeater {
+            model: 500
+            delegate: Rectangle {
+                implicitWidth: flickable.cellWidth
+                implicitHeight: flickable.cellHeight
+                gradient: Gradient {
+                    orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                    GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                }
+            }
+        }
+        QQC2.Button {
+            id: enableSliderButton
+            width: flickable.cellWidth
+            height: flickable.cellHeight
+            contentItem: QQC2.Label {
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: "Enable Slider"
+                wrapMode: Text.Wrap
+            }
+            checked: true
+        }
+        QQC2.Slider {
+            id: slider
+            enabled: enableSliderButton.checked
+            width: flickable.cellWidth
+            height: flickable.cellHeight
+        }
+        Repeater {
+            model: 500
+            delegate: Rectangle {
+                implicitWidth: flickable.cellWidth
+                implicitHeight: flickable.cellHeight
+                gradient: Gradient {
+                    orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                    GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                }
+            }
+        }
+    }
+}
diff --git a/autotests/wheelhandler/ScrollableFlickable.qml b/autotests/wheelhandler/ScrollableFlickable.qml
new file mode 100644 (file)
index 0000000..dec475b
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+
+ContentFlickable {
+    id: flickable
+    leftMargin: QQC2.ScrollBar.vertical && QQC2.ScrollBar.vertical.visible && QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+    rightMargin: QQC2.ScrollBar.vertical && QQC2.ScrollBar.vertical.visible && !QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+    bottomMargin: QQC2.ScrollBar.horizontal && QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.height : 0
+    QQC2.ScrollBar.vertical: QQC2.ScrollBar {
+        parent: flickable.parent
+        anchors.right: flickable.right
+        anchors.top: flickable.top
+        anchors.bottom: flickable.bottom
+        anchors.bottomMargin: flickable.QQC2.ScrollBar.horizontal ? flickable.QQC2.ScrollBar.horizontal.height : anchors.margins
+        active: flickable.QQC2.ScrollBar.vertical.active
+    }
+    QQC2.ScrollBar.horizontal: QQC2.ScrollBar {
+        parent: flickable.parent
+        anchors.left: flickable.left
+        anchors.right: flickable.right
+        anchors.bottom: flickable.bottom
+        anchors.rightMargin: flickable.QQC2.ScrollBar.vertical ? flickable.QQC2.ScrollBar.vertical.width : anchors.margins
+        active: flickable.QQC2.ScrollBar.horizontal.active
+    }
+}
diff --git a/autotests/wheelhandler/tst_filterMouseEvents.qml b/autotests/wheelhandler/tst_filterMouseEvents.qml
new file mode 100644 (file)
index 0000000..eb523ac
--- /dev/null
@@ -0,0 +1,75 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+import QtTest 1.15
+
+TestCase {
+    id: root
+    name: "WheelHandler filterMouseEvents"
+    visible: true
+    when: windowShown
+    width: flickable.implicitWidth
+    height: flickable.implicitHeight
+
+    function test_MouseFlick() {
+        const x = flickable.contentX
+        const y = flickable.contentY
+        mousePress(flickable, flickable.leftMargin + 10, 10)
+        mouseMove(flickable)
+        mouseRelease(flickable)
+        verify(flickable.contentX === x && flickable.contentY === y, "not moved")
+    }
+
+    // NOTE: Unfortunately, this test can't work. Flickable does not handle touch events, only mouse events synthesized from touch events
+    // TODO: Uncomment if Flickable is ever able to use touch events.
+    /*function test_TouchFlick() {
+        const x = flickable.contentX, y = flickable.contentY
+        let touch = touchEvent(flickable)
+        // Press on center.
+        touch.press(0, flickable)
+        touch.commit()
+        // Move a bit towards top left.
+        touch.move(0, flickable, flickable.width/2 - 50, flickable.height/2 - 50)
+        touch.commit()
+        // Release at the spot we moved to.
+        touch.release(0, flickable, flickable.width/2 - 50, flickable.height/2 - 50)
+        touch.commit()
+        verify(flickable.contentX !== x || flickable.contentY !== y, "moved")
+    }*/
+
+    function test_MouseScrollBars() {
+        const x = flickable.contentX, y = flickable.contentY
+        mousePress(flickable, flickable.leftMargin + 10, 10)
+        mouseMove(flickable)
+        const interactive = flickable.QQC2.ScrollBar.vertical.interactive || flickable.QQC2.ScrollBar.horizontal.interactive
+        mouseRelease(flickable)
+        verify(interactive, "interactive scrollbars")
+    }
+
+    function test_TouchScrollBars() {
+        const x = flickable.contentX, y = flickable.contentY
+        let touch = touchEvent(flickable)
+        touch.press(0, flickable, flickable.leftMargin + 10, 10)
+        touch.commit()
+        touch.move(0, flickable)
+        touch.commit()
+        const interactive = flickable.QQC2.ScrollBar.vertical.interactive || flickable.QQC2.ScrollBar.horizontal.interactive
+        touch.release(0, flickable)
+        touch.commit()
+        verify(!interactive, "no interactive scrollbars")
+    }
+
+    ScrollableFlickable {
+        id: flickable
+        anchors.fill: parent
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+            filterMouseEvents: true
+        }
+    }
+}
diff --git a/autotests/wheelhandler/tst_invokables.qml b/autotests/wheelhandler/tst_invokables.qml
new file mode 100644 (file)
index 0000000..e77e1bd
--- /dev/null
@@ -0,0 +1,75 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+import QtTest 1.15
+
+TestCase {
+    id: root
+    readonly property real hstep: wheelHandler.horizontalStepSize
+    readonly property real vstep: wheelHandler.verticalStepSize
+    readonly property real pageWidth: flickable.width - flickable.leftMargin - flickable.rightMargin
+    readonly property real pageHeight: flickable.height - flickable.topMargin - flickable.bottomMargin
+    readonly property real contentWidth: flickable.contentWidth
+    readonly property real contentHeight: flickable.contentHeight
+    property alias wheelHandler: wheelHandler
+    property alias flickable: flickable
+
+    name: "WheelHandler invokable functions"
+    visible: true
+    when: windowShown
+    width: flickable.implicitWidth
+    height: flickable.implicitHeight
+
+    function test_Invokables() {
+        const originalX = flickable.contentX
+        const originalY = flickable.contentY
+        let x = originalX
+        let y = originalY
+
+        wheelHandler.scrollRight()
+        verify(flickable.contentX === x + hstep, "scrollRight()")
+        x = flickable.contentX
+
+        wheelHandler.scrollLeft()
+        verify(flickable.contentX === x - hstep, "scrollLeft()")
+        x = flickable.contentX
+
+        wheelHandler.scrollDown()
+        verify(flickable.contentY === y + vstep, "scrollDown()")
+        y = flickable.contentY
+
+        wheelHandler.scrollUp()
+        verify(flickable.contentY === y - vstep, "scrollUp()")
+        y = flickable.contentY
+
+        wheelHandler.scrollRight(101)
+        verify(flickable.contentX === x + 101, "scrollRight(101)")
+        x = flickable.contentX
+
+        wheelHandler.scrollLeft(101)
+        verify(flickable.contentX === x - 101, "scrollLeft(101)")
+        x = flickable.contentX
+
+        wheelHandler.scrollDown(101)
+        verify(flickable.contentY === y + 101, "scrollDown(101)")
+        y = flickable.contentY
+
+        wheelHandler.scrollUp(101)
+        verify(flickable.contentY === y - 101, "scrollUp(101)")
+        y = flickable.contentY
+    }
+
+    ScrollableFlickable {
+        id: flickable
+        anchors.fill: parent
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+        }
+    }
+}
+
diff --git a/autotests/wheelhandler/tst_onWheel.qml b/autotests/wheelhandler/tst_onWheel.qml
new file mode 100644 (file)
index 0000000..818052e
--- /dev/null
@@ -0,0 +1,107 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+import QtTest 1.15
+
+TestCase {
+    id: root
+    name: "WheelHandler onWheel"
+    visible: true
+    when: windowShown
+    width: 600
+    height: 600
+
+    function test_onWheel() {
+        let contentX = flickable.contentX
+        let contentY = flickable.contentY
+        let contentWidth = flickable.contentWidth
+        let contentHeight = flickable.contentHeight
+
+        // grow
+        mouseWheel(flickable, flickable.leftMargin, 0, -120, -120, Qt.NoButton, Qt.ControlModifier)
+        verify(flickable.contentWidth === contentWidth - 120, "-xDelta")
+        contentWidth = flickable.contentWidth
+        verify(flickable.contentHeight === contentHeight - 120, "-yDelta")
+        contentHeight = flickable.contentHeight
+
+        // check if accepting the event prevents scrolling
+        verify(flickable.contentX === contentX && flickable.contentY === contentY, "not moved")
+
+        // shrink
+        mouseWheel(flickable, flickable.leftMargin, 0, 120, 120, Qt.NoButton, Qt.ControlModifier)
+        verify(flickable.contentWidth === contentWidth + 120, "+xDelta")
+        verify(flickable.contentHeight === contentHeight + 120, "+yDelta")
+
+        // check if accepting the event prevents scrolling
+        verify(flickable.contentX === contentX && flickable.contentY === contentY, "not moved")
+    }
+
+    Rectangle {
+        anchors.fill: parent
+        color: "black"
+    }
+
+    Flickable {
+        id: flickable
+        anchors.fill: parent
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+            onWheel: {
+                if (wheel.modifiers & Qt.ControlModifier) {
+                    // Adding delta is the simplest way to change size without running into floating point number issues
+                    // NOTE: Not limiting minimum content size to a size greater than the Flickable size makes it so
+                    // wheel events stop coming to onWheel when the content size is the size of the flickable or smaller.
+                    // Maybe it's a Flickable issue? Koko had the same problem with Flickable.
+                    flickable.contentWidth = Math.max(720, flickable.contentWidth + wheel.angleDelta.x)
+                    flickable.contentHeight = Math.max(720, flickable.contentHeight + wheel.angleDelta.y)
+                    wheel.accepted = true
+                }
+            }
+        }
+        leftMargin: QQC2.ScrollBar.vertical && QQC2.ScrollBar.vertical.visible && QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        rightMargin: QQC2.ScrollBar.vertical && QQC2.ScrollBar.vertical.visible && !QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        bottomMargin: QQC2.ScrollBar.horizontal && QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.height : 0
+        QQC2.ScrollBar.vertical: QQC2.ScrollBar {
+            parent: flickable.parent
+            anchors.right: flickable.right
+            anchors.top: flickable.top
+            anchors.bottom: flickable.bottom
+            anchors.bottomMargin: flickable.QQC2.ScrollBar.horizontal ? flickable.QQC2.ScrollBar.horizontal.height : anchors.margins
+            active: flickable.QQC2.ScrollBar.vertical.active
+        }
+        QQC2.ScrollBar.horizontal: QQC2.ScrollBar {
+            parent: flickable.parent
+            anchors.left: flickable.left
+            anchors.right: flickable.right
+            anchors.bottom: flickable.bottom
+            anchors.rightMargin: flickable.QQC2.ScrollBar.vertical ? flickable.QQC2.ScrollBar.vertical.width : anchors.margins
+            active: flickable.QQC2.ScrollBar.horizontal.active
+        }
+        contentWidth: 1200
+        contentHeight: 1200
+        Rectangle {
+            id: contentRect
+            anchors.fill: parent
+            gradient: Gradient.WideMatrix
+            border.color: Qt.rgba(0,0,0,0.5)
+            border.width: 2
+        }
+    }
+
+    QQC2.Label {
+        anchors.centerIn: parent
+        leftPadding: 4
+        rightPadding: 4
+        wrapMode: Text.Wrap
+        color: "white"
+        text: `Rectangle size: ${contentRect.width}x${contentRect.height}`
+        background: Rectangle {
+            color: "black"
+        }
+    }
+}
diff --git a/autotests/wheelhandler/tst_scrolling.qml b/autotests/wheelhandler/tst_scrolling.qml
new file mode 100644 (file)
index 0000000..6f6f321
--- /dev/null
@@ -0,0 +1,180 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+import QtTest 1.15
+
+TestCase {
+    id: root
+    readonly property real hstep: wheelHandler.horizontalStepSize
+    readonly property real vstep: wheelHandler.verticalStepSize
+    readonly property real pageWidth: flickable.width - flickable.leftMargin - flickable.rightMargin
+    readonly property real pageHeight: flickable.height - flickable.topMargin - flickable.bottomMargin
+    readonly property real contentWidth: flickable.contentWidth
+    readonly property real contentHeight: flickable.contentHeight
+    property alias wheelHandler: wheelHandler
+    property alias flickable: flickable
+
+    name: "WheelHandler scrolling"
+    visible: true
+    when: windowShown
+    width: flickable.implicitWidth
+    height: flickable.implicitHeight
+
+    function wheelScrolling(angleDelta = 120) {
+        let x = flickable.contentX
+        let y = flickable.contentY
+        const angleDeltaFactor = angleDelta / 120
+        mouseWheel(flickable, flickable.leftMargin, 0, -angleDelta, -angleDelta, Qt.NoButton)
+        verify(flickable.contentX === Math.round(x + hstep * angleDeltaFactor), "+xTick")
+        x = flickable.contentX
+        verify(flickable.contentY === Math.round(y + vstep * angleDeltaFactor), "+yTick")
+        y = flickable.contentY
+
+        mouseWheel(flickable, flickable.leftMargin, 0, angleDelta, angleDelta, Qt.NoButton)
+        verify(flickable.contentX === Math.round(x - hstep * angleDeltaFactor), "-xTick")
+        x = flickable.contentX
+        verify(flickable.contentY === Math.round(y - vstep * angleDeltaFactor), "-yTick")
+        y = flickable.contentY
+
+        if (Qt.platform.pluginName !== "xcb") {
+            mouseWheel(flickable, flickable.leftMargin, 0, 0, -angleDelta, Qt.NoButton, Qt.AltModifier)
+            verify(flickable.contentX === Math.round(x + hstep * angleDeltaFactor), "+h_yTick")
+            x = flickable.contentX
+            verify(flickable.contentY === y, "no +yTick")
+
+            mouseWheel(flickable, flickable.leftMargin, 0, 0, angleDelta, Qt.NoButton, Qt.AltModifier)
+            verify(flickable.contentX === Math.round(x - hstep * angleDeltaFactor), "-h_yTick")
+            x = flickable.contentX
+            verify(flickable.contentY === y, "no -yTick")
+        }
+
+        mouseWheel(flickable, flickable.leftMargin, 0, -angleDelta, -angleDelta, Qt.NoButton, wheelHandler.pageScrollModifiers)
+        verify(flickable.contentX === Math.round(x + pageWidth * angleDeltaFactor), "+xPage")
+        x = flickable.contentX
+        verify(flickable.contentY === Math.round(y + pageHeight * angleDeltaFactor), "+yPage")
+        y = flickable.contentY
+
+        mouseWheel(flickable, flickable.leftMargin, 0, angleDelta, angleDelta, Qt.NoButton, wheelHandler.pageScrollModifiers)
+        verify(flickable.contentX === Math.round(x - pageWidth * angleDeltaFactor), "-xPage")
+        x = flickable.contentX
+        verify(flickable.contentY === Math.round(y - pageHeight * angleDeltaFactor), "-yPage")
+        y = flickable.contentY
+
+        if (Qt.platform.pluginName !== "xcb") {
+            mouseWheel(flickable, flickable.leftMargin, 0, 0, -angleDelta, Qt.NoButton,
+                    Qt.AltModifier | wheelHandler.pageScrollModifiers)
+            verify(flickable.contentX === Math.round(x + pageWidth * angleDeltaFactor), "+h_yPage")
+            x = flickable.contentX
+            verify(flickable.contentY === y, "no +yPage")
+
+            mouseWheel(flickable, flickable.leftMargin, 0, 0, angleDelta, Qt.NoButton,
+                    Qt.AltModifier | wheelHandler.pageScrollModifiers)
+            verify(flickable.contentX === Math.round(x - pageWidth * angleDeltaFactor), "-h_yPage")
+            x = flickable.contentX
+            verify(flickable.contentY === y, "no -yPage")
+        }
+    }
+
+    function test_WheelScrolling() {
+        // HID 1bcf:08a0 Mouse
+        // Angle delta is 120, like most mice.
+        wheelScrolling()
+    }
+
+    function test_HiResWheelScrolling() {
+        // Logitech MX Master 3
+        // Main wheel angle delta is at least 16, plus multiples of 8 when scrolling faster.
+        wheelScrolling(16)
+    }
+
+    function test_TouchpadScrolling() {
+        // UNIW0001:00 093A:0255 Touchpad
+        // 2 finger scroll angle delta is at least 3, but larger increments are used when scrolling faster.
+        wheelScrolling(3)
+    }
+
+    function keyboardScrolling() {
+        const originalX = flickable.contentX
+        const originalY = flickable.contentY
+        let x = originalX
+        let y = originalY
+        keyClick(Qt.Key_Right)
+        verify(flickable.contentX === x + hstep, "Key_Right")
+        x = flickable.contentX
+
+        keyClick(Qt.Key_Left)
+        verify(flickable.contentX === x - hstep, "Key_Left")
+        x = flickable.contentX
+
+        keyClick(Qt.Key_Down)
+        verify(flickable.contentY === y + vstep, "Key_Down")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_Up)
+        verify(flickable.contentY === y - vstep, "Key_Up")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_PageDown)
+        verify(flickable.contentY === y + pageHeight, "Key_PageDown")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_PageUp)
+        verify(flickable.contentY === y - pageHeight, "Key_PageUp")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_End)
+        verify(flickable.contentY === contentHeight - pageHeight, "Key_End")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_Home)
+        verify(flickable.contentY === originalY, "Key_Home")
+        y = flickable.contentY
+
+        keyClick(Qt.Key_PageDown, Qt.AltModifier)
+        verify(flickable.contentX === x + pageWidth, "h_Key_PageDown")
+        x = flickable.contentX
+
+        keyClick(Qt.Key_PageUp, Qt.AltModifier)
+        verify(flickable.contentX === x - pageWidth, "h_Key_PageUp")
+        x = flickable.contentX
+
+        keyClick(Qt.Key_End, Qt.AltModifier)
+        verify(flickable.contentX === contentWidth - pageWidth, "h_Key_End")
+        x = flickable.contentX
+
+        keyClick(Qt.Key_Home, Qt.AltModifier)
+        verify(flickable.contentX === originalX, "h_Key_Home")
+    }
+
+    function test_KeyboardScrolling() {
+        keyboardScrolling()
+    }
+
+    function test_StepSize() {
+        // 101 is a value unlikely to be used by any user's combination of settings and hardware
+        wheelHandler.verticalStepSize = 101
+        wheelHandler.horizontalStepSize = 101
+        wheelScrolling()
+        keyboardScrolling()
+        // reset to default
+        wheelHandler.verticalStepSize = undefined
+        wheelHandler.horizontalStepSize = undefined
+        verify(wheelHandler.verticalStepSize == 20 * Qt.styleHints.wheelScrollLines, "default verticalStepSize")
+        verify(wheelHandler.horizontalStepSize == 20 * Qt.styleHints.wheelScrollLines, "default horizontalStepSize")
+    }
+
+    ScrollableFlickable {
+        id: flickable
+        focus: true
+        anchors.fill: parent
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+            keyNavigationEnabled: true
+        }
+    }
+}
diff --git a/config-OpenMP.h.cmake b/config-OpenMP.h.cmake
new file mode 100644 (file)
index 0000000..76f6d25
--- /dev/null
@@ -0,0 +1 @@
+#cmakedefine01 HAVE_OpenMP
diff --git a/docs/pics/BasicListItemReserve.svg b/docs/pics/BasicListItemReserve.svg
new file mode 100644 (file)
index 0000000..dac57c4
--- /dev/null
@@ -0,0 +1,24 @@
+<svg width="720" height="82" viewBox="0 0 720 82" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="360" height="41" fill="#EFF0F1"/>
+<rect x="12" y="16" width="14" height="1" fill="#DA4453"/>
+<rect x="14" y="28" width="10" height="1" fill="#DA4453"/>
+<rect x="16" y="13" width="6" height="1" fill="#DA4453"/>
+<rect x="14" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="23" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="21" y="14" width="1" height="1" fill="#DA4453"/>
+<rect x="16" y="14" width="1" height="1" fill="#DA4453"/>
+<path d="M40.164 25V16.432H41.244V24.04H44.988V25H40.164ZM48.749 18.46C49.533 18.46 50.113 18.632 50.489 18.976C50.865 19.32 51.053 19.868 51.053 20.62V25H50.285L50.081 24.088H50.033C49.849 24.32 49.657 24.516 49.457 24.676C49.265 24.828 49.041 24.94 48.785 25.012C48.537 25.084 48.233 25.12 47.873 25.12C47.489 25.12 47.141 25.052 46.829 24.916C46.525 24.78 46.285 24.572 46.109 24.292C45.933 24.004 45.845 23.644 45.845 23.212C45.845 22.572 46.097 22.08 46.601 21.736C47.105 21.384 47.881 21.192 48.929 21.16L50.021 21.124V20.74C50.021 20.204 49.905 19.832 49.673 19.624C49.441 19.416 49.113 19.312 48.689 19.312C48.353 19.312 48.033 19.364 47.729 19.468C47.425 19.564 47.141 19.676 46.877 19.804L46.553 19.012C46.833 18.86 47.165 18.732 47.549 18.628C47.933 18.516 48.333 18.46 48.749 18.46ZM50.009 21.856L49.061 21.892C48.261 21.924 47.705 22.052 47.393 22.276C47.089 22.5 46.937 22.816 46.937 23.224C46.937 23.584 47.045 23.848 47.261 24.016C47.485 24.184 47.769 24.268 48.113 24.268C48.657 24.268 49.109 24.12 49.469 23.824C49.829 23.52 50.009 23.056 50.009 22.432V21.856ZM54.0955 15.88V18.1C54.0955 18.372 54.0875 18.628 54.0715 18.868C54.0635 19.1 54.0515 19.284 54.0355 19.42H54.0955C54.2795 19.148 54.5315 18.92 54.8515 18.736C55.1715 18.552 55.5835 18.46 56.0875 18.46C56.8875 18.46 57.5275 18.74 58.0075 19.3C58.4955 19.852 58.7395 20.68 58.7395 21.784C58.7395 22.52 58.6275 23.136 58.4035 23.632C58.1875 24.128 57.8795 24.5 57.4795 24.748C57.0795 24.996 56.6155 25.12 56.0875 25.12C55.5835 25.12 55.1715 25.028 54.8515 24.844C54.5315 24.66 54.2795 24.44 54.0955 24.184H54.0115L53.7955 25H53.0395V15.88H54.0955ZM55.9075 19.336C55.4515 19.336 55.0915 19.424 54.8275 19.6C54.5635 19.768 54.3755 20.032 54.2635 20.392C54.1515 20.744 54.0955 21.196 54.0955 21.748V21.796C54.0955 22.588 54.2235 23.196 54.4795 23.62C54.7435 24.036 55.2275 24.244 55.9315 24.244C56.5075 24.244 56.9355 24.032 57.2155 23.608C57.5035 23.184 57.6475 22.572 57.6475 21.772C57.6475 20.964 57.5035 20.356 57.2155 19.948C56.9355 19.54 56.4995 19.336 55.9075 19.336ZM62.9063 18.448C63.4583 18.448 63.9303 18.568 64.3223 18.808C64.7223 19.048 65.0263 19.388 65.2343 19.828C65.4503 20.26 65.5583 20.768 65.5583 21.352V21.988H61.1543C61.1703 22.716 61.3543 23.272 61.7063 23.656C62.0663 24.032 62.5663 24.22 63.2063 24.22C63.6143 24.22 63.9743 24.184 64.2863 24.112C64.6063 24.032 64.9343 23.92 65.2703 23.776V24.7C64.9423 24.844 64.6183 24.948 64.2983 25.012C63.9783 25.084 63.5983 25.12 63.1583 25.12C62.5503 25.12 62.0103 24.996 61.5383 24.748C61.0743 24.5 60.7103 24.132 60.4463 23.644C60.1903 23.148 60.0623 22.544 60.0623 21.832C60.0623 21.128 60.1783 20.524 60.4103 20.02C60.6503 19.516 60.9823 19.128 61.4063 18.856C61.8383 18.584 62.3383 18.448 62.9063 18.448ZM62.8943 19.312C62.3903 19.312 61.9903 19.476 61.6943 19.804C61.4063 20.124 61.2343 20.572 61.1783 21.148H64.4543C64.4543 20.78 64.3983 20.46 64.2863 20.188C64.1743 19.916 64.0023 19.704 63.7703 19.552C63.5463 19.392 63.2543 19.312 62.8943 19.312ZM68.2518 25H67.1958V15.88H68.2518V25Z" fill="black"/>
+<rect y="41" width="360" height="41" fill="#EFF0F1"/>
+<path d="M10.164 66V57.432H11.244V65.04H14.988V66H10.164ZM18.749 59.46C19.533 59.46 20.113 59.632 20.489 59.976C20.865 60.32 21.053 60.868 21.053 61.62V66H20.285L20.081 65.088H20.033C19.849 65.32 19.657 65.516 19.457 65.676C19.265 65.828 19.041 65.94 18.785 66.012C18.537 66.084 18.233 66.12 17.873 66.12C17.489 66.12 17.141 66.052 16.829 65.916C16.525 65.78 16.285 65.572 16.109 65.292C15.933 65.004 15.845 64.644 15.845 64.212C15.845 63.572 16.097 63.08 16.601 62.736C17.105 62.384 17.881 62.192 18.929 62.16L20.021 62.124V61.74C20.021 61.204 19.905 60.832 19.673 60.624C19.441 60.416 19.113 60.312 18.689 60.312C18.353 60.312 18.033 60.364 17.729 60.468C17.425 60.564 17.141 60.676 16.877 60.804L16.553 60.012C16.833 59.86 17.165 59.732 17.549 59.628C17.933 59.516 18.333 59.46 18.749 59.46ZM20.009 62.856L19.061 62.892C18.261 62.924 17.705 63.052 17.393 63.276C17.089 63.5 16.937 63.816 16.937 64.224C16.937 64.584 17.045 64.848 17.261 65.016C17.485 65.184 17.769 65.268 18.113 65.268C18.657 65.268 19.109 65.12 19.469 64.824C19.829 64.52 20.009 64.056 20.009 63.432V62.856ZM24.0955 56.88V59.1C24.0955 59.372 24.0875 59.628 24.0715 59.868C24.0635 60.1 24.0515 60.284 24.0355 60.42H24.0955C24.2795 60.148 24.5315 59.92 24.8515 59.736C25.1715 59.552 25.5835 59.46 26.0875 59.46C26.8875 59.46 27.5275 59.74 28.0075 60.3C28.4955 60.852 28.7395 61.68 28.7395 62.784C28.7395 63.52 28.6275 64.136 28.4035 64.632C28.1875 65.128 27.8795 65.5 27.4795 65.748C27.0795 65.996 26.6155 66.12 26.0875 66.12C25.5835 66.12 25.1715 66.028 24.8515 65.844C24.5315 65.66 24.2795 65.44 24.0955 65.184H24.0115L23.7955 66H23.0395V56.88H24.0955ZM25.9075 60.336C25.4515 60.336 25.0915 60.424 24.8275 60.6C24.5635 60.768 24.3755 61.032 24.2635 61.392C24.1515 61.744 24.0955 62.196 24.0955 62.748V62.796C24.0955 63.588 24.2235 64.196 24.4795 64.62C24.7435 65.036 25.2275 65.244 25.9315 65.244C26.5075 65.244 26.9355 65.032 27.2155 64.608C27.5035 64.184 27.6475 63.572 27.6475 62.772C27.6475 61.964 27.5035 61.356 27.2155 60.948C26.9355 60.54 26.4995 60.336 25.9075 60.336ZM32.9063 59.448C33.4583 59.448 33.9303 59.568 34.3223 59.808C34.7223 60.048 35.0263 60.388 35.2343 60.828C35.4503 61.26 35.5583 61.768 35.5583 62.352V62.988H31.1543C31.1703 63.716 31.3543 64.272 31.7063 64.656C32.0663 65.032 32.5663 65.22 33.2063 65.22C33.6143 65.22 33.9743 65.184 34.2863 65.112C34.6063 65.032 34.9343 64.92 35.2703 64.776V65.7C34.9423 65.844 34.6183 65.948 34.2983 66.012C33.9783 66.084 33.5983 66.12 33.1583 66.12C32.5503 66.12 32.0103 65.996 31.5383 65.748C31.0743 65.5 30.7103 65.132 30.4463 64.644C30.1903 64.148 30.0623 63.544 30.0623 62.832C30.0623 62.128 30.1783 61.524 30.4103 61.02C30.6503 60.516 30.9823 60.128 31.4063 59.856C31.8383 59.584 32.3383 59.448 32.9063 59.448ZM32.8943 60.312C32.3903 60.312 31.9903 60.476 31.6943 60.804C31.4063 61.124 31.2343 61.572 31.1783 62.148H34.4543C34.4543 61.78 34.3983 61.46 34.2863 61.188C34.1743 60.916 34.0023 60.704 33.7703 60.552C33.5463 60.392 33.2543 60.312 32.8943 60.312ZM38.2518 66H37.1958V56.88H38.2518V66Z" fill="black"/>
+<rect x="360" width="360" height="41" fill="#EFF0F1"/>
+<rect x="372" y="16" width="14" height="1" fill="#DA4453"/>
+<rect x="374" y="28" width="10" height="1" fill="#DA4453"/>
+<rect x="376" y="13" width="6" height="1" fill="#DA4453"/>
+<rect x="374" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="383" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="381" y="14" width="1" height="1" fill="#DA4453"/>
+<rect x="376" y="14" width="1" height="1" fill="#DA4453"/>
+<path d="M400.164 25V16.432H401.244V24.04H404.988V25H400.164ZM408.749 18.46C409.533 18.46 410.113 18.632 410.489 18.976C410.865 19.32 411.053 19.868 411.053 20.62V25H410.285L410.081 24.088H410.033C409.849 24.32 409.657 24.516 409.457 24.676C409.265 24.828 409.041 24.94 408.785 25.012C408.537 25.084 408.233 25.12 407.873 25.12C407.489 25.12 407.141 25.052 406.829 24.916C406.525 24.78 406.285 24.572 406.109 24.292C405.933 24.004 405.845 23.644 405.845 23.212C405.845 22.572 406.097 22.08 406.601 21.736C407.105 21.384 407.881 21.192 408.929 21.16L410.021 21.124V20.74C410.021 20.204 409.905 19.832 409.673 19.624C409.441 19.416 409.113 19.312 408.689 19.312C408.353 19.312 408.033 19.364 407.729 19.468C407.425 19.564 407.141 19.676 406.877 19.804L406.553 19.012C406.833 18.86 407.165 18.732 407.549 18.628C407.933 18.516 408.333 18.46 408.749 18.46ZM410.009 21.856L409.061 21.892C408.261 21.924 407.705 22.052 407.393 22.276C407.089 22.5 406.937 22.816 406.937 23.224C406.937 23.584 407.045 23.848 407.261 24.016C407.485 24.184 407.769 24.268 408.113 24.268C408.657 24.268 409.109 24.12 409.469 23.824C409.829 23.52 410.009 23.056 410.009 22.432V21.856ZM414.096 15.88V18.1C414.096 18.372 414.088 18.628 414.072 18.868C414.064 19.1 414.052 19.284 414.036 19.42H414.096C414.28 19.148 414.532 18.92 414.852 18.736C415.172 18.552 415.584 18.46 416.088 18.46C416.888 18.46 417.528 18.74 418.008 19.3C418.496 19.852 418.74 20.68 418.74 21.784C418.74 22.52 418.628 23.136 418.404 23.632C418.188 24.128 417.88 24.5 417.48 24.748C417.08 24.996 416.616 25.12 416.088 25.12C415.584 25.12 415.172 25.028 414.852 24.844C414.532 24.66 414.28 24.44 414.096 24.184H414.012L413.796 25H413.04V15.88H414.096ZM415.908 19.336C415.452 19.336 415.092 19.424 414.828 19.6C414.564 19.768 414.376 20.032 414.264 20.392C414.152 20.744 414.096 21.196 414.096 21.748V21.796C414.096 22.588 414.224 23.196 414.48 23.62C414.744 24.036 415.228 24.244 415.932 24.244C416.508 24.244 416.936 24.032 417.216 23.608C417.504 23.184 417.648 22.572 417.648 21.772C417.648 20.964 417.504 20.356 417.216 19.948C416.936 19.54 416.5 19.336 415.908 19.336ZM422.906 18.448C423.458 18.448 423.93 18.568 424.322 18.808C424.722 19.048 425.026 19.388 425.234 19.828C425.45 20.26 425.558 20.768 425.558 21.352V21.988H421.154C421.17 22.716 421.354 23.272 421.706 23.656C422.066 24.032 422.566 24.22 423.206 24.22C423.614 24.22 423.974 24.184 424.286 24.112C424.606 24.032 424.934 23.92 425.27 23.776V24.7C424.942 24.844 424.618 24.948 424.298 25.012C423.978 25.084 423.598 25.12 423.158 25.12C422.55 25.12 422.01 24.996 421.538 24.748C421.074 24.5 420.71 24.132 420.446 23.644C420.19 23.148 420.062 22.544 420.062 21.832C420.062 21.128 420.178 20.524 420.41 20.02C420.65 19.516 420.982 19.128 421.406 18.856C421.838 18.584 422.338 18.448 422.906 18.448ZM422.894 19.312C422.39 19.312 421.99 19.476 421.694 19.804C421.406 20.124 421.234 20.572 421.178 21.148H424.454C424.454 20.78 424.398 20.46 424.286 20.188C424.174 19.916 424.002 19.704 423.77 19.552C423.546 19.392 423.254 19.312 422.894 19.312ZM428.252 25H427.196V15.88H428.252V25Z" fill="black"/>
+<rect x="360" y="41" width="360" height="41" fill="#EFF0F1"/>
+<path d="M400.164 66V57.432H401.244V65.04H404.988V66H400.164ZM408.749 59.46C409.533 59.46 410.113 59.632 410.489 59.976C410.865 60.32 411.053 60.868 411.053 61.62V66H410.285L410.081 65.088H410.033C409.849 65.32 409.657 65.516 409.457 65.676C409.265 65.828 409.041 65.94 408.785 66.012C408.537 66.084 408.233 66.12 407.873 66.12C407.489 66.12 407.141 66.052 406.829 65.916C406.525 65.78 406.285 65.572 406.109 65.292C405.933 65.004 405.845 64.644 405.845 64.212C405.845 63.572 406.097 63.08 406.601 62.736C407.105 62.384 407.881 62.192 408.929 62.16L410.021 62.124V61.74C410.021 61.204 409.905 60.832 409.673 60.624C409.441 60.416 409.113 60.312 408.689 60.312C408.353 60.312 408.033 60.364 407.729 60.468C407.425 60.564 407.141 60.676 406.877 60.804L406.553 60.012C406.833 59.86 407.165 59.732 407.549 59.628C407.933 59.516 408.333 59.46 408.749 59.46ZM410.009 62.856L409.061 62.892C408.261 62.924 407.705 63.052 407.393 63.276C407.089 63.5 406.937 63.816 406.937 64.224C406.937 64.584 407.045 64.848 407.261 65.016C407.485 65.184 407.769 65.268 408.113 65.268C408.657 65.268 409.109 65.12 409.469 64.824C409.829 64.52 410.009 64.056 410.009 63.432V62.856ZM414.096 56.88V59.1C414.096 59.372 414.088 59.628 414.072 59.868C414.064 60.1 414.052 60.284 414.036 60.42H414.096C414.28 60.148 414.532 59.92 414.852 59.736C415.172 59.552 415.584 59.46 416.088 59.46C416.888 59.46 417.528 59.74 418.008 60.3C418.496 60.852 418.74 61.68 418.74 62.784C418.74 63.52 418.628 64.136 418.404 64.632C418.188 65.128 417.88 65.5 417.48 65.748C417.08 65.996 416.616 66.12 416.088 66.12C415.584 66.12 415.172 66.028 414.852 65.844C414.532 65.66 414.28 65.44 414.096 65.184H414.012L413.796 66H413.04V56.88H414.096ZM415.908 60.336C415.452 60.336 415.092 60.424 414.828 60.6C414.564 60.768 414.376 61.032 414.264 61.392C414.152 61.744 414.096 62.196 414.096 62.748V62.796C414.096 63.588 414.224 64.196 414.48 64.62C414.744 65.036 415.228 65.244 415.932 65.244C416.508 65.244 416.936 65.032 417.216 64.608C417.504 64.184 417.648 63.572 417.648 62.772C417.648 61.964 417.504 61.356 417.216 60.948C416.936 60.54 416.5 60.336 415.908 60.336ZM422.906 59.448C423.458 59.448 423.93 59.568 424.322 59.808C424.722 60.048 425.026 60.388 425.234 60.828C425.45 61.26 425.558 61.768 425.558 62.352V62.988H421.154C421.17 63.716 421.354 64.272 421.706 64.656C422.066 65.032 422.566 65.22 423.206 65.22C423.614 65.22 423.974 65.184 424.286 65.112C424.606 65.032 424.934 64.92 425.27 64.776V65.7C424.942 65.844 424.618 65.948 424.298 66.012C423.978 66.084 423.598 66.12 423.158 66.12C422.55 66.12 422.01 65.996 421.538 65.748C421.074 65.5 420.71 65.132 420.446 64.644C420.19 64.148 420.062 63.544 420.062 62.832C420.062 62.128 420.178 61.524 420.41 61.02C420.65 60.516 420.982 60.128 421.406 59.856C421.838 59.584 422.338 59.448 422.906 59.448ZM422.894 60.312C422.39 60.312 421.99 60.476 421.694 60.804C421.406 61.124 421.234 61.572 421.178 62.148H424.454C424.454 61.78 424.398 61.46 424.286 61.188C424.174 60.916 424.002 60.704 423.77 60.552C423.546 60.392 423.254 60.312 422.894 60.312ZM428.252 66H427.196V56.88H428.252V66Z" fill="black"/>
+</svg>
diff --git a/docs/pics/BasicListItemTypes.svg b/docs/pics/BasicListItemTypes.svg
new file mode 100644 (file)
index 0000000..3d38b1d
--- /dev/null
@@ -0,0 +1,23 @@
+<svg width="360" height="123" viewBox="0 0 360 123" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="360" height="41" fill="#EFF0F1"/>
+<rect x="12" y="16" width="14" height="1" fill="#DA4453"/>
+<rect x="14" y="28" width="10" height="1" fill="#DA4453"/>
+<rect x="16" y="13" width="6" height="1" fill="#DA4453"/>
+<rect x="14" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="23" y="18" width="1" height="10" fill="#DA4453"/>
+<rect x="21" y="14" width="1" height="1" fill="#DA4453"/>
+<rect x="16" y="14" width="1" height="1" fill="#DA4453"/>
+<path d="M43.188 18H41.652V10.728H39.192V9.432H45.648V10.728H43.188V18ZM47.5718 8.988C47.8038 8.988 47.9998 9.048 48.1598 9.168C48.3278 9.28 48.4118 9.484 48.4118 9.78C48.4118 10.076 48.3278 10.284 48.1598 10.404C47.9998 10.524 47.8038 10.584 47.5718 10.584C47.3398 10.584 47.1398 10.524 46.9718 10.404C46.8118 10.284 46.7318 10.076 46.7318 9.78C46.7318 9.484 46.8118 9.28 46.9718 9.168C47.1398 9.048 47.3398 8.988 47.5718 8.988ZM48.3278 11.496V18H46.8158V11.496H48.3278ZM52.7811 16.908C52.9651 16.908 53.1451 16.892 53.3211 16.86C53.5051 16.82 53.6731 16.776 53.8251 16.728V17.856C53.6651 17.928 53.4571 17.988 53.2011 18.036C52.9451 18.092 52.6771 18.12 52.3971 18.12C52.0291 18.12 51.6931 18.06 51.3891 17.94C51.0851 17.812 50.8451 17.596 50.6691 17.292C50.4931 16.988 50.4051 16.564 50.4051 16.02V12.636H49.5291V11.976L50.4891 11.448L50.9691 10.068H51.9171V11.496H53.7651V12.636H51.9171V16.008C51.9171 16.312 51.9971 16.54 52.1571 16.692C52.3171 16.836 52.5251 16.908 52.7811 16.908ZM56.6481 18H55.1361V8.88H56.6481V18ZM61.1974 11.376C62.0694 11.376 62.7574 11.636 63.2614 12.156C63.7734 12.676 64.0294 13.4 64.0294 14.328V15.096H59.7334C59.7494 15.688 59.9134 16.148 60.2254 16.476C60.5374 16.796 60.9694 16.956 61.5214 16.956C61.9374 16.956 62.3134 16.916 62.6494 16.836C62.9854 16.748 63.3294 16.624 63.6814 16.464V17.676C63.3614 17.828 63.0294 17.94 62.6854 18.012C62.3414 18.084 61.9294 18.12 61.4494 18.12C60.8174 18.12 60.2534 18 59.7574 17.76C59.2694 17.512 58.8854 17.14 58.6054 16.644C58.3334 16.148 58.1974 15.532 58.1974 14.796C58.1974 13.684 58.4734 12.836 59.0254 12.252C59.5774 11.668 60.3014 11.376 61.1974 11.376ZM61.1974 12.492C60.7894 12.492 60.4574 12.624 60.2014 12.888C59.9534 13.144 59.8094 13.528 59.7694 14.04H62.5654C62.5574 13.592 62.4414 13.224 62.2174 12.936C62.0014 12.64 61.6614 12.492 61.1974 12.492ZM68.1792 14.712C68.1792 13.736 68.3192 12.796 68.5992 11.892C68.8872 10.98 69.3352 10.16 69.9432 9.432H71.2272C70.6752 10.184 70.2592 11.012 69.9792 11.916C69.6992 12.82 69.5592 13.748 69.5592 14.7C69.5592 15.628 69.6992 16.54 69.9792 17.436C70.2592 18.332 70.6712 19.152 71.2152 19.896H69.9432C69.3352 19.192 68.8872 18.396 68.5992 17.508C68.3192 16.612 68.1792 15.68 68.1792 14.712ZM72.7058 18V9.432H74.2418V16.704H77.8178V18H72.7058ZM81.7515 11.376C82.5915 11.376 83.2275 11.56 83.6595 11.928C84.0995 12.296 84.3195 12.864 84.3195 13.632V18H83.2515L82.9515 17.1H82.9035C82.6235 17.452 82.3275 17.712 82.0155 17.88C81.7035 18.04 81.2795 18.12 80.7435 18.12C80.1595 18.12 79.6755 17.956 79.2915 17.628C78.9075 17.3 78.7155 16.792 78.7155 16.104C78.7155 15.432 78.9595 14.928 79.4475 14.592C79.9435 14.256 80.6915 14.068 81.6915 14.028L82.8195 13.992V13.668C82.8195 13.26 82.7195 12.968 82.5195 12.792C82.3195 12.608 82.0395 12.516 81.6795 12.516C81.3515 12.516 81.0355 12.564 80.7315 12.66C80.4355 12.756 80.1395 12.868 79.8435 12.996L79.3635 11.94C79.6835 11.772 80.0475 11.636 80.4555 11.532C80.8715 11.428 81.3035 11.376 81.7515 11.376ZM82.8195 14.928L82.0275 14.952C81.3635 14.976 80.9035 15.092 80.6475 15.3C80.3915 15.5 80.2635 15.772 80.2635 16.116C80.2635 16.428 80.3515 16.652 80.5275 16.788C80.7115 16.916 80.9515 16.98 81.2475 16.98C81.6875 16.98 82.0595 16.856 82.3635 16.608C82.6675 16.352 82.8195 15.976 82.8195 15.48V14.928ZM87.7262 8.88V11.04C87.7262 11.296 87.7182 11.544 87.7022 11.784C87.6862 12.024 87.6702 12.212 87.6542 12.348H87.7262C87.9022 12.076 88.1422 11.848 88.4462 11.664C88.7502 11.472 89.1462 11.376 89.6342 11.376C90.3862 11.376 90.9942 11.66 91.4582 12.228C91.9302 12.796 92.1662 13.632 92.1662 14.736C92.1662 15.856 91.9302 16.7 91.4582 17.268C90.9862 17.836 90.3662 18.12 89.5982 18.12C89.1102 18.12 88.7182 18.032 88.4222 17.856C88.1342 17.68 87.9022 17.476 87.7262 17.244H87.6182L87.3422 18H86.2142V8.88H87.7262ZM89.2022 12.6C88.6582 12.6 88.2782 12.764 88.0622 13.092C87.8462 13.42 87.7342 13.924 87.7262 14.604V14.736C87.7262 15.432 87.8302 15.964 88.0382 16.332C88.2542 16.7 88.6502 16.884 89.2262 16.884C89.6742 16.884 90.0182 16.7 90.2582 16.332C90.4982 15.956 90.6182 15.42 90.6182 14.724C90.6182 14.012 90.4942 13.48 90.2462 13.128C90.0062 12.776 89.6582 12.6 89.2022 12.6ZM96.3419 11.376C97.2139 11.376 97.9019 11.636 98.4059 12.156C98.9179 12.676 99.1739 13.4 99.1739 14.328V15.096H94.8779C94.8939 15.688 95.0579 16.148 95.3699 16.476C95.6819 16.796 96.1139 16.956 96.6659 16.956C97.0819 16.956 97.4579 16.916 97.7939 16.836C98.1299 16.748 98.4739 16.624 98.8259 16.464V17.676C98.5059 17.828 98.1739 17.94 97.8299 18.012C97.4859 18.084 97.0739 18.12 96.5939 18.12C95.9619 18.12 95.3979 18 94.9019 17.76C94.4139 17.512 94.0299 17.14 93.7499 16.644C93.4779 16.148 93.3419 15.532 93.3419 14.796C93.3419 13.684 93.6179 12.836 94.1699 12.252C94.7219 11.668 95.4459 11.376 96.3419 11.376ZM96.3419 12.492C95.9339 12.492 95.6019 12.624 95.3459 12.888C95.0979 13.144 94.9539 13.528 94.9139 14.04H97.7099C97.7019 13.592 97.5859 13.224 97.3619 12.936C97.1459 12.64 96.8059 12.492 96.3419 12.492ZM102.211 18H100.699V8.88H102.211V18ZM106.58 14.712C106.58 15.68 106.436 16.612 106.148 17.508C105.868 18.396 105.424 19.192 104.816 19.896H103.544C104.088 19.152 104.5 18.332 104.78 17.436C105.068 16.54 105.212 15.628 105.212 14.7C105.212 13.748 105.068 12.82 104.78 11.916C104.5 11.012 104.084 10.184 103.532 9.432H104.816C105.424 10.16 105.868 10.98 106.148 11.892C106.436 12.796 106.58 13.736 106.58 14.712Z" fill="black"/>
+<path d="M45.024 31.708C45.024 32.22 44.9 32.656 44.652 33.016C44.404 33.368 44.048 33.64 43.584 33.832C43.128 34.024 42.588 34.12 41.964 34.12C41.644 34.12 41.336 34.104 41.04 34.072C40.752 34.04 40.488 33.996 40.248 33.94C40.008 33.876 39.796 33.8 39.612 33.712V32.68C39.9 32.808 40.256 32.924 40.68 33.028C41.112 33.132 41.556 33.184 42.012 33.184C42.436 33.184 42.792 33.128 43.08 33.016C43.368 32.904 43.584 32.744 43.728 32.536C43.872 32.328 43.944 32.084 43.944 31.804C43.944 31.524 43.884 31.288 43.764 31.096C43.644 30.904 43.436 30.728 43.14 30.568C42.852 30.4 42.448 30.224 41.928 30.04C41.56 29.904 41.236 29.76 40.956 29.608C40.684 29.448 40.456 29.268 40.272 29.068C40.088 28.868 39.948 28.64 39.852 28.384C39.764 28.128 39.72 27.832 39.72 27.496C39.72 27.04 39.836 26.652 40.068 26.332C40.3 26.004 40.62 25.752 41.028 25.576C41.444 25.4 41.92 25.312 42.456 25.312C42.928 25.312 43.36 25.356 43.752 25.444C44.144 25.532 44.5 25.648 44.82 25.792L44.484 26.716C44.188 26.588 43.864 26.48 43.512 26.392C43.168 26.304 42.808 26.26 42.432 26.26C42.072 26.26 41.772 26.312 41.532 26.416C41.292 26.52 41.112 26.668 40.992 26.86C40.872 27.044 40.812 27.26 40.812 27.508C40.812 27.796 40.872 28.036 40.992 28.228C41.112 28.42 41.308 28.592 41.58 28.744C41.852 28.896 42.22 29.06 42.684 29.236C43.188 29.42 43.612 29.62 43.956 29.836C44.308 30.044 44.572 30.296 44.748 30.592C44.932 30.888 45.024 31.26 45.024 31.708ZM51.9819 27.568V34H51.1179L50.9619 33.148H50.9139C50.7779 33.372 50.6019 33.556 50.3859 33.7C50.1699 33.844 49.9339 33.948 49.6779 34.012C49.4219 34.084 49.1539 34.12 48.8739 34.12C48.3619 34.12 47.9299 34.04 47.5779 33.88C47.2339 33.712 46.9739 33.456 46.7979 33.112C46.6219 32.768 46.5339 32.324 46.5339 31.78V27.568H47.6019V31.708C47.6019 32.22 47.7179 32.604 47.9499 32.86C48.1819 33.116 48.5419 33.244 49.0299 33.244C49.5099 33.244 49.8859 33.156 50.1579 32.98C50.4379 32.796 50.6339 32.532 50.7459 32.188C50.8659 31.836 50.9259 31.412 50.9259 30.916V27.568H51.9819ZM55.0799 24.88V27.1C55.0799 27.372 55.0719 27.628 55.0559 27.868C55.0479 28.1 55.0359 28.284 55.0199 28.42H55.0799C55.2639 28.148 55.5159 27.92 55.8359 27.736C56.1559 27.552 56.5679 27.46 57.0719 27.46C57.8719 27.46 58.5119 27.74 58.9919 28.3C59.4799 28.852 59.7239 29.68 59.7239 30.784C59.7239 31.52 59.6119 32.136 59.3879 32.632C59.1719 33.128 58.8639 33.5 58.4639 33.748C58.0639 33.996 57.5999 34.12 57.0719 34.12C56.5679 34.12 56.1559 34.028 55.8359 33.844C55.5159 33.66 55.2639 33.44 55.0799 33.184H54.9959L54.7799 34H54.0239V24.88H55.0799ZM56.8919 28.336C56.4359 28.336 56.0759 28.424 55.8119 28.6C55.5479 28.768 55.3599 29.032 55.2479 29.392C55.1359 29.744 55.0799 30.196 55.0799 30.748V30.796C55.0799 31.588 55.2079 32.196 55.4639 32.62C55.7279 33.036 56.2119 33.244 56.9159 33.244C57.4919 33.244 57.9199 33.032 58.1999 32.608C58.4879 32.184 58.6319 31.572 58.6319 30.772C58.6319 29.964 58.4879 29.356 58.1999 28.948C57.9199 28.54 57.4839 28.336 56.8919 28.336ZM63.5547 33.256C63.7147 33.256 63.8787 33.244 64.0467 33.22C64.2147 33.188 64.3507 33.156 64.4547 33.124V33.928C64.3427 33.984 64.1827 34.028 63.9747 34.06C63.7667 34.1 63.5667 34.12 63.3747 34.12C63.0387 34.12 62.7267 34.064 62.4387 33.952C62.1587 33.832 61.9307 33.628 61.7547 33.34C61.5787 33.052 61.4907 32.648 61.4907 32.128V28.384H60.5787V27.88L61.5027 27.46L61.9227 26.092H62.5467V27.568H64.4067V28.384H62.5467V32.104C62.5467 32.496 62.6387 32.788 62.8227 32.98C63.0147 33.164 63.2587 33.256 63.5547 33.256ZM66.7987 27.568V34H65.7427V27.568H66.7987ZM66.2827 25.156C66.4427 25.156 66.5827 25.212 66.7027 25.324C66.8307 25.428 66.8947 25.596 66.8947 25.828C66.8947 26.052 66.8307 26.22 66.7027 26.332C66.5827 26.444 66.4427 26.5 66.2827 26.5C66.1067 26.5 65.9587 26.444 65.8387 26.332C65.7187 26.22 65.6587 26.052 65.6587 25.828C65.6587 25.596 65.7187 25.428 65.8387 25.324C65.9587 25.212 66.1067 25.156 66.2827 25.156ZM70.9844 33.256C71.1444 33.256 71.3084 33.244 71.4764 33.22C71.6444 33.188 71.7804 33.156 71.8844 33.124V33.928C71.7724 33.984 71.6124 34.028 71.4044 34.06C71.1964 34.1 70.9964 34.12 70.8044 34.12C70.4684 34.12 70.1564 34.064 69.8684 33.952C69.5884 33.832 69.3604 33.628 69.1844 33.34C69.0084 33.052 68.9204 32.648 68.9204 32.128V28.384H68.0084V27.88L68.9324 27.46L69.3524 26.092H69.9764V27.568H71.8364V28.384H69.9764V32.104C69.9764 32.496 70.0684 32.788 70.2524 32.98C70.4444 33.164 70.6884 33.256 70.9844 33.256ZM74.2283 34H73.1723V24.88H74.2283V34ZM78.7501 27.448C79.3021 27.448 79.7741 27.568 80.1661 27.808C80.5661 28.048 80.8701 28.388 81.0781 28.828C81.2941 29.26 81.4021 29.768 81.4021 30.352V30.988H76.9981C77.0141 31.716 77.1981 32.272 77.5501 32.656C77.9101 33.032 78.4101 33.22 79.0501 33.22C79.4581 33.22 79.8181 33.184 80.1301 33.112C80.4501 33.032 80.7781 32.92 81.1141 32.776V33.7C80.7861 33.844 80.4621 33.948 80.1421 34.012C79.8221 34.084 79.4421 34.12 79.0021 34.12C78.3941 34.12 77.8541 33.996 77.3821 33.748C76.9181 33.5 76.5541 33.132 76.2901 32.644C76.0341 32.148 75.9061 31.544 75.9061 30.832C75.9061 30.128 76.0221 29.524 76.2541 29.02C76.4941 28.516 76.8261 28.128 77.2501 27.856C77.6821 27.584 78.1821 27.448 78.7501 27.448ZM78.7381 28.312C78.2341 28.312 77.8341 28.476 77.5381 28.804C77.2501 29.124 77.0781 29.572 77.0221 30.148H80.2981C80.2981 29.78 80.2421 29.46 80.1301 29.188C80.0181 28.916 79.8461 28.704 79.6141 28.552C79.3901 28.392 79.0981 28.312 78.7381 28.312Z" fill="black"/>
+<rect y="41" width="360" height="41" fill="#EFF0F1"/>
+<rect x="12" y="57" width="14" height="1" fill="#DA4453"/>
+<rect x="14" y="69" width="10" height="1" fill="#DA4453"/>
+<rect x="16" y="54" width="6" height="1" fill="#DA4453"/>
+<rect x="14" y="59" width="1" height="10" fill="#DA4453"/>
+<rect x="23" y="59" width="1" height="10" fill="#DA4453"/>
+<rect x="21" y="55" width="1" height="1" fill="#DA4453"/>
+<rect x="16" y="55" width="1" height="1" fill="#DA4453"/>
+<path d="M40.164 66V57.432H41.244V65.04H44.988V66H40.164ZM48.749 59.46C49.533 59.46 50.113 59.632 50.489 59.976C50.865 60.32 51.053 60.868 51.053 61.62V66H50.285L50.081 65.088H50.033C49.849 65.32 49.657 65.516 49.457 65.676C49.265 65.828 49.041 65.94 48.785 66.012C48.537 66.084 48.233 66.12 47.873 66.12C47.489 66.12 47.141 66.052 46.829 65.916C46.525 65.78 46.285 65.572 46.109 65.292C45.933 65.004 45.845 64.644 45.845 64.212C45.845 63.572 46.097 63.08 46.601 62.736C47.105 62.384 47.881 62.192 48.929 62.16L50.021 62.124V61.74C50.021 61.204 49.905 60.832 49.673 60.624C49.441 60.416 49.113 60.312 48.689 60.312C48.353 60.312 48.033 60.364 47.729 60.468C47.425 60.564 47.141 60.676 46.877 60.804L46.553 60.012C46.833 59.86 47.165 59.732 47.549 59.628C47.933 59.516 48.333 59.46 48.749 59.46ZM50.009 62.856L49.061 62.892C48.261 62.924 47.705 63.052 47.393 63.276C47.089 63.5 46.937 63.816 46.937 64.224C46.937 64.584 47.045 64.848 47.261 65.016C47.485 65.184 47.769 65.268 48.113 65.268C48.657 65.268 49.109 65.12 49.469 64.824C49.829 64.52 50.009 64.056 50.009 63.432V62.856ZM54.0955 56.88V59.1C54.0955 59.372 54.0875 59.628 54.0715 59.868C54.0635 60.1 54.0515 60.284 54.0355 60.42H54.0955C54.2795 60.148 54.5315 59.92 54.8515 59.736C55.1715 59.552 55.5835 59.46 56.0875 59.46C56.8875 59.46 57.5275 59.74 58.0075 60.3C58.4955 60.852 58.7395 61.68 58.7395 62.784C58.7395 63.52 58.6275 64.136 58.4035 64.632C58.1875 65.128 57.8795 65.5 57.4795 65.748C57.0795 65.996 56.6155 66.12 56.0875 66.12C55.5835 66.12 55.1715 66.028 54.8515 65.844C54.5315 65.66 54.2795 65.44 54.0955 65.184H54.0115L53.7955 66H53.0395V56.88H54.0955ZM55.9075 60.336C55.4515 60.336 55.0915 60.424 54.8275 60.6C54.5635 60.768 54.3755 61.032 54.2635 61.392C54.1515 61.744 54.0955 62.196 54.0955 62.748V62.796C54.0955 63.588 54.2235 64.196 54.4795 64.62C54.7435 65.036 55.2275 65.244 55.9315 65.244C56.5075 65.244 56.9355 65.032 57.2155 64.608C57.5035 64.184 57.6475 63.572 57.6475 62.772C57.6475 61.964 57.5035 61.356 57.2155 60.948C56.9355 60.54 56.4995 60.336 55.9075 60.336ZM62.9063 59.448C63.4583 59.448 63.9303 59.568 64.3223 59.808C64.7223 60.048 65.0263 60.388 65.2343 60.828C65.4503 61.26 65.5583 61.768 65.5583 62.352V62.988H61.1543C61.1703 63.716 61.3543 64.272 61.7063 64.656C62.0663 65.032 62.5663 65.22 63.2063 65.22C63.6143 65.22 63.9743 65.184 64.2863 65.112C64.6063 65.032 64.9343 64.92 65.2703 64.776V65.7C64.9423 65.844 64.6183 65.948 64.2983 66.012C63.9783 66.084 63.5983 66.12 63.1583 66.12C62.5503 66.12 62.0103 65.996 61.5383 65.748C61.0743 65.5 60.7103 65.132 60.4463 64.644C60.1903 64.148 60.0623 63.544 60.0623 62.832C60.0623 62.128 60.1783 61.524 60.4103 61.02C60.6503 60.516 60.9823 60.128 61.4063 59.856C61.8383 59.584 62.3383 59.448 62.9063 59.448ZM62.8943 60.312C62.3903 60.312 61.9903 60.476 61.6943 60.804C61.4063 61.124 61.2343 61.572 61.1783 62.148H64.4543C64.4543 61.78 64.3983 61.46 64.2863 61.188C64.1743 60.916 64.0023 60.704 63.7703 60.552C63.5463 60.392 63.2543 60.312 62.8943 60.312ZM68.2518 66H67.1958V56.88H68.2518V66Z" fill="black"/>
+<rect y="82" width="360" height="41" fill="#EFF0F1"/>
+<path d="M10.164 107V98.432H11.244V106.04H14.988V107H10.164ZM18.749 100.46C19.533 100.46 20.113 100.632 20.489 100.976C20.865 101.32 21.053 101.868 21.053 102.62V107H20.285L20.081 106.088H20.033C19.849 106.32 19.657 106.516 19.457 106.676C19.265 106.828 19.041 106.94 18.785 107.012C18.537 107.084 18.233 107.12 17.873 107.12C17.489 107.12 17.141 107.052 16.829 106.916C16.525 106.78 16.285 106.572 16.109 106.292C15.933 106.004 15.845 105.644 15.845 105.212C15.845 104.572 16.097 104.08 16.601 103.736C17.105 103.384 17.881 103.192 18.929 103.16L20.021 103.124V102.74C20.021 102.204 19.905 101.832 19.673 101.624C19.441 101.416 19.113 101.312 18.689 101.312C18.353 101.312 18.033 101.364 17.729 101.468C17.425 101.564 17.141 101.676 16.877 101.804L16.553 101.012C16.833 100.86 17.165 100.732 17.549 100.628C17.933 100.516 18.333 100.46 18.749 100.46ZM20.009 103.856L19.061 103.892C18.261 103.924 17.705 104.052 17.393 104.276C17.089 104.5 16.937 104.816 16.937 105.224C16.937 105.584 17.045 105.848 17.261 106.016C17.485 106.184 17.769 106.268 18.113 106.268C18.657 106.268 19.109 106.12 19.469 105.824C19.829 105.52 20.009 105.056 20.009 104.432V103.856ZM24.0955 97.88V100.1C24.0955 100.372 24.0875 100.628 24.0715 100.868C24.0635 101.1 24.0515 101.284 24.0355 101.42H24.0955C24.2795 101.148 24.5315 100.92 24.8515 100.736C25.1715 100.552 25.5835 100.46 26.0875 100.46C26.8875 100.46 27.5275 100.74 28.0075 101.3C28.4955 101.852 28.7395 102.68 28.7395 103.784C28.7395 104.52 28.6275 105.136 28.4035 105.632C28.1875 106.128 27.8795 106.5 27.4795 106.748C27.0795 106.996 26.6155 107.12 26.0875 107.12C25.5835 107.12 25.1715 107.028 24.8515 106.844C24.5315 106.66 24.2795 106.44 24.0955 106.184H24.0115L23.7955 107H23.0395V97.88H24.0955ZM25.9075 101.336C25.4515 101.336 25.0915 101.424 24.8275 101.6C24.5635 101.768 24.3755 102.032 24.2635 102.392C24.1515 102.744 24.0955 103.196 24.0955 103.748V103.796C24.0955 104.588 24.2235 105.196 24.4795 105.62C24.7435 106.036 25.2275 106.244 25.9315 106.244C26.5075 106.244 26.9355 106.032 27.2155 105.608C27.5035 105.184 27.6475 104.572 27.6475 103.772C27.6475 102.964 27.5035 102.356 27.2155 101.948C26.9355 101.54 26.4995 101.336 25.9075 101.336ZM32.9063 100.448C33.4583 100.448 33.9303 100.568 34.3223 100.808C34.7223 101.048 35.0263 101.388 35.2343 101.828C35.4503 102.26 35.5583 102.768 35.5583 103.352V103.988H31.1543C31.1703 104.716 31.3543 105.272 31.7063 105.656C32.0663 106.032 32.5663 106.22 33.2063 106.22C33.6143 106.22 33.9743 106.184 34.2863 106.112C34.6063 106.032 34.9343 105.92 35.2703 105.776V106.7C34.9423 106.844 34.6183 106.948 34.2983 107.012C33.9783 107.084 33.5983 107.12 33.1583 107.12C32.5503 107.12 32.0103 106.996 31.5383 106.748C31.0743 106.5 30.7103 106.132 30.4463 105.644C30.1903 105.148 30.0623 104.544 30.0623 103.832C30.0623 103.128 30.1783 102.524 30.4103 102.02C30.6503 101.516 30.9823 101.128 31.4063 100.856C31.8383 100.584 32.3383 100.448 32.9063 100.448ZM32.8943 101.312C32.3903 101.312 31.9903 101.476 31.6943 101.804C31.4063 102.124 31.2343 102.572 31.1783 103.148H34.4543C34.4543 102.78 34.3983 102.46 34.2863 102.188C34.1743 101.916 34.0023 101.704 33.7703 101.552C33.5463 101.392 33.2543 101.312 32.8943 101.312ZM38.2518 107H37.1958V97.88H38.2518V107Z" fill="black"/>
+</svg>
diff --git a/docs/pics/MinimalExample.webp b/docs/pics/MinimalExample.webp
new file mode 100644 (file)
index 0000000..0b0ca2e
Binary files /dev/null and b/docs/pics/MinimalExample.webp differ
diff --git a/docs/pics/PageRouterModel.svg b/docs/pics/PageRouterModel.svg
new file mode 100644 (file)
index 0000000..cedea9e
--- /dev/null
@@ -0,0 +1,19 @@
+<svg width="986" height="592" viewBox="0 0 986 592" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="249" width="981" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M523 363H753.023C755.968 363 758.674 364.618 760.069 367.212L804.693 450.212C805.964 452.577 805.964 455.423 804.693 457.788L760.069 540.788C758.674 543.382 755.968 545 753.023 545H523C518.582 545 515 541.418 515 537V371C515 366.582 518.582 363 523 363Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M661.716 428.864C663.956 428.864 665.588 429.304 666.612 430.184C667.636 431.064 668.148 432.304 668.148 433.904C668.148 434.848 667.932 435.736 667.5 436.568C667.084 437.384 666.372 438.048 665.364 438.56C664.372 439.072 663.02 439.328 661.308 439.328H659.34V446H657.18V428.864H661.716ZM661.524 430.712H659.34V437.48H661.068C662.7 437.48 663.916 437.216 664.716 436.688C665.516 436.16 665.916 435.264 665.916 434C665.916 432.896 665.556 432.072 664.836 431.528C664.132 430.984 663.028 430.712 661.524 430.712ZM676.295 432.92C677.863 432.92 679.023 433.264 679.775 433.952C680.527 434.64 680.903 435.736 680.903 437.24V446H679.367L678.959 444.176H678.863C678.303 444.88 677.711 445.4 677.087 445.736C676.463 446.072 675.615 446.24 674.543 446.24C673.375 446.24 672.407 445.936 671.639 445.328C670.871 444.704 670.487 443.736 670.487 442.424C670.487 441.144 670.991 440.16 671.999 439.472C673.007 438.768 674.559 438.384 676.655 438.32L678.839 438.248V437.48C678.839 436.408 678.607 435.664 678.143 435.248C677.679 434.832 677.023 434.624 676.175 434.624C675.503 434.624 674.863 434.728 674.255 434.936C673.647 435.128 673.079 435.352 672.551 435.608L671.903 434.024C672.463 433.72 673.127 433.464 673.895 433.256C674.663 433.032 675.463 432.92 676.295 432.92ZM678.815 439.712L676.919 439.784C675.319 439.848 674.207 440.104 673.583 440.552C672.975 441 672.671 441.632 672.671 442.448C672.671 443.168 672.887 443.696 673.319 444.032C673.767 444.368 674.335 444.536 675.023 444.536C676.095 444.536 676.991 444.24 677.711 443.648C678.447 443.04 678.815 442.112 678.815 440.864V439.712ZM689.436 432.896C690.284 432.896 691.044 433.056 691.716 433.376C692.404 433.696 692.988 434.184 693.468 434.84H693.588L693.876 433.136H695.556V446.216C695.556 448.056 695.084 449.44 694.14 450.368C693.212 451.296 691.764 451.76 689.796 451.76C687.908 451.76 686.364 451.488 685.164 450.944V449C686.428 449.672 688.012 450.008 689.916 450.008C691.02 450.008 691.884 449.68 692.508 449.024C693.148 448.384 693.468 447.504 693.468 446.384V445.88C693.468 445.688 693.476 445.416 693.492 445.064C693.508 444.696 693.524 444.44 693.54 444.296H693.444C692.58 445.592 691.252 446.24 689.46 446.24C687.796 446.24 686.492 445.656 685.548 444.488C684.62 443.32 684.156 441.688 684.156 439.592C684.156 437.544 684.62 435.92 685.548 434.72C686.492 433.504 687.788 432.896 689.436 432.896ZM689.724 434.672C688.652 434.672 687.82 435.104 687.228 435.968C686.636 436.816 686.34 438.032 686.34 439.616C686.34 441.2 686.628 442.416 687.204 443.264C687.78 444.096 688.636 444.512 689.772 444.512C691.068 444.512 692.012 444.168 692.604 443.48C693.196 442.776 693.492 441.648 693.492 440.096V439.592C693.492 437.848 693.188 436.592 692.58 435.824C691.972 435.056 691.02 434.672 689.724 434.672ZM704.61 432.896C705.698 432.896 706.642 433.136 707.442 433.616C708.242 434.096 708.85 434.776 709.266 435.656C709.698 436.52 709.914 437.536 709.914 438.704V439.976H701.106C701.138 441.432 701.506 442.544 702.21 443.312C702.93 444.064 703.93 444.44 705.21 444.44C706.026 444.44 706.746 444.368 707.37 444.224C708.01 444.064 708.666 443.84 709.338 443.552V445.4C708.682 445.688 708.034 445.896 707.394 446.024C706.754 446.168 705.994 446.24 705.114 446.24C703.882 446.24 702.802 445.992 701.874 445.496C700.946 445 700.218 444.264 699.69 443.288C699.178 442.312 698.922 441.104 698.922 439.664C698.922 438.256 699.154 437.048 699.618 436.04C700.098 435.032 700.762 434.256 701.61 433.712C702.474 433.168 703.474 432.896 704.61 432.896ZM704.586 434.624C703.578 434.624 702.778 434.952 702.186 435.608C701.61 436.248 701.266 437.144 701.154 438.296H707.706C707.69 437.208 707.434 436.328 706.938 435.656C706.442 434.968 705.658 434.624 704.586 434.624ZM650.101 461.864C649.877 462.792 649.645 463.8 649.405 464.888C649.181 465.96 649.013 466.912 648.901 467.744H646.621L646.453 467.48C646.661 466.648 646.957 465.72 647.341 464.696C647.741 463.672 648.141 462.728 648.541 461.864H650.101ZM645.661 461.864C645.437 462.792 645.213 463.8 644.989 464.888C644.765 465.96 644.589 466.912 644.461 467.744H642.205L642.061 467.48C642.285 466.648 642.581 465.72 642.949 464.696C643.333 463.672 643.725 462.728 644.125 461.864H645.661ZM659.086 461.864L652.702 479H650.638L657.022 461.864H659.086ZM663.48 479H661.368V460.76H663.48V479ZM678.74 472.544C678.74 474.672 678.196 476.32 677.108 477.488C676.036 478.656 674.58 479.24 672.74 479.24C671.604 479.24 670.588 478.984 669.692 478.472C668.812 477.944 668.116 477.184 667.604 476.192C667.092 475.184 666.836 473.968 666.836 472.544C666.836 470.416 667.364 468.776 668.42 467.624C669.492 466.472 670.956 465.896 672.812 465.896C673.964 465.896 674.988 466.16 675.884 466.688C676.78 467.2 677.476 467.952 677.972 468.944C678.484 469.92 678.74 471.12 678.74 472.544ZM669.02 472.544C669.02 474.064 669.316 475.272 669.908 476.168C670.516 477.048 671.476 477.488 672.788 477.488C674.084 477.488 675.036 477.048 675.644 476.168C676.252 475.272 676.556 474.064 676.556 472.544C676.556 471.024 676.252 469.832 675.644 468.968C675.036 468.104 674.076 467.672 672.764 467.672C671.452 467.672 670.5 468.104 669.908 468.968C669.316 469.832 669.02 471.024 669.02 472.544ZM686.647 465.896C687.495 465.896 688.255 466.056 688.927 466.376C689.615 466.696 690.199 467.184 690.679 467.84H690.799L691.087 466.136H692.767V479.216C692.767 481.056 692.295 482.44 691.351 483.368C690.423 484.296 688.975 484.76 687.007 484.76C685.119 484.76 683.575 484.488 682.375 483.944V482C683.639 482.672 685.223 483.008 687.127 483.008C688.231 483.008 689.095 482.68 689.719 482.024C690.359 481.384 690.679 480.504 690.679 479.384V478.88C690.679 478.688 690.687 478.416 690.703 478.064C690.719 477.696 690.735 477.44 690.751 477.296H690.655C689.791 478.592 688.463 479.24 686.671 479.24C685.007 479.24 683.703 478.656 682.759 477.488C681.831 476.32 681.367 474.688 681.367 472.592C681.367 470.544 681.831 468.92 682.759 467.72C683.703 466.504 684.999 465.896 686.647 465.896ZM686.935 467.672C685.863 467.672 685.031 468.104 684.439 468.968C683.847 469.816 683.551 471.032 683.551 472.616C683.551 474.2 683.839 475.416 684.415 476.264C684.991 477.096 685.847 477.512 686.983 477.512C688.279 477.512 689.223 477.168 689.815 476.48C690.407 475.776 690.703 474.648 690.703 473.096V472.592C690.703 470.848 690.399 469.592 689.791 468.824C689.183 468.056 688.231 467.672 686.935 467.672ZM697.933 461.312C698.253 461.312 698.533 461.424 698.773 461.648C699.029 461.856 699.157 462.192 699.157 462.656C699.157 463.12 699.029 463.464 698.773 463.688C698.533 463.896 698.253 464 697.933 464C697.581 464 697.285 463.896 697.045 463.688C696.805 463.464 696.685 463.12 696.685 462.656C696.685 462.192 696.805 461.856 697.045 461.648C697.285 461.424 697.581 461.312 697.933 461.312ZM698.965 466.136V479H696.853V466.136H698.965ZM709.232 465.896C710.768 465.896 711.928 466.272 712.712 467.024C713.496 467.776 713.888 468.976 713.888 470.624V479H711.8V470.768C711.8 468.704 710.84 467.672 708.92 467.672C707.496 467.672 706.512 468.072 705.968 468.872C705.424 469.672 705.152 470.824 705.152 472.328V479H703.04V466.136H704.744L705.056 467.888H705.176C705.592 467.216 706.168 466.72 706.904 466.4C707.64 466.064 708.416 465.896 709.232 465.896ZM723.762 461.864L723.93 462.128C723.722 462.976 723.418 463.912 723.018 464.936C722.634 465.96 722.25 466.896 721.866 467.744H720.282C720.426 467.152 720.578 466.504 720.738 465.8C720.898 465.096 721.042 464.408 721.17 463.736C721.314 463.048 721.426 462.424 721.506 461.864H723.762ZM719.322 461.864L719.49 462.128C719.266 462.976 718.962 463.912 718.578 464.936C718.194 465.96 717.81 466.896 717.426 467.744H715.89C716.05 467.152 716.202 466.504 716.346 465.8C716.49 465.096 716.626 464.408 716.754 463.736C716.882 463.048 716.986 462.424 717.066 461.864H719.322Z" fill="black"/>
+<path d="M325 363H555.023C557.968 363 560.674 364.618 562.069 367.212L606.693 450.212C607.964 452.577 607.964 455.423 606.693 457.788L562.069 540.788C560.674 543.382 557.968 545 555.023 545H325C320.582 545 317 541.418 317 537V371C317 366.582 320.582 363 325 363Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M463.716 428.864C465.956 428.864 467.588 429.304 468.612 430.184C469.636 431.064 470.148 432.304 470.148 433.904C470.148 434.848 469.932 435.736 469.5 436.568C469.084 437.384 468.372 438.048 467.364 438.56C466.372 439.072 465.02 439.328 463.308 439.328H461.34V446H459.18V428.864H463.716ZM463.524 430.712H461.34V437.48H463.068C464.7 437.48 465.916 437.216 466.716 436.688C467.516 436.16 467.916 435.264 467.916 434C467.916 432.896 467.556 432.072 466.836 431.528C466.132 430.984 465.028 430.712 463.524 430.712ZM478.295 432.92C479.863 432.92 481.023 433.264 481.775 433.952C482.527 434.64 482.903 435.736 482.903 437.24V446H481.367L480.959 444.176H480.863C480.303 444.88 479.711 445.4 479.087 445.736C478.463 446.072 477.615 446.24 476.543 446.24C475.375 446.24 474.407 445.936 473.639 445.328C472.871 444.704 472.487 443.736 472.487 442.424C472.487 441.144 472.991 440.16 473.999 439.472C475.007 438.768 476.559 438.384 478.655 438.32L480.839 438.248V437.48C480.839 436.408 480.607 435.664 480.143 435.248C479.679 434.832 479.023 434.624 478.175 434.624C477.503 434.624 476.863 434.728 476.255 434.936C475.647 435.128 475.079 435.352 474.551 435.608L473.903 434.024C474.463 433.72 475.127 433.464 475.895 433.256C476.663 433.032 477.463 432.92 478.295 432.92ZM480.815 439.712L478.919 439.784C477.319 439.848 476.207 440.104 475.583 440.552C474.975 441 474.671 441.632 474.671 442.448C474.671 443.168 474.887 443.696 475.319 444.032C475.767 444.368 476.335 444.536 477.023 444.536C478.095 444.536 478.991 444.24 479.711 443.648C480.447 443.04 480.815 442.112 480.815 440.864V439.712ZM491.436 432.896C492.284 432.896 493.044 433.056 493.716 433.376C494.404 433.696 494.988 434.184 495.468 434.84H495.588L495.876 433.136H497.556V446.216C497.556 448.056 497.084 449.44 496.14 450.368C495.212 451.296 493.764 451.76 491.796 451.76C489.908 451.76 488.364 451.488 487.164 450.944V449C488.428 449.672 490.012 450.008 491.916 450.008C493.02 450.008 493.884 449.68 494.508 449.024C495.148 448.384 495.468 447.504 495.468 446.384V445.88C495.468 445.688 495.476 445.416 495.492 445.064C495.508 444.696 495.524 444.44 495.54 444.296H495.444C494.58 445.592 493.252 446.24 491.46 446.24C489.796 446.24 488.492 445.656 487.548 444.488C486.62 443.32 486.156 441.688 486.156 439.592C486.156 437.544 486.62 435.92 487.548 434.72C488.492 433.504 489.788 432.896 491.436 432.896ZM491.724 434.672C490.652 434.672 489.82 435.104 489.228 435.968C488.636 436.816 488.34 438.032 488.34 439.616C488.34 441.2 488.628 442.416 489.204 443.264C489.78 444.096 490.636 444.512 491.772 444.512C493.068 444.512 494.012 444.168 494.604 443.48C495.196 442.776 495.492 441.648 495.492 440.096V439.592C495.492 437.848 495.188 436.592 494.58 435.824C493.972 435.056 493.02 434.672 491.724 434.672ZM506.61 432.896C507.698 432.896 508.642 433.136 509.442 433.616C510.242 434.096 510.85 434.776 511.266 435.656C511.698 436.52 511.914 437.536 511.914 438.704V439.976H503.106C503.138 441.432 503.506 442.544 504.21 443.312C504.93 444.064 505.93 444.44 507.21 444.44C508.026 444.44 508.746 444.368 509.37 444.224C510.01 444.064 510.666 443.84 511.338 443.552V445.4C510.682 445.688 510.034 445.896 509.394 446.024C508.754 446.168 507.994 446.24 507.114 446.24C505.882 446.24 504.802 445.992 503.874 445.496C502.946 445 502.218 444.264 501.69 443.288C501.178 442.312 500.922 441.104 500.922 439.664C500.922 438.256 501.154 437.048 501.618 436.04C502.098 435.032 502.762 434.256 503.61 433.712C504.474 433.168 505.474 432.896 506.61 432.896ZM506.586 434.624C505.578 434.624 504.778 434.952 504.186 435.608C503.61 436.248 503.266 437.144 503.154 438.296H509.706C509.69 437.208 509.434 436.328 508.938 435.656C508.442 434.968 507.658 434.624 506.586 434.624ZM458.863 461.864C458.639 462.792 458.407 463.8 458.167 464.888C457.943 465.96 457.775 466.912 457.663 467.744H455.383L455.215 467.48C455.423 466.648 455.719 465.72 456.103 464.696C456.503 463.672 456.903 462.728 457.303 461.864H458.863ZM454.423 461.864C454.199 462.792 453.975 463.8 453.751 464.888C453.527 465.96 453.351 466.912 453.223 467.744H450.967L450.823 467.48C451.047 466.648 451.343 465.72 451.711 464.696C452.095 463.672 452.487 462.728 452.887 461.864H454.423ZM467.848 461.864L461.464 479H459.4L465.784 461.864H467.848ZM475.002 465.92C476.57 465.92 477.73 466.264 478.482 466.952C479.234 467.64 479.61 468.736 479.61 470.24V479H478.074L477.666 477.176H477.57C477.01 477.88 476.418 478.4 475.794 478.736C475.17 479.072 474.322 479.24 473.25 479.24C472.082 479.24 471.114 478.936 470.346 478.328C469.578 477.704 469.194 476.736 469.194 475.424C469.194 474.144 469.698 473.16 470.706 472.472C471.714 471.768 473.266 471.384 475.362 471.32L477.546 471.248V470.48C477.546 469.408 477.314 468.664 476.85 468.248C476.386 467.832 475.73 467.624 474.882 467.624C474.21 467.624 473.57 467.728 472.962 467.936C472.354 468.128 471.786 468.352 471.258 468.608L470.61 467.024C471.17 466.72 471.834 466.464 472.602 466.256C473.37 466.032 474.17 465.92 475.002 465.92ZM477.522 472.712L475.626 472.784C474.026 472.848 472.914 473.104 472.29 473.552C471.682 474 471.378 474.632 471.378 475.448C471.378 476.168 471.594 476.696 472.026 477.032C472.474 477.368 473.042 477.536 473.73 477.536C474.802 477.536 475.698 477.24 476.418 476.648C477.154 476.04 477.522 475.112 477.522 473.864V472.712ZM489.703 465.896C491.287 465.896 492.559 466.448 493.519 467.552C494.495 468.656 494.983 470.32 494.983 472.544C494.983 474.736 494.495 476.4 493.519 477.536C492.559 478.672 491.279 479.24 489.679 479.24C488.687 479.24 487.863 479.056 487.207 478.688C486.567 478.32 486.063 477.88 485.695 477.368H485.551C485.567 477.64 485.591 477.984 485.623 478.4C485.671 478.816 485.695 479.176 485.695 479.48V484.76H483.583V466.136H485.311L485.599 467.888H485.695C486.079 467.328 486.583 466.856 487.207 466.472C487.831 466.088 488.663 465.896 489.703 465.896ZM489.319 467.672C488.007 467.672 487.079 468.04 486.535 468.776C485.991 469.512 485.711 470.632 485.695 472.136V472.544C485.695 474.128 485.951 475.352 486.463 476.216C486.991 477.064 487.959 477.488 489.367 477.488C490.135 477.488 490.775 477.28 491.287 476.864C491.799 476.432 492.175 475.84 492.415 475.088C492.671 474.336 492.799 473.48 492.799 472.52C492.799 471.048 492.511 469.872 491.935 468.992C491.375 468.112 490.503 467.672 489.319 467.672ZM504.469 465.896C506.053 465.896 507.325 466.448 508.285 467.552C509.261 468.656 509.749 470.32 509.749 472.544C509.749 474.736 509.261 476.4 508.285 477.536C507.325 478.672 506.045 479.24 504.445 479.24C503.453 479.24 502.629 479.056 501.973 478.688C501.333 478.32 500.829 477.88 500.461 477.368H500.317C500.333 477.64 500.357 477.984 500.389 478.4C500.437 478.816 500.461 479.176 500.461 479.48V484.76H498.349V466.136H500.077L500.365 467.888H500.461C500.845 467.328 501.349 466.856 501.973 466.472C502.597 466.088 503.429 465.896 504.469 465.896ZM504.085 467.672C502.773 467.672 501.845 468.04 501.301 468.776C500.757 469.512 500.477 470.632 500.461 472.136V472.544C500.461 474.128 500.717 475.352 501.229 476.216C501.757 477.064 502.725 477.488 504.133 477.488C504.901 477.488 505.541 477.28 506.053 476.864C506.565 476.432 506.941 475.84 507.181 475.088C507.437 474.336 507.565 473.48 507.565 472.52C507.565 471.048 507.277 469.872 506.701 468.992C506.141 468.112 505.269 467.672 504.085 467.672ZM519 461.864L519.168 462.128C518.96 462.976 518.656 463.912 518.256 464.936C517.872 465.96 517.488 466.896 517.104 467.744H515.52C515.664 467.152 515.816 466.504 515.976 465.8C516.136 465.096 516.28 464.408 516.408 463.736C516.552 463.048 516.664 462.424 516.744 461.864H519ZM514.56 461.864L514.728 462.128C514.504 462.976 514.2 463.912 513.816 464.936C513.432 465.96 513.048 466.896 512.664 467.744H511.128C511.288 467.152 511.44 466.504 511.584 465.8C511.728 465.096 511.864 464.408 511.992 463.736C512.12 463.048 512.224 462.424 512.304 461.864H514.56Z" fill="black"/>
+<path d="M187 363H357.023C359.968 363 362.674 364.618 364.069 367.212L408.693 450.212C409.964 452.577 409.964 455.423 408.693 457.788L364.069 540.788C362.674 543.382 359.968 545 357.023 545H187C182.582 545 179 541.418 179 537V371C179 366.582 182.582 363 187 363Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M248.716 428.864C250.956 428.864 252.588 429.304 253.612 430.184C254.636 431.064 255.148 432.304 255.148 433.904C255.148 434.848 254.932 435.736 254.5 436.568C254.084 437.384 253.372 438.048 252.364 438.56C251.372 439.072 250.02 439.328 248.308 439.328H246.34V446H244.18V428.864H248.716ZM248.524 430.712H246.34V437.48H248.068C249.7 437.48 250.916 437.216 251.716 436.688C252.516 436.16 252.916 435.264 252.916 434C252.916 432.896 252.556 432.072 251.836 431.528C251.132 430.984 250.028 430.712 248.524 430.712ZM263.295 432.92C264.863 432.92 266.023 433.264 266.775 433.952C267.527 434.64 267.903 435.736 267.903 437.24V446H266.367L265.959 444.176H265.863C265.303 444.88 264.711 445.4 264.087 445.736C263.463 446.072 262.615 446.24 261.543 446.24C260.375 446.24 259.407 445.936 258.639 445.328C257.871 444.704 257.487 443.736 257.487 442.424C257.487 441.144 257.991 440.16 258.999 439.472C260.007 438.768 261.559 438.384 263.655 438.32L265.839 438.248V437.48C265.839 436.408 265.607 435.664 265.143 435.248C264.679 434.832 264.023 434.624 263.175 434.624C262.503 434.624 261.863 434.728 261.255 434.936C260.647 435.128 260.079 435.352 259.551 435.608L258.903 434.024C259.463 433.72 260.127 433.464 260.895 433.256C261.663 433.032 262.463 432.92 263.295 432.92ZM265.815 439.712L263.919 439.784C262.319 439.848 261.207 440.104 260.583 440.552C259.975 441 259.671 441.632 259.671 442.448C259.671 443.168 259.887 443.696 260.319 444.032C260.767 444.368 261.335 444.536 262.023 444.536C263.095 444.536 263.991 444.24 264.711 443.648C265.447 443.04 265.815 442.112 265.815 440.864V439.712ZM276.436 432.896C277.284 432.896 278.044 433.056 278.716 433.376C279.404 433.696 279.988 434.184 280.468 434.84H280.588L280.876 433.136H282.556V446.216C282.556 448.056 282.084 449.44 281.14 450.368C280.212 451.296 278.764 451.76 276.796 451.76C274.908 451.76 273.364 451.488 272.164 450.944V449C273.428 449.672 275.012 450.008 276.916 450.008C278.02 450.008 278.884 449.68 279.508 449.024C280.148 448.384 280.468 447.504 280.468 446.384V445.88C280.468 445.688 280.476 445.416 280.492 445.064C280.508 444.696 280.524 444.44 280.54 444.296H280.444C279.58 445.592 278.252 446.24 276.46 446.24C274.796 446.24 273.492 445.656 272.548 444.488C271.62 443.32 271.156 441.688 271.156 439.592C271.156 437.544 271.62 435.92 272.548 434.72C273.492 433.504 274.788 432.896 276.436 432.896ZM276.724 434.672C275.652 434.672 274.82 435.104 274.228 435.968C273.636 436.816 273.34 438.032 273.34 439.616C273.34 441.2 273.628 442.416 274.204 443.264C274.78 444.096 275.636 444.512 276.772 444.512C278.068 444.512 279.012 444.168 279.604 443.48C280.196 442.776 280.492 441.648 280.492 440.096V439.592C280.492 437.848 280.188 436.592 279.58 435.824C278.972 435.056 278.02 434.672 276.724 434.672ZM291.61 432.896C292.698 432.896 293.642 433.136 294.442 433.616C295.242 434.096 295.85 434.776 296.266 435.656C296.698 436.52 296.914 437.536 296.914 438.704V439.976H288.106C288.138 441.432 288.506 442.544 289.21 443.312C289.93 444.064 290.93 444.44 292.21 444.44C293.026 444.44 293.746 444.368 294.37 444.224C295.01 444.064 295.666 443.84 296.338 443.552V445.4C295.682 445.688 295.034 445.896 294.394 446.024C293.754 446.168 292.994 446.24 292.114 446.24C290.882 446.24 289.802 445.992 288.874 445.496C287.946 445 287.218 444.264 286.69 443.288C286.178 442.312 285.922 441.104 285.922 439.664C285.922 438.256 286.154 437.048 286.618 436.04C287.098 435.032 287.762 434.256 288.61 433.712C289.474 433.168 290.474 432.896 291.61 432.896ZM291.586 434.624C290.578 434.624 289.778 434.952 289.186 435.608C288.61 436.248 288.266 437.144 288.154 438.296H294.706C294.69 437.208 294.434 436.328 293.938 435.656C293.442 434.968 292.658 434.624 291.586 434.624ZM232.683 461.864C232.459 462.792 232.227 463.8 231.987 464.888C231.763 465.96 231.595 466.912 231.483 467.744H229.203L229.035 467.48C229.243 466.648 229.539 465.72 229.923 464.696C230.323 463.672 230.723 462.728 231.123 461.864H232.683ZM228.243 461.864C228.019 462.792 227.795 463.8 227.571 464.888C227.347 465.96 227.171 466.912 227.043 467.744H224.787L224.643 467.48C224.867 466.648 225.163 465.72 225.531 464.696C225.915 463.672 226.307 462.728 226.707 461.864H228.243ZM241.668 461.864L235.284 479H233.22L239.604 461.864H241.668ZM246.062 460.76V466.112C246.062 466.752 246.022 467.352 245.942 467.912H246.086C246.502 467.256 247.062 466.76 247.766 466.424C248.486 466.088 249.262 465.92 250.094 465.92C251.646 465.92 252.814 466.296 253.598 467.048C254.398 467.784 254.798 468.976 254.798 470.624V479H252.71V470.768C252.71 468.704 251.75 467.672 249.83 467.672C248.39 467.672 247.398 468.08 246.854 468.896C246.326 469.696 246.062 470.848 246.062 472.352V479H243.95V460.76H246.062ZM269.97 472.544C269.97 474.672 269.426 476.32 268.338 477.488C267.266 478.656 265.81 479.24 263.97 479.24C262.834 479.24 261.818 478.984 260.922 478.472C260.042 477.944 259.346 477.184 258.834 476.192C258.322 475.184 258.066 473.968 258.066 472.544C258.066 470.416 258.594 468.776 259.65 467.624C260.722 466.472 262.186 465.896 264.042 465.896C265.194 465.896 266.218 466.16 267.114 466.688C268.01 467.2 268.706 467.952 269.202 468.944C269.714 469.92 269.97 471.12 269.97 472.544ZM260.25 472.544C260.25 474.064 260.546 475.272 261.138 476.168C261.746 477.048 262.706 477.488 264.018 477.488C265.314 477.488 266.266 477.048 266.874 476.168C267.482 475.272 267.786 474.064 267.786 472.544C267.786 471.024 267.482 469.832 266.874 468.968C266.266 468.104 265.306 467.672 263.994 467.672C262.682 467.672 261.73 468.104 261.138 468.968C260.546 469.832 260.25 471.024 260.25 472.544ZM287.429 465.896C288.885 465.896 289.973 466.272 290.693 467.024C291.413 467.776 291.773 468.976 291.773 470.624V479H289.685V470.72C289.685 468.688 288.813 467.672 287.069 467.672C285.821 467.672 284.925 468.032 284.381 468.752C283.853 469.472 283.589 470.52 283.589 471.896V479H281.501V470.72C281.501 468.688 280.621 467.672 278.861 467.672C277.565 467.672 276.669 468.072 276.173 468.872C275.677 469.672 275.429 470.824 275.429 472.328V479H273.317V466.136H275.021L275.333 467.888H275.453C275.853 467.216 276.389 466.72 277.061 466.4C277.749 466.064 278.477 465.896 279.245 465.896C281.261 465.896 282.573 466.616 283.181 468.056H283.301C283.733 467.32 284.317 466.776 285.053 466.424C285.789 466.072 286.581 465.896 287.429 465.896ZM300.715 465.896C301.803 465.896 302.747 466.136 303.547 466.616C304.347 467.096 304.955 467.776 305.371 468.656C305.803 469.52 306.019 470.536 306.019 471.704V472.976H297.211C297.243 474.432 297.611 475.544 298.315 476.312C299.035 477.064 300.035 477.44 301.315 477.44C302.131 477.44 302.851 477.368 303.475 477.224C304.115 477.064 304.771 476.84 305.443 476.552V478.4C304.787 478.688 304.139 478.896 303.499 479.024C302.859 479.168 302.099 479.24 301.219 479.24C299.987 479.24 298.907 478.992 297.979 478.496C297.051 478 296.323 477.264 295.795 476.288C295.283 475.312 295.027 474.104 295.027 472.664C295.027 471.256 295.259 470.048 295.723 469.04C296.203 468.032 296.867 467.256 297.715 466.712C298.579 466.168 299.579 465.896 300.715 465.896ZM300.691 467.624C299.683 467.624 298.883 467.952 298.291 468.608C297.715 469.248 297.371 470.144 297.259 471.296H303.811C303.795 470.208 303.539 469.328 303.043 468.656C302.547 467.968 301.763 467.624 300.691 467.624ZM315.18 461.864L315.348 462.128C315.14 462.976 314.836 463.912 314.436 464.936C314.052 465.96 313.668 466.896 313.284 467.744H311.7C311.844 467.152 311.996 466.504 312.156 465.8C312.316 465.096 312.46 464.408 312.588 463.736C312.732 463.048 312.844 462.424 312.924 461.864H315.18ZM310.74 461.864L310.908 462.128C310.684 462.976 310.38 463.912 309.996 464.936C309.612 465.96 309.228 466.896 308.844 467.744H307.308C307.468 467.152 307.62 466.504 307.764 465.8C307.908 465.096 308.044 464.408 308.172 463.736C308.3 463.048 308.404 462.424 308.484 461.864H310.74Z" fill="black"/>
+<path d="M433.001 284.864C435.241 284.864 436.873 285.304 437.897 286.184C438.921 287.064 439.433 288.304 439.433 289.904C439.433 290.848 439.217 291.736 438.785 292.568C438.369 293.384 437.657 294.048 436.649 294.56C435.657 295.072 434.305 295.328 432.593 295.328H430.625V302H428.465V284.864H433.001ZM432.809 286.712H430.625V293.48H432.353C433.985 293.48 435.201 293.216 436.001 292.688C436.801 292.16 437.201 291.264 437.201 290C437.201 288.896 436.841 288.072 436.121 287.528C435.417 286.984 434.313 286.712 432.809 286.712ZM447.58 288.92C449.148 288.92 450.308 289.264 451.06 289.952C451.812 290.64 452.188 291.736 452.188 293.24V302H450.652L450.244 300.176H450.148C449.588 300.88 448.996 301.4 448.372 301.736C447.748 302.072 446.9 302.24 445.828 302.24C444.66 302.24 443.692 301.936 442.924 301.328C442.156 300.704 441.772 299.736 441.772 298.424C441.772 297.144 442.276 296.16 443.284 295.472C444.292 294.768 445.844 294.384 447.94 294.32L450.124 294.248V293.48C450.124 292.408 449.892 291.664 449.428 291.248C448.964 290.832 448.308 290.624 447.46 290.624C446.788 290.624 446.148 290.728 445.54 290.936C444.932 291.128 444.364 291.352 443.836 291.608L443.188 290.024C443.748 289.72 444.412 289.464 445.18 289.256C445.948 289.032 446.748 288.92 447.58 288.92ZM450.1 295.712L448.204 295.784C446.604 295.848 445.492 296.104 444.868 296.552C444.26 297 443.956 297.632 443.956 298.448C443.956 299.168 444.172 299.696 444.604 300.032C445.052 300.368 445.62 300.536 446.308 300.536C447.38 300.536 448.276 300.24 448.996 299.648C449.732 299.04 450.1 298.112 450.1 296.864V295.712ZM460.721 288.896C461.569 288.896 462.329 289.056 463.001 289.376C463.689 289.696 464.273 290.184 464.753 290.84H464.873L465.161 289.136H466.841V302.216C466.841 304.056 466.369 305.44 465.425 306.368C464.497 307.296 463.049 307.76 461.081 307.76C459.193 307.76 457.649 307.488 456.449 306.944V305C457.713 305.672 459.297 306.008 461.201 306.008C462.305 306.008 463.169 305.68 463.793 305.024C464.433 304.384 464.753 303.504 464.753 302.384V301.88C464.753 301.688 464.761 301.416 464.777 301.064C464.793 300.696 464.809 300.44 464.825 300.296H464.729C463.865 301.592 462.537 302.24 460.745 302.24C459.081 302.24 457.777 301.656 456.833 300.488C455.905 299.32 455.441 297.688 455.441 295.592C455.441 293.544 455.905 291.92 456.833 290.72C457.777 289.504 459.073 288.896 460.721 288.896ZM461.009 290.672C459.937 290.672 459.105 291.104 458.513 291.968C457.921 292.816 457.625 294.032 457.625 295.616C457.625 297.2 457.913 298.416 458.489 299.264C459.065 300.096 459.921 300.512 461.057 300.512C462.353 300.512 463.297 300.168 463.889 299.48C464.481 298.776 464.777 297.648 464.777 296.096V295.592C464.777 293.848 464.473 292.592 463.865 291.824C463.257 291.056 462.305 290.672 461.009 290.672ZM475.895 288.896C476.983 288.896 477.927 289.136 478.727 289.616C479.527 290.096 480.135 290.776 480.551 291.656C480.983 292.52 481.199 293.536 481.199 294.704V295.976H472.391C472.423 297.432 472.791 298.544 473.495 299.312C474.215 300.064 475.215 300.44 476.495 300.44C477.311 300.44 478.031 300.368 478.655 300.224C479.295 300.064 479.951 299.84 480.623 299.552V301.4C479.967 301.688 479.319 301.896 478.679 302.024C478.039 302.168 477.279 302.24 476.399 302.24C475.167 302.24 474.087 301.992 473.159 301.496C472.231 301 471.503 300.264 470.975 299.288C470.463 298.312 470.207 297.104 470.207 295.664C470.207 294.256 470.439 293.048 470.903 292.04C471.383 291.032 472.047 290.256 472.895 289.712C473.759 289.168 474.759 288.896 475.895 288.896ZM475.871 290.624C474.863 290.624 474.063 290.952 473.471 291.608C472.895 292.248 472.551 293.144 472.439 294.296H478.991C478.975 293.208 478.719 292.328 478.223 291.656C477.727 290.968 476.943 290.624 475.871 290.624ZM489.49 284.864C491.618 284.864 493.186 285.272 494.194 286.088C495.218 286.888 495.73 288.104 495.73 289.736C495.73 290.648 495.562 291.408 495.226 292.016C494.89 292.624 494.458 293.112 493.93 293.48C493.418 293.848 492.874 294.128 492.298 294.32L497.002 302H494.482L490.33 294.92H486.922V302H484.762V284.864H489.49ZM489.37 286.736H486.922V293.096H489.49C490.882 293.096 491.898 292.824 492.538 292.28C493.178 291.72 493.498 290.904 493.498 289.832C493.498 288.712 493.162 287.92 492.49 287.456C491.818 286.976 490.778 286.736 489.37 286.736ZM510.587 295.544C510.587 297.672 510.043 299.32 508.955 300.488C507.883 301.656 506.427 302.24 504.587 302.24C503.451 302.24 502.435 301.984 501.539 301.472C500.659 300.944 499.963 300.184 499.451 299.192C498.939 298.184 498.683 296.968 498.683 295.544C498.683 293.416 499.211 291.776 500.267 290.624C501.339 289.472 502.803 288.896 504.659 288.896C505.811 288.896 506.835 289.16 507.731 289.688C508.627 290.2 509.323 290.952 509.819 291.944C510.331 292.92 510.587 294.12 510.587 295.544ZM500.867 295.544C500.867 297.064 501.163 298.272 501.755 299.168C502.363 300.048 503.323 300.488 504.635 300.488C505.931 300.488 506.883 300.048 507.491 299.168C508.099 298.272 508.403 297.064 508.403 295.544C508.403 294.024 508.099 292.832 507.491 291.968C506.883 291.104 505.923 290.672 504.611 290.672C503.299 290.672 502.347 291.104 501.755 291.968C501.163 292.832 500.867 294.024 500.867 295.544ZM524.687 289.136V302H522.959L522.647 300.296H522.551C522.135 300.968 521.559 301.464 520.823 301.784C520.087 302.088 519.303 302.24 518.471 302.24C516.919 302.24 515.751 301.872 514.967 301.136C514.183 300.384 513.791 299.192 513.791 297.56V289.136H515.927V297.416C515.927 299.464 516.879 300.488 518.783 300.488C520.207 300.488 521.191 300.088 521.735 299.288C522.295 298.488 522.575 297.336 522.575 295.832V289.136H524.687ZM533.066 300.512C533.386 300.512 533.714 300.488 534.05 300.44C534.386 300.392 534.658 300.328 534.866 300.248V301.856C534.642 301.968 534.322 302.056 533.906 302.12C533.49 302.2 533.09 302.24 532.706 302.24C532.034 302.24 531.41 302.128 530.834 301.904C530.274 301.664 529.818 301.256 529.466 300.68C529.114 300.104 528.938 299.296 528.938 298.256V290.768H527.114V289.76L528.962 288.92L529.802 286.184H531.05V289.136H534.77V290.768H531.05V298.208C531.05 298.992 531.234 299.576 531.602 299.96C531.986 300.328 532.474 300.512 533.066 300.512ZM542.41 288.896C543.498 288.896 544.442 289.136 545.242 289.616C546.042 290.096 546.65 290.776 547.066 291.656C547.498 292.52 547.714 293.536 547.714 294.704V295.976H538.906C538.938 297.432 539.306 298.544 540.01 299.312C540.73 300.064 541.73 300.44 543.01 300.44C543.826 300.44 544.546 300.368 545.17 300.224C545.81 300.064 546.466 299.84 547.138 299.552V301.4C546.482 301.688 545.834 301.896 545.194 302.024C544.554 302.168 543.794 302.24 542.914 302.24C541.682 302.24 540.602 301.992 539.674 301.496C538.746 301 538.018 300.264 537.49 299.288C536.978 298.312 536.722 297.104 536.722 295.664C536.722 294.256 536.954 293.048 537.418 292.04C537.898 291.032 538.562 290.256 539.41 289.712C540.274 289.168 541.274 288.896 542.41 288.896ZM542.386 290.624C541.378 290.624 540.578 290.952 539.986 291.608C539.41 292.248 539.066 293.144 538.954 294.296H545.506C545.49 293.208 545.234 292.328 544.738 291.656C544.242 290.968 543.458 290.624 542.386 290.624ZM556.989 288.896C557.229 288.896 557.485 288.912 557.757 288.944C558.029 288.96 558.277 288.992 558.501 289.04L558.237 290.984C558.029 290.936 557.797 290.896 557.541 290.864C557.285 290.832 557.053 290.816 556.845 290.816C556.189 290.816 555.573 291 554.997 291.368C554.421 291.72 553.957 292.224 553.605 292.88C553.269 293.52 553.101 294.272 553.101 295.136V302H550.989V289.136H552.717L552.957 291.488H553.053C553.453 290.784 553.981 290.176 554.637 289.664C555.309 289.152 556.093 288.896 556.989 288.896ZM353.289 317.864C353.065 318.792 352.833 319.8 352.593 320.888C352.369 321.96 352.201 322.912 352.089 323.744H349.809L349.641 323.48C349.849 322.648 350.145 321.72 350.529 320.696C350.929 319.672 351.329 318.728 351.729 317.864H353.289ZM348.849 317.864C348.625 318.792 348.401 319.8 348.177 320.888C347.953 321.96 347.777 322.912 347.649 323.744H345.393L345.249 323.48C345.473 322.648 345.769 321.72 346.137 320.696C346.521 319.672 346.913 318.728 347.313 317.864H348.849ZM362.274 317.864L355.89 335H353.826L360.21 317.864H362.274ZM366.668 316.76V322.112C366.668 322.752 366.628 323.352 366.548 323.912H366.692C367.108 323.256 367.668 322.76 368.372 322.424C369.092 322.088 369.868 321.92 370.7 321.92C372.252 321.92 373.42 322.296 374.204 323.048C375.004 323.784 375.404 324.976 375.404 326.624V335H373.316V326.768C373.316 324.704 372.356 323.672 370.436 323.672C368.996 323.672 368.004 324.08 367.46 324.896C366.932 325.696 366.668 326.848 366.668 328.352V335H364.556V316.76H366.668ZM390.576 328.544C390.576 330.672 390.032 332.32 388.944 333.488C387.872 334.656 386.416 335.24 384.576 335.24C383.44 335.24 382.424 334.984 381.528 334.472C380.648 333.944 379.952 333.184 379.44 332.192C378.928 331.184 378.672 329.968 378.672 328.544C378.672 326.416 379.2 324.776 380.256 323.624C381.328 322.472 382.792 321.896 384.648 321.896C385.8 321.896 386.824 322.16 387.72 322.688C388.616 323.2 389.312 323.952 389.808 324.944C390.32 325.92 390.576 327.12 390.576 328.544ZM380.856 328.544C380.856 330.064 381.152 331.272 381.744 332.168C382.352 333.048 383.312 333.488 384.624 333.488C385.92 333.488 386.872 333.048 387.48 332.168C388.088 331.272 388.392 330.064 388.392 328.544C388.392 327.024 388.088 325.832 387.48 324.968C386.872 324.104 385.912 323.672 384.6 323.672C383.288 323.672 382.336 324.104 381.744 324.968C381.152 325.832 380.856 327.024 380.856 328.544ZM408.035 321.896C409.491 321.896 410.579 322.272 411.299 323.024C412.019 323.776 412.379 324.976 412.379 326.624V335H410.291V326.72C410.291 324.688 409.419 323.672 407.675 323.672C406.427 323.672 405.531 324.032 404.987 324.752C404.459 325.472 404.195 326.52 404.195 327.896V335H402.107V326.72C402.107 324.688 401.227 323.672 399.467 323.672C398.171 323.672 397.275 324.072 396.779 324.872C396.283 325.672 396.035 326.824 396.035 328.328V335H393.923V322.136H395.627L395.939 323.888H396.059C396.459 323.216 396.995 322.72 397.667 322.4C398.355 322.064 399.083 321.896 399.851 321.896C401.867 321.896 403.179 322.616 403.787 324.056H403.907C404.339 323.32 404.923 322.776 405.659 322.424C406.395 322.072 407.187 321.896 408.035 321.896ZM421.321 321.896C422.409 321.896 423.353 322.136 424.153 322.616C424.953 323.096 425.561 323.776 425.977 324.656C426.409 325.52 426.625 326.536 426.625 327.704V328.976H417.817C417.849 330.432 418.217 331.544 418.921 332.312C419.641 333.064 420.641 333.44 421.921 333.44C422.737 333.44 423.457 333.368 424.081 333.224C424.721 333.064 425.377 332.84 426.049 332.552V334.4C425.393 334.688 424.745 334.896 424.105 335.024C423.465 335.168 422.705 335.24 421.825 335.24C420.593 335.24 419.513 334.992 418.585 334.496C417.657 334 416.929 333.264 416.401 332.288C415.889 331.312 415.633 330.104 415.633 328.664C415.633 327.256 415.865 326.048 416.329 325.04C416.809 324.032 417.473 323.256 418.321 322.712C419.185 322.168 420.185 321.896 421.321 321.896ZM421.297 323.624C420.289 323.624 419.489 323.952 418.897 324.608C418.321 325.248 417.977 326.144 417.865 327.296H424.417C424.401 326.208 424.145 325.328 423.649 324.656C423.153 323.968 422.369 323.624 421.297 323.624ZM435.785 317.864L435.953 318.128C435.745 318.976 435.441 319.912 435.041 320.936C434.657 321.96 434.273 322.896 433.889 323.744H432.305C432.449 323.152 432.601 322.504 432.761 321.8C432.921 321.096 433.065 320.408 433.193 319.736C433.337 319.048 433.449 318.424 433.529 317.864H435.785ZM431.345 317.864L431.513 318.128C431.289 318.976 430.985 319.912 430.601 320.936C430.217 321.96 429.833 322.896 429.449 323.744H427.913C428.073 323.152 428.225 322.504 428.369 321.8C428.513 321.096 428.649 320.408 428.777 319.736C428.905 319.048 429.009 318.424 429.089 317.864H431.345ZM443.684 330.344L452.732 326.576L443.684 322.28V320.408L454.988 326.048V327.248L443.684 332.216V330.344ZM470.781 317.864C470.557 318.792 470.325 319.8 470.085 320.888C469.861 321.96 469.693 322.912 469.581 323.744H467.301L467.133 323.48C467.341 322.648 467.637 321.72 468.021 320.696C468.421 319.672 468.821 318.728 469.221 317.864H470.781ZM466.341 317.864C466.117 318.792 465.893 319.8 465.669 320.888C465.445 321.96 465.269 322.912 465.141 323.744H462.885L462.741 323.48C462.965 322.648 463.261 321.72 463.629 320.696C464.013 319.672 464.405 318.728 464.805 317.864H466.341ZM479.766 317.864L473.382 335H471.318L477.702 317.864H479.766ZM486.92 321.92C488.488 321.92 489.648 322.264 490.4 322.952C491.152 323.64 491.528 324.736 491.528 326.24V335H489.992L489.584 333.176H489.488C488.928 333.88 488.336 334.4 487.712 334.736C487.088 335.072 486.24 335.24 485.168 335.24C484 335.24 483.032 334.936 482.264 334.328C481.496 333.704 481.112 332.736 481.112 331.424C481.112 330.144 481.616 329.16 482.624 328.472C483.632 327.768 485.184 327.384 487.28 327.32L489.464 327.248V326.48C489.464 325.408 489.232 324.664 488.768 324.248C488.304 323.832 487.648 323.624 486.8 323.624C486.128 323.624 485.488 323.728 484.88 323.936C484.272 324.128 483.704 324.352 483.176 324.608L482.528 323.024C483.088 322.72 483.752 322.464 484.52 322.256C485.288 322.032 486.088 321.92 486.92 321.92ZM489.44 328.712L487.544 328.784C485.944 328.848 484.832 329.104 484.208 329.552C483.6 330 483.296 330.632 483.296 331.448C483.296 332.168 483.512 332.696 483.944 333.032C484.392 333.368 484.96 333.536 485.648 333.536C486.72 333.536 487.616 333.24 488.336 332.648C489.072 332.04 489.44 331.112 489.44 329.864V328.712ZM501.621 321.896C503.205 321.896 504.477 322.448 505.437 323.552C506.413 324.656 506.901 326.32 506.901 328.544C506.901 330.736 506.413 332.4 505.437 333.536C504.477 334.672 503.197 335.24 501.597 335.24C500.605 335.24 499.781 335.056 499.125 334.688C498.485 334.32 497.981 333.88 497.613 333.368H497.469C497.485 333.64 497.509 333.984 497.541 334.4C497.589 334.816 497.613 335.176 497.613 335.48V340.76H495.501V322.136H497.229L497.517 323.888H497.613C497.997 323.328 498.501 322.856 499.125 322.472C499.749 322.088 500.581 321.896 501.621 321.896ZM501.237 323.672C499.925 323.672 498.997 324.04 498.453 324.776C497.909 325.512 497.629 326.632 497.613 328.136V328.544C497.613 330.128 497.869 331.352 498.381 332.216C498.909 333.064 499.877 333.488 501.285 333.488C502.053 333.488 502.693 333.28 503.205 332.864C503.717 332.432 504.093 331.84 504.333 331.088C504.589 330.336 504.717 329.48 504.717 328.52C504.717 327.048 504.429 325.872 503.853 324.992C503.293 324.112 502.421 323.672 501.237 323.672ZM516.387 321.896C517.971 321.896 519.243 322.448 520.203 323.552C521.179 324.656 521.667 326.32 521.667 328.544C521.667 330.736 521.179 332.4 520.203 333.536C519.243 334.672 517.963 335.24 516.363 335.24C515.371 335.24 514.547 335.056 513.891 334.688C513.251 334.32 512.747 333.88 512.379 333.368H512.235C512.251 333.64 512.275 333.984 512.307 334.4C512.355 334.816 512.379 335.176 512.379 335.48V340.76H510.267V322.136H511.995L512.283 323.888H512.379C512.763 323.328 513.267 322.856 513.891 322.472C514.515 322.088 515.347 321.896 516.387 321.896ZM516.003 323.672C514.691 323.672 513.763 324.04 513.219 324.776C512.675 325.512 512.395 326.632 512.379 328.136V328.544C512.379 330.128 512.635 331.352 513.147 332.216C513.675 333.064 514.643 333.488 516.051 333.488C516.819 333.488 517.459 333.28 517.971 332.864C518.483 332.432 518.859 331.84 519.099 331.088C519.355 330.336 519.483 329.48 519.483 328.52C519.483 327.048 519.195 325.872 518.619 324.992C518.059 324.112 517.187 323.672 516.003 323.672ZM530.918 317.864L531.086 318.128C530.878 318.976 530.574 319.912 530.174 320.936C529.79 321.96 529.406 322.896 529.022 323.744H527.438C527.582 323.152 527.734 322.504 527.894 321.8C528.054 321.096 528.198 320.408 528.326 319.736C528.47 319.048 528.582 318.424 528.662 317.864H530.918ZM526.478 317.864L526.646 318.128C526.422 318.976 526.118 319.912 525.734 320.936C525.35 321.96 524.966 322.896 524.582 323.744H523.046C523.206 323.152 523.358 322.504 523.502 321.8C523.646 321.096 523.782 320.408 523.91 319.736C524.038 319.048 524.142 318.424 524.222 317.864H526.478ZM538.817 330.344L547.865 326.576L538.817 322.28V320.408L550.121 326.048V327.248L538.817 332.216V330.344ZM565.914 317.864C565.69 318.792 565.458 319.8 565.218 320.888C564.994 321.96 564.826 322.912 564.714 323.744H562.434L562.266 323.48C562.474 322.648 562.77 321.72 563.154 320.696C563.554 319.672 563.954 318.728 564.354 317.864H565.914ZM561.474 317.864C561.25 318.792 561.026 319.8 560.802 320.888C560.578 321.96 560.402 322.912 560.274 323.744H558.018L557.874 323.48C558.098 322.648 558.394 321.72 558.762 320.696C559.146 319.672 559.538 318.728 559.938 317.864H561.474ZM574.899 317.864L568.515 335H566.451L572.835 317.864H574.899ZM579.293 335H577.181V316.76H579.293V335ZM594.552 328.544C594.552 330.672 594.008 332.32 592.92 333.488C591.848 334.656 590.392 335.24 588.552 335.24C587.416 335.24 586.4 334.984 585.504 334.472C584.624 333.944 583.928 333.184 583.416 332.192C582.904 331.184 582.648 329.968 582.648 328.544C582.648 326.416 583.176 324.776 584.232 323.624C585.304 322.472 586.768 321.896 588.624 321.896C589.776 321.896 590.8 322.16 591.696 322.688C592.592 323.2 593.288 323.952 593.784 324.944C594.296 325.92 594.552 327.12 594.552 328.544ZM584.832 328.544C584.832 330.064 585.128 331.272 585.72 332.168C586.328 333.048 587.288 333.488 588.6 333.488C589.896 333.488 590.848 333.048 591.456 332.168C592.064 331.272 592.368 330.064 592.368 328.544C592.368 327.024 592.064 325.832 591.456 324.968C590.848 324.104 589.888 323.672 588.576 323.672C587.264 323.672 586.312 324.104 585.72 324.968C585.128 325.832 584.832 327.024 584.832 328.544ZM602.459 321.896C603.307 321.896 604.067 322.056 604.739 322.376C605.427 322.696 606.011 323.184 606.491 323.84H606.611L606.899 322.136H608.579V335.216C608.579 337.056 608.107 338.44 607.163 339.368C606.235 340.296 604.787 340.76 602.819 340.76C600.931 340.76 599.387 340.488 598.187 339.944V338C599.451 338.672 601.035 339.008 602.939 339.008C604.043 339.008 604.907 338.68 605.531 338.024C606.171 337.384 606.491 336.504 606.491 335.384V334.88C606.491 334.688 606.499 334.416 606.515 334.064C606.531 333.696 606.547 333.44 606.563 333.296H606.467C605.603 334.592 604.275 335.24 602.483 335.24C600.819 335.24 599.515 334.656 598.571 333.488C597.643 332.32 597.179 330.688 597.179 328.592C597.179 326.544 597.643 324.92 598.571 323.72C599.515 322.504 600.811 321.896 602.459 321.896ZM602.747 323.672C601.675 323.672 600.843 324.104 600.251 324.968C599.659 325.816 599.363 327.032 599.363 328.616C599.363 330.2 599.651 331.416 600.227 332.264C600.803 333.096 601.659 333.512 602.795 333.512C604.091 333.512 605.035 333.168 605.627 332.48C606.219 331.776 606.515 330.648 606.515 329.096V328.592C606.515 326.848 606.211 325.592 605.603 324.824C604.995 324.056 604.043 323.672 602.747 323.672ZM613.745 317.312C614.065 317.312 614.345 317.424 614.585 317.648C614.841 317.856 614.969 318.192 614.969 318.656C614.969 319.12 614.841 319.464 614.585 319.688C614.345 319.896 614.065 320 613.745 320C613.393 320 613.097 319.896 612.857 319.688C612.617 319.464 612.497 319.12 612.497 318.656C612.497 318.192 612.617 317.856 612.857 317.648C613.097 317.424 613.393 317.312 613.745 317.312ZM614.777 322.136V335H612.665V322.136H614.777ZM625.045 321.896C626.581 321.896 627.741 322.272 628.525 323.024C629.309 323.776 629.701 324.976 629.701 326.624V335H627.613V326.768C627.613 324.704 626.653 323.672 624.733 323.672C623.309 323.672 622.325 324.072 621.781 324.872C621.237 325.672 620.965 326.824 620.965 328.328V335H618.853V322.136H620.557L620.869 323.888H620.989C621.405 323.216 621.981 322.72 622.717 322.4C623.453 322.064 624.229 321.896 625.045 321.896ZM639.574 317.864L639.742 318.128C639.534 318.976 639.23 319.912 638.83 320.936C638.446 321.96 638.062 322.896 637.678 323.744H636.094C636.238 323.152 636.39 322.504 636.55 321.8C636.71 321.096 636.854 320.408 636.982 319.736C637.126 319.048 637.238 318.424 637.318 317.864H639.574ZM635.134 317.864L635.302 318.128C635.078 318.976 634.774 319.912 634.39 320.936C634.006 321.96 633.622 322.896 633.238 323.744H631.702C631.862 323.152 632.014 322.504 632.158 321.8C632.302 321.096 632.438 320.408 632.566 319.736C632.694 319.048 632.798 318.424 632.878 317.864H635.134Z" fill="black"/>
+<rect x="81" y="2" width="182" height="182" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M117.458 67.864C119.698 67.864 121.33 68.304 122.354 69.184C123.378 70.064 123.89 71.304 123.89 72.904C123.89 73.848 123.674 74.736 123.242 75.568C122.826 76.384 122.114 77.048 121.106 77.56C120.114 78.072 118.762 78.328 117.05 78.328H115.082V85H112.922V67.864H117.458ZM117.266 69.712H115.082V76.48H116.81C118.442 76.48 119.658 76.216 120.458 75.688C121.258 75.16 121.658 74.264 121.658 73C121.658 71.896 121.298 71.072 120.578 70.528C119.874 69.984 118.77 69.712 117.266 69.712ZM132.037 71.92C133.605 71.92 134.765 72.264 135.517 72.952C136.269 73.64 136.645 74.736 136.645 76.24V85H135.109L134.701 83.176H134.605C134.045 83.88 133.453 84.4 132.829 84.736C132.205 85.072 131.357 85.24 130.285 85.24C129.117 85.24 128.149 84.936 127.381 84.328C126.613 83.704 126.229 82.736 126.229 81.424C126.229 80.144 126.733 79.16 127.741 78.472C128.749 77.768 130.301 77.384 132.397 77.32L134.581 77.248V76.48C134.581 75.408 134.349 74.664 133.885 74.248C133.421 73.832 132.765 73.624 131.917 73.624C131.245 73.624 130.605 73.728 129.997 73.936C129.389 74.128 128.821 74.352 128.293 74.608L127.645 73.024C128.205 72.72 128.869 72.464 129.637 72.256C130.405 72.032 131.205 71.92 132.037 71.92ZM134.557 78.712L132.661 78.784C131.061 78.848 129.949 79.104 129.325 79.552C128.717 80 128.413 80.632 128.413 81.448C128.413 82.168 128.629 82.696 129.061 83.032C129.509 83.368 130.077 83.536 130.765 83.536C131.837 83.536 132.733 83.24 133.453 82.648C134.189 82.04 134.557 81.112 134.557 79.864V78.712ZM145.178 71.896C146.026 71.896 146.786 72.056 147.458 72.376C148.146 72.696 148.73 73.184 149.21 73.84H149.33L149.618 72.136H151.298V85.216C151.298 87.056 150.826 88.44 149.882 89.368C148.954 90.296 147.506 90.76 145.538 90.76C143.65 90.76 142.106 90.488 140.906 89.944V88C142.17 88.672 143.754 89.008 145.658 89.008C146.762 89.008 147.626 88.68 148.25 88.024C148.89 87.384 149.21 86.504 149.21 85.384V84.88C149.21 84.688 149.218 84.416 149.234 84.064C149.25 83.696 149.266 83.44 149.282 83.296H149.186C148.322 84.592 146.994 85.24 145.202 85.24C143.538 85.24 142.234 84.656 141.29 83.488C140.362 82.32 139.898 80.688 139.898 78.592C139.898 76.544 140.362 74.92 141.29 73.72C142.234 72.504 143.53 71.896 145.178 71.896ZM145.466 73.672C144.394 73.672 143.562 74.104 142.97 74.968C142.378 75.816 142.082 77.032 142.082 78.616C142.082 80.2 142.37 81.416 142.946 82.264C143.522 83.096 144.378 83.512 145.514 83.512C146.81 83.512 147.754 83.168 148.346 82.48C148.938 81.776 149.234 80.648 149.234 79.096V78.592C149.234 76.848 148.93 75.592 148.322 74.824C147.714 74.056 146.762 73.672 145.466 73.672ZM160.352 71.896C161.44 71.896 162.384 72.136 163.184 72.616C163.984 73.096 164.592 73.776 165.008 74.656C165.44 75.52 165.656 76.536 165.656 77.704V78.976H156.848C156.88 80.432 157.248 81.544 157.952 82.312C158.672 83.064 159.672 83.44 160.952 83.44C161.768 83.44 162.488 83.368 163.112 83.224C163.752 83.064 164.408 82.84 165.08 82.552V84.4C164.424 84.688 163.776 84.896 163.136 85.024C162.496 85.168 161.736 85.24 160.856 85.24C159.624 85.24 158.544 84.992 157.616 84.496C156.688 84 155.96 83.264 155.432 82.288C154.92 81.312 154.664 80.104 154.664 78.664C154.664 77.256 154.896 76.048 155.36 75.04C155.84 74.032 156.504 73.256 157.352 72.712C158.216 72.168 159.216 71.896 160.352 71.896ZM160.328 73.624C159.32 73.624 158.52 73.952 157.928 74.608C157.352 75.248 157.008 76.144 156.896 77.296H163.448C163.432 76.208 163.176 75.328 162.68 74.656C162.184 73.968 161.4 73.624 160.328 73.624ZM173.947 67.864C176.075 67.864 177.643 68.272 178.651 69.088C179.675 69.888 180.187 71.104 180.187 72.736C180.187 73.648 180.019 74.408 179.683 75.016C179.347 75.624 178.915 76.112 178.387 76.48C177.875 76.848 177.331 77.128 176.755 77.32L181.459 85H178.939L174.787 77.92H171.379V85H169.219V67.864H173.947ZM173.827 69.736H171.379V76.096H173.947C175.339 76.096 176.355 75.824 176.995 75.28C177.635 74.72 177.955 73.904 177.955 72.832C177.955 71.712 177.619 70.92 176.947 70.456C176.275 69.976 175.235 69.736 173.827 69.736ZM195.044 78.544C195.044 80.672 194.5 82.32 193.412 83.488C192.34 84.656 190.884 85.24 189.044 85.24C187.908 85.24 186.892 84.984 185.996 84.472C185.116 83.944 184.42 83.184 183.908 82.192C183.396 81.184 183.14 79.968 183.14 78.544C183.14 76.416 183.668 74.776 184.724 73.624C185.796 72.472 187.26 71.896 189.116 71.896C190.268 71.896 191.292 72.16 192.188 72.688C193.084 73.2 193.78 73.952 194.276 74.944C194.788 75.92 195.044 77.12 195.044 78.544ZM185.324 78.544C185.324 80.064 185.62 81.272 186.212 82.168C186.82 83.048 187.78 83.488 189.092 83.488C190.388 83.488 191.34 83.048 191.948 82.168C192.556 81.272 192.86 80.064 192.86 78.544C192.86 77.024 192.556 75.832 191.948 74.968C191.34 74.104 190.38 73.672 189.068 73.672C187.756 73.672 186.804 74.104 186.212 74.968C185.62 75.832 185.324 77.024 185.324 78.544ZM209.144 72.136V85H207.416L207.104 83.296H207.008C206.592 83.968 206.016 84.464 205.28 84.784C204.544 85.088 203.76 85.24 202.928 85.24C201.376 85.24 200.208 84.872 199.424 84.136C198.64 83.384 198.248 82.192 198.248 80.56V72.136H200.384V80.416C200.384 82.464 201.336 83.488 203.24 83.488C204.664 83.488 205.648 83.088 206.192 82.288C206.752 81.488 207.032 80.336 207.032 78.832V72.136H209.144ZM217.523 83.512C217.844 83.512 218.172 83.488 218.508 83.44C218.844 83.392 219.116 83.328 219.324 83.248V84.856C219.1 84.968 218.78 85.056 218.364 85.12C217.948 85.2 217.548 85.24 217.164 85.24C216.492 85.24 215.868 85.128 215.292 84.904C214.732 84.664 214.276 84.256 213.924 83.68C213.572 83.104 213.396 82.296 213.396 81.256V73.768H211.572V72.76L213.42 71.92L214.26 69.184H215.508V72.136H219.228V73.768H215.508V81.208C215.508 81.992 215.692 82.576 216.06 82.96C216.444 83.328 216.932 83.512 217.523 83.512ZM226.867 71.896C227.955 71.896 228.899 72.136 229.699 72.616C230.499 73.096 231.107 73.776 231.523 74.656C231.955 75.52 232.171 76.536 232.171 77.704V78.976H223.363C223.395 80.432 223.763 81.544 224.467 82.312C225.187 83.064 226.187 83.44 227.467 83.44C228.283 83.44 229.003 83.368 229.627 83.224C230.267 83.064 230.923 82.84 231.595 82.552V84.4C230.939 84.688 230.291 84.896 229.651 85.024C229.011 85.168 228.251 85.24 227.371 85.24C226.139 85.24 225.059 84.992 224.131 84.496C223.203 84 222.475 83.264 221.947 82.288C221.435 81.312 221.179 80.104 221.179 78.664C221.179 77.256 221.411 76.048 221.875 75.04C222.355 74.032 223.019 73.256 223.867 72.712C224.731 72.168 225.731 71.896 226.867 71.896ZM226.843 73.624C225.835 73.624 225.035 73.952 224.443 74.608C223.867 75.248 223.523 76.144 223.411 77.296H229.963C229.947 76.208 229.691 75.328 229.195 74.656C228.699 73.968 227.915 73.624 226.843 73.624ZM134.683 100.864C134.459 101.792 134.227 102.8 133.987 103.888C133.763 104.96 133.595 105.912 133.483 106.744H131.203L131.035 106.48C131.243 105.648 131.539 104.72 131.923 103.696C132.323 102.672 132.723 101.728 133.123 100.864H134.683ZM130.243 100.864C130.019 101.792 129.795 102.8 129.571 103.888C129.347 104.96 129.171 105.912 129.043 106.744H126.787L126.643 106.48C126.867 105.648 127.163 104.72 127.531 103.696C127.915 102.672 128.307 101.728 128.707 100.864H130.243ZM143.668 100.864L137.284 118H135.22L141.604 100.864H143.668ZM148.062 99.76V105.112C148.062 105.752 148.022 106.352 147.942 106.912H148.086C148.502 106.256 149.062 105.76 149.766 105.424C150.486 105.088 151.262 104.92 152.094 104.92C153.646 104.92 154.814 105.296 155.598 106.048C156.398 106.784 156.798 107.976 156.798 109.624V118H154.71V109.768C154.71 107.704 153.75 106.672 151.83 106.672C150.39 106.672 149.398 107.08 148.854 107.896C148.326 108.696 148.062 109.848 148.062 111.352V118H145.95V99.76H148.062ZM171.97 111.544C171.97 113.672 171.426 115.32 170.338 116.488C169.266 117.656 167.81 118.24 165.97 118.24C164.834 118.24 163.818 117.984 162.922 117.472C162.042 116.944 161.346 116.184 160.834 115.192C160.322 114.184 160.066 112.968 160.066 111.544C160.066 109.416 160.594 107.776 161.65 106.624C162.722 105.472 164.186 104.896 166.042 104.896C167.194 104.896 168.218 105.16 169.114 105.688C170.01 106.2 170.706 106.952 171.202 107.944C171.714 108.92 171.97 110.12 171.97 111.544ZM162.25 111.544C162.25 113.064 162.546 114.272 163.138 115.168C163.746 116.048 164.706 116.488 166.018 116.488C167.314 116.488 168.266 116.048 168.874 115.168C169.482 114.272 169.786 113.064 169.786 111.544C169.786 110.024 169.482 108.832 168.874 107.968C168.266 107.104 167.306 106.672 165.994 106.672C164.682 106.672 163.73 107.104 163.138 107.968C162.546 108.832 162.25 110.024 162.25 111.544ZM189.429 104.896C190.885 104.896 191.973 105.272 192.693 106.024C193.413 106.776 193.773 107.976 193.773 109.624V118H191.685V109.72C191.685 107.688 190.813 106.672 189.069 106.672C187.821 106.672 186.925 107.032 186.381 107.752C185.853 108.472 185.589 109.52 185.589 110.896V118H183.501V109.72C183.501 107.688 182.621 106.672 180.861 106.672C179.565 106.672 178.669 107.072 178.173 107.872C177.677 108.672 177.429 109.824 177.429 111.328V118H175.317V105.136H177.021L177.333 106.888H177.453C177.853 106.216 178.389 105.72 179.061 105.4C179.749 105.064 180.477 104.896 181.245 104.896C183.261 104.896 184.573 105.616 185.181 107.056H185.301C185.733 106.32 186.317 105.776 187.053 105.424C187.789 105.072 188.581 104.896 189.429 104.896ZM202.715 104.896C203.803 104.896 204.747 105.136 205.547 105.616C206.347 106.096 206.955 106.776 207.371 107.656C207.803 108.52 208.019 109.536 208.019 110.704V111.976H199.211C199.243 113.432 199.611 114.544 200.315 115.312C201.035 116.064 202.035 116.44 203.315 116.44C204.131 116.44 204.851 116.368 205.475 116.224C206.115 116.064 206.771 115.84 207.443 115.552V117.4C206.787 117.688 206.139 117.896 205.499 118.024C204.859 118.168 204.099 118.24 203.219 118.24C201.987 118.24 200.907 117.992 199.979 117.496C199.051 117 198.323 116.264 197.795 115.288C197.283 114.312 197.027 113.104 197.027 111.664C197.027 110.256 197.259 109.048 197.723 108.04C198.203 107.032 198.867 106.256 199.715 105.712C200.579 105.168 201.579 104.896 202.715 104.896ZM202.691 106.624C201.683 106.624 200.883 106.952 200.291 107.608C199.715 108.248 199.371 109.144 199.259 110.296H205.811C205.795 109.208 205.539 108.328 205.043 107.656C204.547 106.968 203.763 106.624 202.691 106.624ZM217.18 100.864L217.348 101.128C217.14 101.976 216.836 102.912 216.436 103.936C216.052 104.96 215.668 105.896 215.284 106.744H213.7C213.844 106.152 213.996 105.504 214.156 104.8C214.316 104.096 214.46 103.408 214.588 102.736C214.732 102.048 214.844 101.424 214.924 100.864H217.18ZM212.74 100.864L212.908 101.128C212.684 101.976 212.38 102.912 211.996 103.936C211.612 104.96 211.228 105.896 210.844 106.744H209.308C209.468 106.152 209.62 105.504 209.764 104.8C209.908 104.096 210.044 103.408 210.172 102.736C210.3 102.048 210.404 101.424 210.484 100.864H212.74Z" fill="black"/>
+<rect x="295" y="2" width="182" height="182" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M331.458 67.864C333.698 67.864 335.33 68.304 336.354 69.184C337.378 70.064 337.89 71.304 337.89 72.904C337.89 73.848 337.674 74.736 337.242 75.568C336.826 76.384 336.114 77.048 335.106 77.56C334.114 78.072 332.762 78.328 331.05 78.328H329.082V85H326.922V67.864H331.458ZM331.266 69.712H329.082V76.48H330.81C332.442 76.48 333.658 76.216 334.458 75.688C335.258 75.16 335.658 74.264 335.658 73C335.658 71.896 335.298 71.072 334.578 70.528C333.874 69.984 332.77 69.712 331.266 69.712ZM346.037 71.92C347.605 71.92 348.765 72.264 349.517 72.952C350.269 73.64 350.645 74.736 350.645 76.24V85H349.109L348.701 83.176H348.605C348.045 83.88 347.453 84.4 346.829 84.736C346.205 85.072 345.357 85.24 344.285 85.24C343.117 85.24 342.149 84.936 341.381 84.328C340.613 83.704 340.229 82.736 340.229 81.424C340.229 80.144 340.733 79.16 341.741 78.472C342.749 77.768 344.301 77.384 346.397 77.32L348.581 77.248V76.48C348.581 75.408 348.349 74.664 347.885 74.248C347.421 73.832 346.765 73.624 345.917 73.624C345.245 73.624 344.605 73.728 343.997 73.936C343.389 74.128 342.821 74.352 342.293 74.608L341.645 73.024C342.205 72.72 342.869 72.464 343.637 72.256C344.405 72.032 345.205 71.92 346.037 71.92ZM348.557 78.712L346.661 78.784C345.061 78.848 343.949 79.104 343.325 79.552C342.717 80 342.413 80.632 342.413 81.448C342.413 82.168 342.629 82.696 343.061 83.032C343.509 83.368 344.077 83.536 344.765 83.536C345.837 83.536 346.733 83.24 347.453 82.648C348.189 82.04 348.557 81.112 348.557 79.864V78.712ZM359.178 71.896C360.026 71.896 360.786 72.056 361.458 72.376C362.146 72.696 362.73 73.184 363.21 73.84H363.33L363.618 72.136H365.298V85.216C365.298 87.056 364.826 88.44 363.882 89.368C362.954 90.296 361.506 90.76 359.538 90.76C357.65 90.76 356.106 90.488 354.906 89.944V88C356.17 88.672 357.754 89.008 359.658 89.008C360.762 89.008 361.626 88.68 362.25 88.024C362.89 87.384 363.21 86.504 363.21 85.384V84.88C363.21 84.688 363.218 84.416 363.234 84.064C363.25 83.696 363.266 83.44 363.282 83.296H363.186C362.322 84.592 360.994 85.24 359.202 85.24C357.538 85.24 356.234 84.656 355.29 83.488C354.362 82.32 353.898 80.688 353.898 78.592C353.898 76.544 354.362 74.92 355.29 73.72C356.234 72.504 357.53 71.896 359.178 71.896ZM359.466 73.672C358.394 73.672 357.562 74.104 356.97 74.968C356.378 75.816 356.082 77.032 356.082 78.616C356.082 80.2 356.37 81.416 356.946 82.264C357.522 83.096 358.378 83.512 359.514 83.512C360.81 83.512 361.754 83.168 362.346 82.48C362.938 81.776 363.234 80.648 363.234 79.096V78.592C363.234 76.848 362.93 75.592 362.322 74.824C361.714 74.056 360.762 73.672 359.466 73.672ZM374.352 71.896C375.44 71.896 376.384 72.136 377.184 72.616C377.984 73.096 378.592 73.776 379.008 74.656C379.44 75.52 379.656 76.536 379.656 77.704V78.976H370.848C370.88 80.432 371.248 81.544 371.952 82.312C372.672 83.064 373.672 83.44 374.952 83.44C375.768 83.44 376.488 83.368 377.112 83.224C377.752 83.064 378.408 82.84 379.08 82.552V84.4C378.424 84.688 377.776 84.896 377.136 85.024C376.496 85.168 375.736 85.24 374.856 85.24C373.624 85.24 372.544 84.992 371.616 84.496C370.688 84 369.96 83.264 369.432 82.288C368.92 81.312 368.664 80.104 368.664 78.664C368.664 77.256 368.896 76.048 369.36 75.04C369.84 74.032 370.504 73.256 371.352 72.712C372.216 72.168 373.216 71.896 374.352 71.896ZM374.328 73.624C373.32 73.624 372.52 73.952 371.928 74.608C371.352 75.248 371.008 76.144 370.896 77.296H377.448C377.432 76.208 377.176 75.328 376.68 74.656C376.184 73.968 375.4 73.624 374.328 73.624ZM387.947 67.864C390.075 67.864 391.643 68.272 392.651 69.088C393.675 69.888 394.187 71.104 394.187 72.736C394.187 73.648 394.019 74.408 393.683 75.016C393.347 75.624 392.915 76.112 392.387 76.48C391.875 76.848 391.331 77.128 390.755 77.32L395.459 85H392.939L388.787 77.92H385.379V85H383.219V67.864H387.947ZM387.827 69.736H385.379V76.096H387.947C389.339 76.096 390.355 75.824 390.995 75.28C391.635 74.72 391.955 73.904 391.955 72.832C391.955 71.712 391.619 70.92 390.947 70.456C390.275 69.976 389.235 69.736 387.827 69.736ZM409.044 78.544C409.044 80.672 408.5 82.32 407.412 83.488C406.34 84.656 404.884 85.24 403.044 85.24C401.908 85.24 400.892 84.984 399.996 84.472C399.116 83.944 398.42 83.184 397.908 82.192C397.396 81.184 397.14 79.968 397.14 78.544C397.14 76.416 397.668 74.776 398.724 73.624C399.796 72.472 401.26 71.896 403.116 71.896C404.268 71.896 405.292 72.16 406.188 72.688C407.084 73.2 407.78 73.952 408.276 74.944C408.788 75.92 409.044 77.12 409.044 78.544ZM399.324 78.544C399.324 80.064 399.62 81.272 400.212 82.168C400.82 83.048 401.78 83.488 403.092 83.488C404.388 83.488 405.34 83.048 405.948 82.168C406.556 81.272 406.86 80.064 406.86 78.544C406.86 77.024 406.556 75.832 405.948 74.968C405.34 74.104 404.38 73.672 403.068 73.672C401.756 73.672 400.804 74.104 400.212 74.968C399.62 75.832 399.324 77.024 399.324 78.544ZM423.144 72.136V85H421.416L421.104 83.296H421.008C420.592 83.968 420.016 84.464 419.28 84.784C418.544 85.088 417.76 85.24 416.928 85.24C415.376 85.24 414.208 84.872 413.424 84.136C412.64 83.384 412.248 82.192 412.248 80.56V72.136H414.384V80.416C414.384 82.464 415.336 83.488 417.24 83.488C418.664 83.488 419.648 83.088 420.192 82.288C420.752 81.488 421.032 80.336 421.032 78.832V72.136H423.144ZM431.523 83.512C431.844 83.512 432.172 83.488 432.508 83.44C432.844 83.392 433.116 83.328 433.324 83.248V84.856C433.1 84.968 432.78 85.056 432.364 85.12C431.948 85.2 431.548 85.24 431.164 85.24C430.492 85.24 429.868 85.128 429.292 84.904C428.732 84.664 428.276 84.256 427.924 83.68C427.572 83.104 427.396 82.296 427.396 81.256V73.768H425.572V72.76L427.42 71.92L428.26 69.184H429.508V72.136H433.228V73.768H429.508V81.208C429.508 81.992 429.692 82.576 430.06 82.96C430.444 83.328 430.932 83.512 431.523 83.512ZM440.867 71.896C441.955 71.896 442.899 72.136 443.699 72.616C444.499 73.096 445.107 73.776 445.523 74.656C445.955 75.52 446.171 76.536 446.171 77.704V78.976H437.363C437.395 80.432 437.763 81.544 438.467 82.312C439.187 83.064 440.187 83.44 441.467 83.44C442.283 83.44 443.003 83.368 443.627 83.224C444.267 83.064 444.923 82.84 445.595 82.552V84.4C444.939 84.688 444.291 84.896 443.651 85.024C443.011 85.168 442.251 85.24 441.371 85.24C440.139 85.24 439.059 84.992 438.131 84.496C437.203 84 436.475 83.264 435.947 82.288C435.435 81.312 435.179 80.104 435.179 78.664C435.179 77.256 435.411 76.048 435.875 75.04C436.355 74.032 437.019 73.256 437.867 72.712C438.731 72.168 439.731 71.896 440.867 71.896ZM440.843 73.624C439.835 73.624 439.035 73.952 438.443 74.608C437.867 75.248 437.523 76.144 437.411 77.296H443.963C443.947 76.208 443.691 75.328 443.195 74.656C442.699 73.968 441.915 73.624 440.843 73.624ZM353.101 100.864C352.877 101.792 352.645 102.8 352.405 103.888C352.181 104.96 352.013 105.912 351.901 106.744H349.621L349.453 106.48C349.661 105.648 349.957 104.72 350.341 103.696C350.741 102.672 351.141 101.728 351.541 100.864H353.101ZM348.661 100.864C348.437 101.792 348.213 102.8 347.989 103.888C347.765 104.96 347.589 105.912 347.461 106.744H345.205L345.061 106.48C345.285 105.648 345.581 104.72 345.949 103.696C346.333 102.672 346.725 101.728 347.125 100.864H348.661ZM362.086 100.864L355.702 118H353.638L360.022 100.864H362.086ZM366.48 118H364.368V99.76H366.48V118ZM381.74 111.544C381.74 113.672 381.196 115.32 380.108 116.488C379.036 117.656 377.58 118.24 375.74 118.24C374.604 118.24 373.588 117.984 372.692 117.472C371.812 116.944 371.116 116.184 370.604 115.192C370.092 114.184 369.836 112.968 369.836 111.544C369.836 109.416 370.364 107.776 371.42 106.624C372.492 105.472 373.956 104.896 375.812 104.896C376.964 104.896 377.988 105.16 378.884 105.688C379.78 106.2 380.476 106.952 380.972 107.944C381.484 108.92 381.74 110.12 381.74 111.544ZM372.02 111.544C372.02 113.064 372.316 114.272 372.908 115.168C373.516 116.048 374.476 116.488 375.788 116.488C377.084 116.488 378.036 116.048 378.644 115.168C379.252 114.272 379.556 113.064 379.556 111.544C379.556 110.024 379.252 108.832 378.644 107.968C378.036 107.104 377.076 106.672 375.764 106.672C374.452 106.672 373.5 107.104 372.908 107.968C372.316 108.832 372.02 110.024 372.02 111.544ZM389.647 104.896C390.495 104.896 391.255 105.056 391.927 105.376C392.615 105.696 393.199 106.184 393.679 106.84H393.799L394.087 105.136H395.767V118.216C395.767 120.056 395.295 121.44 394.351 122.368C393.423 123.296 391.975 123.76 390.007 123.76C388.119 123.76 386.575 123.488 385.375 122.944V121C386.639 121.672 388.223 122.008 390.127 122.008C391.231 122.008 392.095 121.68 392.719 121.024C393.359 120.384 393.679 119.504 393.679 118.384V117.88C393.679 117.688 393.687 117.416 393.703 117.064C393.719 116.696 393.735 116.44 393.751 116.296H393.655C392.791 117.592 391.463 118.24 389.671 118.24C388.007 118.24 386.703 117.656 385.759 116.488C384.831 115.32 384.367 113.688 384.367 111.592C384.367 109.544 384.831 107.92 385.759 106.72C386.703 105.504 387.999 104.896 389.647 104.896ZM389.935 106.672C388.863 106.672 388.031 107.104 387.439 107.968C386.847 108.816 386.551 110.032 386.551 111.616C386.551 113.2 386.839 114.416 387.415 115.264C387.991 116.096 388.847 116.512 389.983 116.512C391.279 116.512 392.223 116.168 392.815 115.48C393.407 114.776 393.703 113.648 393.703 112.096V111.592C393.703 109.848 393.399 108.592 392.791 107.824C392.183 107.056 391.231 106.672 389.935 106.672ZM400.933 100.312C401.253 100.312 401.533 100.424 401.773 100.648C402.029 100.856 402.157 101.192 402.157 101.656C402.157 102.12 402.029 102.464 401.773 102.688C401.533 102.896 401.253 103 400.933 103C400.581 103 400.285 102.896 400.045 102.688C399.805 102.464 399.685 102.12 399.685 101.656C399.685 101.192 399.805 100.856 400.045 100.648C400.285 100.424 400.581 100.312 400.933 100.312ZM401.965 105.136V118H399.853V105.136H401.965ZM412.232 104.896C413.768 104.896 414.928 105.272 415.712 106.024C416.496 106.776 416.888 107.976 416.888 109.624V118H414.8V109.768C414.8 107.704 413.84 106.672 411.92 106.672C410.496 106.672 409.512 107.072 408.968 107.872C408.424 108.672 408.152 109.824 408.152 111.328V118H406.04V105.136H407.744L408.056 106.888H408.176C408.592 106.216 409.168 105.72 409.904 105.4C410.64 105.064 411.416 104.896 412.232 104.896ZM426.762 100.864L426.93 101.128C426.722 101.976 426.418 102.912 426.018 103.936C425.634 104.96 425.25 105.896 424.866 106.744H423.282C423.426 106.152 423.578 105.504 423.738 104.8C423.898 104.096 424.042 103.408 424.17 102.736C424.314 102.048 424.426 101.424 424.506 100.864H426.762ZM422.322 100.864L422.49 101.128C422.266 101.976 421.962 102.912 421.578 103.936C421.194 104.96 420.81 105.896 420.426 106.744H418.89C419.05 106.152 419.202 105.504 419.346 104.8C419.49 104.096 419.626 103.408 419.754 102.736C419.882 102.048 419.986 101.424 420.066 100.864H422.322Z" fill="black"/>
+<rect x="508" y="2" width="182" height="182" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M544.458 67.864C546.698 67.864 548.33 68.304 549.354 69.184C550.378 70.064 550.89 71.304 550.89 72.904C550.89 73.848 550.674 74.736 550.242 75.568C549.826 76.384 549.114 77.048 548.106 77.56C547.114 78.072 545.762 78.328 544.05 78.328H542.082V85H539.922V67.864H544.458ZM544.266 69.712H542.082V76.48H543.81C545.442 76.48 546.658 76.216 547.458 75.688C548.258 75.16 548.658 74.264 548.658 73C548.658 71.896 548.298 71.072 547.578 70.528C546.874 69.984 545.77 69.712 544.266 69.712ZM559.037 71.92C560.605 71.92 561.765 72.264 562.517 72.952C563.269 73.64 563.645 74.736 563.645 76.24V85H562.109L561.701 83.176H561.605C561.045 83.88 560.453 84.4 559.829 84.736C559.205 85.072 558.357 85.24 557.285 85.24C556.117 85.24 555.149 84.936 554.381 84.328C553.613 83.704 553.229 82.736 553.229 81.424C553.229 80.144 553.733 79.16 554.741 78.472C555.749 77.768 557.301 77.384 559.397 77.32L561.581 77.248V76.48C561.581 75.408 561.349 74.664 560.885 74.248C560.421 73.832 559.765 73.624 558.917 73.624C558.245 73.624 557.605 73.728 556.997 73.936C556.389 74.128 555.821 74.352 555.293 74.608L554.645 73.024C555.205 72.72 555.869 72.464 556.637 72.256C557.405 72.032 558.205 71.92 559.037 71.92ZM561.557 78.712L559.661 78.784C558.061 78.848 556.949 79.104 556.325 79.552C555.717 80 555.413 80.632 555.413 81.448C555.413 82.168 555.629 82.696 556.061 83.032C556.509 83.368 557.077 83.536 557.765 83.536C558.837 83.536 559.733 83.24 560.453 82.648C561.189 82.04 561.557 81.112 561.557 79.864V78.712ZM572.178 71.896C573.026 71.896 573.786 72.056 574.458 72.376C575.146 72.696 575.73 73.184 576.21 73.84H576.33L576.618 72.136H578.298V85.216C578.298 87.056 577.826 88.44 576.882 89.368C575.954 90.296 574.506 90.76 572.538 90.76C570.65 90.76 569.106 90.488 567.906 89.944V88C569.17 88.672 570.754 89.008 572.658 89.008C573.762 89.008 574.626 88.68 575.25 88.024C575.89 87.384 576.21 86.504 576.21 85.384V84.88C576.21 84.688 576.218 84.416 576.234 84.064C576.25 83.696 576.266 83.44 576.282 83.296H576.186C575.322 84.592 573.994 85.24 572.202 85.24C570.538 85.24 569.234 84.656 568.29 83.488C567.362 82.32 566.898 80.688 566.898 78.592C566.898 76.544 567.362 74.92 568.29 73.72C569.234 72.504 570.53 71.896 572.178 71.896ZM572.466 73.672C571.394 73.672 570.562 74.104 569.97 74.968C569.378 75.816 569.082 77.032 569.082 78.616C569.082 80.2 569.37 81.416 569.946 82.264C570.522 83.096 571.378 83.512 572.514 83.512C573.81 83.512 574.754 83.168 575.346 82.48C575.938 81.776 576.234 80.648 576.234 79.096V78.592C576.234 76.848 575.93 75.592 575.322 74.824C574.714 74.056 573.762 73.672 572.466 73.672ZM587.352 71.896C588.44 71.896 589.384 72.136 590.184 72.616C590.984 73.096 591.592 73.776 592.008 74.656C592.44 75.52 592.656 76.536 592.656 77.704V78.976H583.848C583.88 80.432 584.248 81.544 584.952 82.312C585.672 83.064 586.672 83.44 587.952 83.44C588.768 83.44 589.488 83.368 590.112 83.224C590.752 83.064 591.408 82.84 592.08 82.552V84.4C591.424 84.688 590.776 84.896 590.136 85.024C589.496 85.168 588.736 85.24 587.856 85.24C586.624 85.24 585.544 84.992 584.616 84.496C583.688 84 582.96 83.264 582.432 82.288C581.92 81.312 581.664 80.104 581.664 78.664C581.664 77.256 581.896 76.048 582.36 75.04C582.84 74.032 583.504 73.256 584.352 72.712C585.216 72.168 586.216 71.896 587.352 71.896ZM587.328 73.624C586.32 73.624 585.52 73.952 584.928 74.608C584.352 75.248 584.008 76.144 583.896 77.296H590.448C590.432 76.208 590.176 75.328 589.68 74.656C589.184 73.968 588.4 73.624 587.328 73.624ZM600.947 67.864C603.075 67.864 604.643 68.272 605.651 69.088C606.675 69.888 607.187 71.104 607.187 72.736C607.187 73.648 607.019 74.408 606.683 75.016C606.347 75.624 605.915 76.112 605.387 76.48C604.875 76.848 604.331 77.128 603.755 77.32L608.459 85H605.939L601.787 77.92H598.379V85H596.219V67.864H600.947ZM600.827 69.736H598.379V76.096H600.947C602.339 76.096 603.355 75.824 603.995 75.28C604.635 74.72 604.955 73.904 604.955 72.832C604.955 71.712 604.619 70.92 603.947 70.456C603.275 69.976 602.235 69.736 600.827 69.736ZM622.044 78.544C622.044 80.672 621.5 82.32 620.412 83.488C619.34 84.656 617.884 85.24 616.044 85.24C614.908 85.24 613.892 84.984 612.996 84.472C612.116 83.944 611.42 83.184 610.908 82.192C610.396 81.184 610.14 79.968 610.14 78.544C610.14 76.416 610.668 74.776 611.724 73.624C612.796 72.472 614.26 71.896 616.116 71.896C617.268 71.896 618.292 72.16 619.188 72.688C620.084 73.2 620.78 73.952 621.276 74.944C621.788 75.92 622.044 77.12 622.044 78.544ZM612.324 78.544C612.324 80.064 612.62 81.272 613.212 82.168C613.82 83.048 614.78 83.488 616.092 83.488C617.388 83.488 618.34 83.048 618.948 82.168C619.556 81.272 619.86 80.064 619.86 78.544C619.86 77.024 619.556 75.832 618.948 74.968C618.34 74.104 617.38 73.672 616.068 73.672C614.756 73.672 613.804 74.104 613.212 74.968C612.62 75.832 612.324 77.024 612.324 78.544ZM636.144 72.136V85H634.416L634.104 83.296H634.008C633.592 83.968 633.016 84.464 632.28 84.784C631.544 85.088 630.76 85.24 629.928 85.24C628.376 85.24 627.208 84.872 626.424 84.136C625.64 83.384 625.248 82.192 625.248 80.56V72.136H627.384V80.416C627.384 82.464 628.336 83.488 630.24 83.488C631.664 83.488 632.648 83.088 633.192 82.288C633.752 81.488 634.032 80.336 634.032 78.832V72.136H636.144ZM644.523 83.512C644.844 83.512 645.172 83.488 645.508 83.44C645.844 83.392 646.116 83.328 646.324 83.248V84.856C646.1 84.968 645.78 85.056 645.364 85.12C644.948 85.2 644.548 85.24 644.164 85.24C643.492 85.24 642.868 85.128 642.292 84.904C641.732 84.664 641.276 84.256 640.924 83.68C640.572 83.104 640.396 82.296 640.396 81.256V73.768H638.572V72.76L640.42 71.92L641.26 69.184H642.508V72.136H646.228V73.768H642.508V81.208C642.508 81.992 642.692 82.576 643.06 82.96C643.444 83.328 643.932 83.512 644.523 83.512ZM653.867 71.896C654.955 71.896 655.899 72.136 656.699 72.616C657.499 73.096 658.107 73.776 658.523 74.656C658.955 75.52 659.171 76.536 659.171 77.704V78.976H650.363C650.395 80.432 650.763 81.544 651.467 82.312C652.187 83.064 653.187 83.44 654.467 83.44C655.283 83.44 656.003 83.368 656.627 83.224C657.267 83.064 657.923 82.84 658.595 82.552V84.4C657.939 84.688 657.291 84.896 656.651 85.024C656.011 85.168 655.251 85.24 654.371 85.24C653.139 85.24 652.059 84.992 651.131 84.496C650.203 84 649.475 83.264 648.947 82.288C648.435 81.312 648.179 80.104 648.179 78.664C648.179 77.256 648.411 76.048 648.875 75.04C649.355 74.032 650.019 73.256 650.867 72.712C651.731 72.168 652.731 71.896 653.867 71.896ZM653.843 73.624C652.835 73.624 652.035 73.952 651.443 74.608C650.867 75.248 650.523 76.144 650.411 77.296H656.963C656.947 76.208 656.691 75.328 656.195 74.656C655.699 73.968 654.915 73.624 653.843 73.624ZM572.863 100.864C572.639 101.792 572.407 102.8 572.167 103.888C571.943 104.96 571.775 105.912 571.663 106.744H569.383L569.215 106.48C569.423 105.648 569.719 104.72 570.103 103.696C570.503 102.672 570.903 101.728 571.303 100.864H572.863ZM568.423 100.864C568.199 101.792 567.975 102.8 567.751 103.888C567.527 104.96 567.351 105.912 567.223 106.744H564.967L564.823 106.48C565.047 105.648 565.343 104.72 565.711 103.696C566.095 102.672 566.487 101.728 566.887 100.864H568.423ZM581.848 100.864L575.464 118H573.4L579.784 100.864H581.848ZM589.002 104.92C590.57 104.92 591.73 105.264 592.482 105.952C593.234 106.64 593.61 107.736 593.61 109.24V118H592.074L591.666 116.176H591.57C591.01 116.88 590.418 117.4 589.794 117.736C589.17 118.072 588.322 118.24 587.25 118.24C586.082 118.24 585.114 117.936 584.346 117.328C583.578 116.704 583.194 115.736 583.194 114.424C583.194 113.144 583.698 112.16 584.706 111.472C585.714 110.768 587.266 110.384 589.362 110.32L591.546 110.248V109.48C591.546 108.408 591.314 107.664 590.85 107.248C590.386 106.832 589.73 106.624 588.882 106.624C588.21 106.624 587.57 106.728 586.962 106.936C586.354 107.128 585.786 107.352 585.258 107.608L584.61 106.024C585.17 105.72 585.834 105.464 586.602 105.256C587.37 105.032 588.17 104.92 589.002 104.92ZM591.522 111.712L589.626 111.784C588.026 111.848 586.914 112.104 586.29 112.552C585.682 113 585.378 113.632 585.378 114.448C585.378 115.168 585.594 115.696 586.026 116.032C586.474 116.368 587.042 116.536 587.73 116.536C588.802 116.536 589.698 116.24 590.418 115.648C591.154 115.04 591.522 114.112 591.522 112.864V111.712ZM603.703 104.896C605.287 104.896 606.559 105.448 607.519 106.552C608.495 107.656 608.983 109.32 608.983 111.544C608.983 113.736 608.495 115.4 607.519 116.536C606.559 117.672 605.279 118.24 603.679 118.24C602.687 118.24 601.863 118.056 601.207 117.688C600.567 117.32 600.063 116.88 599.695 116.368H599.551C599.567 116.64 599.591 116.984 599.623 117.4C599.671 117.816 599.695 118.176 599.695 118.48V123.76H597.583V105.136H599.311L599.599 106.888H599.695C600.079 106.328 600.583 105.856 601.207 105.472C601.831 105.088 602.663 104.896 603.703 104.896ZM603.319 106.672C602.007 106.672 601.079 107.04 600.535 107.776C599.991 108.512 599.711 109.632 599.695 111.136V111.544C599.695 113.128 599.951 114.352 600.463 115.216C600.991 116.064 601.959 116.488 603.367 116.488C604.135 116.488 604.775 116.28 605.287 115.864C605.799 115.432 606.175 114.84 606.415 114.088C606.671 113.336 606.799 112.48 606.799 111.52C606.799 110.048 606.511 108.872 605.935 107.992C605.375 107.112 604.503 106.672 603.319 106.672ZM618.469 104.896C620.053 104.896 621.325 105.448 622.285 106.552C623.261 107.656 623.749 109.32 623.749 111.544C623.749 113.736 623.261 115.4 622.285 116.536C621.325 117.672 620.045 118.24 618.445 118.24C617.453 118.24 616.629 118.056 615.973 117.688C615.333 117.32 614.829 116.88 614.461 116.368H614.317C614.333 116.64 614.357 116.984 614.389 117.4C614.437 117.816 614.461 118.176 614.461 118.48V123.76H612.349V105.136H614.077L614.365 106.888H614.461C614.845 106.328 615.349 105.856 615.973 105.472C616.597 105.088 617.429 104.896 618.469 104.896ZM618.085 106.672C616.773 106.672 615.845 107.04 615.301 107.776C614.757 108.512 614.477 109.632 614.461 111.136V111.544C614.461 113.128 614.717 114.352 615.229 115.216C615.757 116.064 616.725 116.488 618.133 116.488C618.901 116.488 619.541 116.28 620.053 115.864C620.565 115.432 620.941 114.84 621.181 114.088C621.437 113.336 621.565 112.48 621.565 111.52C621.565 110.048 621.277 108.872 620.701 107.992C620.141 107.112 619.269 106.672 618.085 106.672ZM633 100.864L633.168 101.128C632.96 101.976 632.656 102.912 632.256 103.936C631.872 104.96 631.488 105.896 631.104 106.744H629.52C629.664 106.152 629.816 105.504 629.976 104.8C630.136 104.096 630.28 103.408 630.408 102.736C630.552 102.048 630.664 101.424 630.744 100.864H633ZM628.56 100.864L628.728 101.128C628.504 101.976 628.2 102.912 627.816 103.936C627.432 104.96 627.048 105.896 626.664 106.744H625.128C625.288 106.152 625.44 105.504 625.584 104.8C625.728 104.096 625.864 103.408 625.992 102.736C626.12 102.048 626.224 101.424 626.304 100.864H628.56Z" fill="black"/>
+<rect x="722" y="2" width="182" height="182" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M758.458 67.864C760.698 67.864 762.33 68.304 763.354 69.184C764.378 70.064 764.89 71.304 764.89 72.904C764.89 73.848 764.674 74.736 764.242 75.568C763.826 76.384 763.114 77.048 762.106 77.56C761.114 78.072 759.762 78.328 758.05 78.328H756.082V85H753.922V67.864H758.458ZM758.266 69.712H756.082V76.48H757.81C759.442 76.48 760.658 76.216 761.458 75.688C762.258 75.16 762.658 74.264 762.658 73C762.658 71.896 762.298 71.072 761.578 70.528C760.874 69.984 759.77 69.712 758.266 69.712ZM773.037 71.92C774.605 71.92 775.765 72.264 776.517 72.952C777.269 73.64 777.645 74.736 777.645 76.24V85H776.109L775.701 83.176H775.605C775.045 83.88 774.453 84.4 773.829 84.736C773.205 85.072 772.357 85.24 771.285 85.24C770.117 85.24 769.149 84.936 768.381 84.328C767.613 83.704 767.229 82.736 767.229 81.424C767.229 80.144 767.733 79.16 768.741 78.472C769.749 77.768 771.301 77.384 773.397 77.32L775.581 77.248V76.48C775.581 75.408 775.349 74.664 774.885 74.248C774.421 73.832 773.765 73.624 772.917 73.624C772.245 73.624 771.605 73.728 770.997 73.936C770.389 74.128 769.821 74.352 769.293 74.608L768.645 73.024C769.205 72.72 769.869 72.464 770.637 72.256C771.405 72.032 772.205 71.92 773.037 71.92ZM775.557 78.712L773.661 78.784C772.061 78.848 770.949 79.104 770.325 79.552C769.717 80 769.413 80.632 769.413 81.448C769.413 82.168 769.629 82.696 770.061 83.032C770.509 83.368 771.077 83.536 771.765 83.536C772.837 83.536 773.733 83.24 774.453 82.648C775.189 82.04 775.557 81.112 775.557 79.864V78.712ZM786.178 71.896C787.026 71.896 787.786 72.056 788.458 72.376C789.146 72.696 789.73 73.184 790.21 73.84H790.33L790.618 72.136H792.298V85.216C792.298 87.056 791.826 88.44 790.882 89.368C789.954 90.296 788.506 90.76 786.538 90.76C784.65 90.76 783.106 90.488 781.906 89.944V88C783.17 88.672 784.754 89.008 786.658 89.008C787.762 89.008 788.626 88.68 789.25 88.024C789.89 87.384 790.21 86.504 790.21 85.384V84.88C790.21 84.688 790.218 84.416 790.234 84.064C790.25 83.696 790.266 83.44 790.282 83.296H790.186C789.322 84.592 787.994 85.24 786.202 85.24C784.538 85.24 783.234 84.656 782.29 83.488C781.362 82.32 780.898 80.688 780.898 78.592C780.898 76.544 781.362 74.92 782.29 73.72C783.234 72.504 784.53 71.896 786.178 71.896ZM786.466 73.672C785.394 73.672 784.562 74.104 783.97 74.968C783.378 75.816 783.082 77.032 783.082 78.616C783.082 80.2 783.37 81.416 783.946 82.264C784.522 83.096 785.378 83.512 786.514 83.512C787.81 83.512 788.754 83.168 789.346 82.48C789.938 81.776 790.234 80.648 790.234 79.096V78.592C790.234 76.848 789.93 75.592 789.322 74.824C788.714 74.056 787.762 73.672 786.466 73.672ZM801.352 71.896C802.44 71.896 803.384 72.136 804.184 72.616C804.984 73.096 805.592 73.776 806.008 74.656C806.44 75.52 806.656 76.536 806.656 77.704V78.976H797.848C797.88 80.432 798.248 81.544 798.952 82.312C799.672 83.064 800.672 83.44 801.952 83.44C802.768 83.44 803.488 83.368 804.112 83.224C804.752 83.064 805.408 82.84 806.08 82.552V84.4C805.424 84.688 804.776 84.896 804.136 85.024C803.496 85.168 802.736 85.24 801.856 85.24C800.624 85.24 799.544 84.992 798.616 84.496C797.688 84 796.96 83.264 796.432 82.288C795.92 81.312 795.664 80.104 795.664 78.664C795.664 77.256 795.896 76.048 796.36 75.04C796.84 74.032 797.504 73.256 798.352 72.712C799.216 72.168 800.216 71.896 801.352 71.896ZM801.328 73.624C800.32 73.624 799.52 73.952 798.928 74.608C798.352 75.248 798.008 76.144 797.896 77.296H804.448C804.432 76.208 804.176 75.328 803.68 74.656C803.184 73.968 802.4 73.624 801.328 73.624ZM814.947 67.864C817.075 67.864 818.643 68.272 819.651 69.088C820.675 69.888 821.187 71.104 821.187 72.736C821.187 73.648 821.019 74.408 820.683 75.016C820.347 75.624 819.915 76.112 819.387 76.48C818.875 76.848 818.331 77.128 817.755 77.32L822.459 85H819.939L815.787 77.92H812.379V85H810.219V67.864H814.947ZM814.827 69.736H812.379V76.096H814.947C816.339 76.096 817.355 75.824 817.995 75.28C818.635 74.72 818.955 73.904 818.955 72.832C818.955 71.712 818.619 70.92 817.947 70.456C817.275 69.976 816.235 69.736 814.827 69.736ZM836.044 78.544C836.044 80.672 835.5 82.32 834.412 83.488C833.34 84.656 831.884 85.24 830.044 85.24C828.908 85.24 827.892 84.984 826.996 84.472C826.116 83.944 825.42 83.184 824.908 82.192C824.396 81.184 824.14 79.968 824.14 78.544C824.14 76.416 824.668 74.776 825.724 73.624C826.796 72.472 828.26 71.896 830.116 71.896C831.268 71.896 832.292 72.16 833.188 72.688C834.084 73.2 834.78 73.952 835.276 74.944C835.788 75.92 836.044 77.12 836.044 78.544ZM826.324 78.544C826.324 80.064 826.62 81.272 827.212 82.168C827.82 83.048 828.78 83.488 830.092 83.488C831.388 83.488 832.34 83.048 832.948 82.168C833.556 81.272 833.86 80.064 833.86 78.544C833.86 77.024 833.556 75.832 832.948 74.968C832.34 74.104 831.38 73.672 830.068 73.672C828.756 73.672 827.804 74.104 827.212 74.968C826.62 75.832 826.324 77.024 826.324 78.544ZM850.144 72.136V85H848.416L848.104 83.296H848.008C847.592 83.968 847.016 84.464 846.28 84.784C845.544 85.088 844.76 85.24 843.928 85.24C842.376 85.24 841.208 84.872 840.424 84.136C839.64 83.384 839.248 82.192 839.248 80.56V72.136H841.384V80.416C841.384 82.464 842.336 83.488 844.24 83.488C845.664 83.488 846.648 83.088 847.192 82.288C847.752 81.488 848.032 80.336 848.032 78.832V72.136H850.144ZM858.523 83.512C858.844 83.512 859.172 83.488 859.508 83.44C859.844 83.392 860.116 83.328 860.324 83.248V84.856C860.1 84.968 859.78 85.056 859.364 85.12C858.948 85.2 858.548 85.24 858.164 85.24C857.492 85.24 856.868 85.128 856.292 84.904C855.732 84.664 855.276 84.256 854.924 83.68C854.572 83.104 854.396 82.296 854.396 81.256V73.768H852.572V72.76L854.42 71.92L855.26 69.184H856.508V72.136H860.228V73.768H856.508V81.208C856.508 81.992 856.692 82.576 857.06 82.96C857.444 83.328 857.932 83.512 858.523 83.512ZM867.867 71.896C868.955 71.896 869.899 72.136 870.699 72.616C871.499 73.096 872.107 73.776 872.523 74.656C872.955 75.52 873.171 76.536 873.171 77.704V78.976H864.363C864.395 80.432 864.763 81.544 865.467 82.312C866.187 83.064 867.187 83.44 868.467 83.44C869.283 83.44 870.003 83.368 870.627 83.224C871.267 83.064 871.923 82.84 872.595 82.552V84.4C871.939 84.688 871.291 84.896 870.651 85.024C870.011 85.168 869.251 85.24 868.371 85.24C867.139 85.24 866.059 84.992 865.131 84.496C864.203 84 863.475 83.264 862.947 82.288C862.435 81.312 862.179 80.104 862.179 78.664C862.179 77.256 862.411 76.048 862.875 75.04C863.355 74.032 864.019 73.256 864.867 72.712C865.731 72.168 866.731 71.896 867.867 71.896ZM867.843 73.624C866.835 73.624 866.035 73.952 865.443 74.608C864.867 75.248 864.523 76.144 864.411 77.296H870.963C870.947 76.208 870.691 75.328 870.195 74.656C869.699 73.968 868.915 73.624 867.843 73.624ZM760.578 100.864C760.354 101.792 760.122 102.8 759.882 103.888C759.658 104.96 759.49 105.912 759.378 106.744H757.098L756.93 106.48C757.138 105.648 757.434 104.72 757.818 103.696C758.218 102.672 758.618 101.728 759.018 100.864H760.578ZM756.138 100.864C755.914 101.792 755.69 102.8 755.466 103.888C755.242 104.96 755.066 105.912 754.938 106.744H752.682L752.538 106.48C752.762 105.648 753.058 104.72 753.426 103.696C753.81 102.672 754.202 101.728 754.602 100.864H756.138ZM769.563 100.864L763.179 118H761.115L767.499 100.864H769.563ZM772.925 100.312C773.245 100.312 773.525 100.424 773.765 100.648C774.021 100.856 774.149 101.192 774.149 101.656C774.149 102.12 774.021 102.464 773.765 102.688C773.525 102.896 773.245 103 772.925 103C772.573 103 772.277 102.896 772.037 102.688C771.797 102.464 771.677 102.12 771.677 101.656C771.677 101.192 771.797 100.856 772.037 100.648C772.277 100.424 772.573 100.312 772.925 100.312ZM773.957 105.136V118H771.845V105.136H773.957ZM784.224 104.896C785.76 104.896 786.92 105.272 787.704 106.024C788.488 106.776 788.88 107.976 788.88 109.624V118H786.792V109.768C786.792 107.704 785.832 106.672 783.912 106.672C782.488 106.672 781.504 107.072 780.96 107.872C780.416 108.672 780.144 109.824 780.144 111.328V118H778.032V105.136H779.736L780.048 106.888H780.168C780.584 106.216 781.16 105.72 781.896 105.4C782.632 105.064 783.408 104.896 784.224 104.896ZM801.244 114.448C801.244 115.696 800.78 116.64 799.852 117.28C798.924 117.92 797.676 118.24 796.108 118.24C795.212 118.24 794.436 118.168 793.78 118.024C793.14 117.88 792.572 117.68 792.076 117.424V115.504C792.588 115.76 793.204 116 793.924 116.224C794.66 116.432 795.404 116.536 796.156 116.536C797.228 116.536 798.004 116.368 798.484 116.032C798.964 115.68 799.204 115.216 799.204 114.64C799.204 114.32 799.116 114.032 798.94 113.776C798.764 113.52 798.444 113.264 797.98 113.008C797.532 112.752 796.884 112.464 796.036 112.144C795.204 111.824 794.492 111.504 793.9 111.184C793.308 110.864 792.852 110.48 792.532 110.032C792.212 109.584 792.052 109.008 792.052 108.304C792.052 107.216 792.492 106.376 793.372 105.784C794.268 105.192 795.436 104.896 796.876 104.896C797.66 104.896 798.388 104.976 799.06 105.136C799.748 105.296 800.388 105.504 800.98 105.76L800.26 107.44C799.716 107.216 799.148 107.024 798.556 106.864C797.964 106.704 797.356 106.624 796.732 106.624C795.868 106.624 795.204 106.768 794.74 107.056C794.292 107.328 794.068 107.704 794.068 108.184C794.068 108.552 794.172 108.856 794.38 109.096C794.588 109.336 794.932 109.576 795.412 109.816C795.908 110.056 796.564 110.328 797.38 110.632C798.196 110.936 798.892 111.248 799.468 111.568C800.044 111.888 800.484 112.28 800.788 112.744C801.092 113.192 801.244 113.76 801.244 114.448ZM808.648 116.512C808.969 116.512 809.297 116.488 809.633 116.44C809.969 116.392 810.241 116.328 810.449 116.248V117.856C810.225 117.968 809.905 118.056 809.489 118.12C809.073 118.2 808.673 118.24 808.289 118.24C807.617 118.24 806.993 118.128 806.417 117.904C805.857 117.664 805.401 117.256 805.049 116.68C804.697 116.104 804.521 115.296 804.521 114.256V106.768H802.697V105.76L804.545 104.92L805.385 102.184H806.633V105.136H810.353V106.768H806.633V114.208C806.633 114.992 806.817 115.576 807.185 115.96C807.569 116.328 808.057 116.512 808.648 116.512ZM817.896 104.92C819.464 104.92 820.624 105.264 821.376 105.952C822.128 106.64 822.504 107.736 822.504 109.24V118H820.968L820.56 116.176H820.464C819.904 116.88 819.312 117.4 818.688 117.736C818.064 118.072 817.216 118.24 816.144 118.24C814.976 118.24 814.008 117.936 813.24 117.328C812.472 116.704 812.088 115.736 812.088 114.424C812.088 113.144 812.592 112.16 813.6 111.472C814.608 110.768 816.16 110.384 818.256 110.32L820.44 110.248V109.48C820.44 108.408 820.208 107.664 819.744 107.248C819.28 106.832 818.624 106.624 817.776 106.624C817.104 106.624 816.464 106.728 815.856 106.936C815.248 107.128 814.68 107.352 814.152 107.608L813.504 106.024C814.064 105.72 814.728 105.464 815.496 105.256C816.264 105.032 817.064 104.92 817.896 104.92ZM820.416 111.712L818.52 111.784C816.92 111.848 815.808 112.104 815.184 112.552C814.576 113 814.272 113.632 814.272 114.448C814.272 115.168 814.488 115.696 814.92 116.032C815.368 116.368 815.936 116.536 816.624 116.536C817.696 116.536 818.592 116.24 819.312 115.648C820.048 115.04 820.416 114.112 820.416 112.864V111.712ZM828.59 118H826.478V99.76H828.59V118ZM834.777 118H832.665V99.76H834.777V118ZM843.821 104.896C844.909 104.896 845.853 105.136 846.653 105.616C847.453 106.096 848.061 106.776 848.477 107.656C848.909 108.52 849.125 109.536 849.125 110.704V111.976H840.317C840.349 113.432 840.717 114.544 841.421 115.312C842.141 116.064 843.141 116.44 844.421 116.44C845.237 116.44 845.957 116.368 846.581 116.224C847.221 116.064 847.877 115.84 848.549 115.552V117.4C847.893 117.688 847.245 117.896 846.605 118.024C845.965 118.168 845.205 118.24 844.325 118.24C843.093 118.24 842.013 117.992 841.085 117.496C840.157 117 839.429 116.264 838.901 115.288C838.389 114.312 838.133 113.104 838.133 111.664C838.133 110.256 838.365 109.048 838.829 108.04C839.309 107.032 839.973 106.256 840.821 105.712C841.685 105.168 842.685 104.896 843.821 104.896ZM843.797 106.624C842.789 106.624 841.989 106.952 841.397 107.608C840.821 108.248 840.477 109.144 840.365 110.296H846.917C846.901 109.208 846.645 108.328 846.149 107.656C845.653 106.968 844.869 106.624 843.797 106.624ZM856.959 118.24C855.359 118.24 854.079 117.688 853.119 116.584C852.159 115.464 851.679 113.8 851.679 111.592C851.679 109.384 852.159 107.72 853.119 106.6C854.095 105.464 855.383 104.896 856.983 104.896C857.975 104.896 858.783 105.08 859.407 105.448C860.047 105.816 860.567 106.264 860.967 106.792H861.111C861.079 106.584 861.047 106.28 861.015 105.88C860.983 105.464 860.967 105.136 860.967 104.896V99.76H863.079V118H861.375L861.063 116.272H860.967C860.583 116.816 860.071 117.28 859.431 117.664C858.791 118.048 857.967 118.24 856.959 118.24ZM857.295 116.488C858.655 116.488 859.607 116.12 860.151 115.384C860.711 114.632 860.991 113.504 860.991 112V111.616C860.991 110.016 860.727 108.792 860.199 107.944C859.671 107.08 858.695 106.648 857.271 106.648C856.135 106.648 855.279 107.104 854.703 108.016C854.143 108.912 853.863 110.12 853.863 111.64C853.863 113.176 854.143 114.368 854.703 115.216C855.279 116.064 856.143 116.488 857.295 116.488ZM873.285 100.864L873.453 101.128C873.245 101.976 872.941 102.912 872.541 103.936C872.157 104.96 871.773 105.896 871.389 106.744H869.805C869.949 106.152 870.101 105.504 870.261 104.8C870.421 104.096 870.565 103.408 870.693 102.736C870.837 102.048 870.949 101.424 871.029 100.864H873.285ZM868.845 100.864L869.013 101.128C868.789 101.976 868.485 102.912 868.101 103.936C867.717 104.96 867.333 105.896 866.949 106.744H865.413C865.573 106.152 865.725 105.504 865.869 104.8C866.013 104.096 866.149 103.408 866.277 102.736C866.405 102.048 866.509 101.424 866.589 100.864H868.845Z" fill="black"/>
+<path d="M495 162C495 160.895 494.105 160 493 160C491.895 160 491 160.895 491 162L495 162ZM491.586 275.414C492.367 276.195 493.633 276.195 494.414 275.414L507.142 262.686C507.923 261.905 507.923 260.639 507.142 259.858C506.361 259.077 505.095 259.077 504.314 259.858L493 271.172L481.686 259.858C480.905 259.077 479.639 259.077 478.858 259.858C478.077 260.639 478.077 261.905 478.858 262.686L491.586 275.414ZM491 162L491 274L495 274L495 162L491 162Z" fill="#232629"/>
+</svg>
diff --git a/docs/pics/PageRouterNavigate.svg b/docs/pics/PageRouterNavigate.svg
new file mode 100644 (file)
index 0000000..e128ba3
--- /dev/null
@@ -0,0 +1,16 @@
+<svg width="1477" height="726" viewBox="0 0 1477 726" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="383" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M289 505C289 500.582 292.582 497 297 497H527.023C529.968 497 532.674 498.618 534.069 501.212L578.693 584.212C579.964 586.577 579.964 589.423 578.693 591.788L534.069 674.788C532.674 677.382 529.968 679 527.023 679H297C292.582 679 289 675.418 289 671V505Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M435.716 562.864C437.956 562.864 439.588 563.304 440.612 564.184C441.636 565.064 442.148 566.304 442.148 567.904C442.148 568.848 441.932 569.736 441.5 570.568C441.084 571.384 440.372 572.048 439.364 572.56C438.372 573.072 437.02 573.328 435.308 573.328H433.34V580H431.18V562.864H435.716ZM435.524 564.712H433.34V571.48H435.068C436.7 571.48 437.916 571.216 438.716 570.688C439.516 570.16 439.916 569.264 439.916 568C439.916 566.896 439.556 566.072 438.836 565.528C438.132 564.984 437.028 564.712 435.524 564.712ZM450.295 566.92C451.863 566.92 453.023 567.264 453.775 567.952C454.527 568.64 454.903 569.736 454.903 571.24V580H453.367L452.959 578.176H452.863C452.303 578.88 451.711 579.4 451.087 579.736C450.463 580.072 449.615 580.24 448.543 580.24C447.375 580.24 446.407 579.936 445.639 579.328C444.871 578.704 444.487 577.736 444.487 576.424C444.487 575.144 444.991 574.16 445.999 573.472C447.007 572.768 448.559 572.384 450.655 572.32L452.839 572.248V571.48C452.839 570.408 452.607 569.664 452.143 569.248C451.679 568.832 451.023 568.624 450.175 568.624C449.503 568.624 448.863 568.728 448.255 568.936C447.647 569.128 447.079 569.352 446.551 569.608L445.903 568.024C446.463 567.72 447.127 567.464 447.895 567.256C448.663 567.032 449.463 566.92 450.295 566.92ZM452.815 573.712L450.919 573.784C449.319 573.848 448.207 574.104 447.583 574.552C446.975 575 446.671 575.632 446.671 576.448C446.671 577.168 446.887 577.696 447.319 578.032C447.767 578.368 448.335 578.536 449.023 578.536C450.095 578.536 450.991 578.24 451.711 577.648C452.447 577.04 452.815 576.112 452.815 574.864V573.712ZM463.436 566.896C464.284 566.896 465.044 567.056 465.716 567.376C466.404 567.696 466.988 568.184 467.468 568.84H467.588L467.876 567.136H469.556V580.216C469.556 582.056 469.084 583.44 468.14 584.368C467.212 585.296 465.764 585.76 463.796 585.76C461.908 585.76 460.364 585.488 459.164 584.944V583C460.428 583.672 462.012 584.008 463.916 584.008C465.02 584.008 465.884 583.68 466.508 583.024C467.148 582.384 467.468 581.504 467.468 580.384V579.88C467.468 579.688 467.476 579.416 467.492 579.064C467.508 578.696 467.524 578.44 467.54 578.296H467.444C466.58 579.592 465.252 580.24 463.46 580.24C461.796 580.24 460.492 579.656 459.548 578.488C458.62 577.32 458.156 575.688 458.156 573.592C458.156 571.544 458.62 569.92 459.548 568.72C460.492 567.504 461.788 566.896 463.436 566.896ZM463.724 568.672C462.652 568.672 461.82 569.104 461.228 569.968C460.636 570.816 460.34 572.032 460.34 573.616C460.34 575.2 460.628 576.416 461.204 577.264C461.78 578.096 462.636 578.512 463.772 578.512C465.068 578.512 466.012 578.168 466.604 577.48C467.196 576.776 467.492 575.648 467.492 574.096V573.592C467.492 571.848 467.188 570.592 466.58 569.824C465.972 569.056 465.02 568.672 463.724 568.672ZM478.61 566.896C479.698 566.896 480.642 567.136 481.442 567.616C482.242 568.096 482.85 568.776 483.266 569.656C483.698 570.52 483.914 571.536 483.914 572.704V573.976H475.106C475.138 575.432 475.506 576.544 476.21 577.312C476.93 578.064 477.93 578.44 479.21 578.44C480.026 578.44 480.746 578.368 481.37 578.224C482.01 578.064 482.666 577.84 483.338 577.552V579.4C482.682 579.688 482.034 579.896 481.394 580.024C480.754 580.168 479.994 580.24 479.114 580.24C477.882 580.24 476.802 579.992 475.874 579.496C474.946 579 474.218 578.264 473.69 577.288C473.178 576.312 472.922 575.104 472.922 573.664C472.922 572.256 473.154 571.048 473.618 570.04C474.098 569.032 474.762 568.256 475.61 567.712C476.474 567.168 477.474 566.896 478.61 566.896ZM478.586 568.624C477.578 568.624 476.778 568.952 476.186 569.608C475.61 570.248 475.266 571.144 475.154 572.296H481.706C481.69 571.208 481.434 570.328 480.938 569.656C480.442 568.968 479.658 568.624 478.586 568.624ZM404.578 595.864C404.354 596.792 404.122 597.8 403.882 598.888C403.658 599.96 403.49 600.912 403.378 601.744H401.098L400.93 601.48C401.138 600.648 401.434 599.72 401.818 598.696C402.218 597.672 402.618 596.728 403.018 595.864H404.578ZM400.138 595.864C399.914 596.792 399.69 597.8 399.466 598.888C399.242 599.96 399.066 600.912 398.938 601.744H396.682L396.538 601.48C396.762 600.648 397.058 599.72 397.426 598.696C397.81 597.672 398.202 596.728 398.602 595.864H400.138ZM413.563 595.864L407.179 613H405.115L411.499 595.864H413.563ZM416.925 595.312C417.245 595.312 417.525 595.424 417.765 595.648C418.021 595.856 418.149 596.192 418.149 596.656C418.149 597.12 418.021 597.464 417.765 597.688C417.525 597.896 417.245 598 416.925 598C416.573 598 416.277 597.896 416.037 597.688C415.797 597.464 415.677 597.12 415.677 596.656C415.677 596.192 415.797 595.856 416.037 595.648C416.277 595.424 416.573 595.312 416.925 595.312ZM417.957 600.136V613H415.845V600.136H417.957ZM428.224 599.896C429.76 599.896 430.92 600.272 431.704 601.024C432.488 601.776 432.88 602.976 432.88 604.624V613H430.792V604.768C430.792 602.704 429.832 601.672 427.912 601.672C426.488 601.672 425.504 602.072 424.96 602.872C424.416 603.672 424.144 604.824 424.144 606.328V613H422.032V600.136H423.736L424.048 601.888H424.168C424.584 601.216 425.16 600.72 425.896 600.4C426.632 600.064 427.408 599.896 428.224 599.896ZM445.244 609.448C445.244 610.696 444.78 611.64 443.852 612.28C442.924 612.92 441.676 613.24 440.108 613.24C439.212 613.24 438.436 613.168 437.78 613.024C437.14 612.88 436.572 612.68 436.076 612.424V610.504C436.588 610.76 437.204 611 437.924 611.224C438.66 611.432 439.404 611.536 440.156 611.536C441.228 611.536 442.004 611.368 442.484 611.032C442.964 610.68 443.204 610.216 443.204 609.64C443.204 609.32 443.116 609.032 442.94 608.776C442.764 608.52 442.444 608.264 441.98 608.008C441.532 607.752 440.884 607.464 440.036 607.144C439.204 606.824 438.492 606.504 437.9 606.184C437.308 605.864 436.852 605.48 436.532 605.032C436.212 604.584 436.052 604.008 436.052 603.304C436.052 602.216 436.492 601.376 437.372 600.784C438.268 600.192 439.436 599.896 440.876 599.896C441.66 599.896 442.388 599.976 443.06 600.136C443.748 600.296 444.388 600.504 444.98 600.76L444.26 602.44C443.716 602.216 443.148 602.024 442.556 601.864C441.964 601.704 441.356 601.624 440.732 601.624C439.868 601.624 439.204 601.768 438.74 602.056C438.292 602.328 438.068 602.704 438.068 603.184C438.068 603.552 438.172 603.856 438.38 604.096C438.588 604.336 438.932 604.576 439.412 604.816C439.908 605.056 440.564 605.328 441.38 605.632C442.196 605.936 442.892 606.248 443.468 606.568C444.044 606.888 444.484 607.28 444.788 607.744C445.092 608.192 445.244 608.76 445.244 609.448ZM452.648 611.512C452.969 611.512 453.297 611.488 453.633 611.44C453.969 611.392 454.241 611.328 454.449 611.248V612.856C454.225 612.968 453.905 613.056 453.489 613.12C453.073 613.2 452.673 613.24 452.289 613.24C451.617 613.24 450.993 613.128 450.417 612.904C449.857 612.664 449.401 612.256 449.049 611.68C448.697 611.104 448.521 610.296 448.521 609.256V601.768H446.697V600.76L448.545 599.92L449.385 597.184H450.633V600.136H454.353V601.768H450.633V609.208C450.633 609.992 450.817 610.576 451.185 610.96C451.569 611.328 452.057 611.512 452.648 611.512ZM461.896 599.92C463.464 599.92 464.624 600.264 465.376 600.952C466.128 601.64 466.504 602.736 466.504 604.24V613H464.968L464.56 611.176H464.464C463.904 611.88 463.312 612.4 462.688 612.736C462.064 613.072 461.216 613.24 460.144 613.24C458.976 613.24 458.008 612.936 457.24 612.328C456.472 611.704 456.088 610.736 456.088 609.424C456.088 608.144 456.592 607.16 457.6 606.472C458.608 605.768 460.16 605.384 462.256 605.32L464.44 605.248V604.48C464.44 603.408 464.208 602.664 463.744 602.248C463.28 601.832 462.624 601.624 461.776 601.624C461.104 601.624 460.464 601.728 459.856 601.936C459.248 602.128 458.68 602.352 458.152 602.608L457.504 601.024C458.064 600.72 458.728 600.464 459.496 600.256C460.264 600.032 461.064 599.92 461.896 599.92ZM464.416 606.712L462.52 606.784C460.92 606.848 459.808 607.104 459.184 607.552C458.576 608 458.272 608.632 458.272 609.448C458.272 610.168 458.488 610.696 458.92 611.032C459.368 611.368 459.936 611.536 460.624 611.536C461.696 611.536 462.592 611.24 463.312 610.648C464.048 610.04 464.416 609.112 464.416 607.864V606.712ZM472.59 613H470.478V594.76H472.59V613ZM478.777 613H476.665V594.76H478.777V613ZM487.821 599.896C488.909 599.896 489.853 600.136 490.653 600.616C491.453 601.096 492.061 601.776 492.477 602.656C492.909 603.52 493.125 604.536 493.125 605.704V606.976H484.317C484.349 608.432 484.717 609.544 485.421 610.312C486.141 611.064 487.141 611.44 488.421 611.44C489.237 611.44 489.957 611.368 490.581 611.224C491.221 611.064 491.877 610.84 492.549 610.552V612.4C491.893 612.688 491.245 612.896 490.605 613.024C489.965 613.168 489.205 613.24 488.325 613.24C487.093 613.24 486.013 612.992 485.085 612.496C484.157 612 483.429 611.264 482.901 610.288C482.389 609.312 482.133 608.104 482.133 606.664C482.133 605.256 482.365 604.048 482.829 603.04C483.309 602.032 483.973 601.256 484.821 600.712C485.685 600.168 486.685 599.896 487.821 599.896ZM487.797 601.624C486.789 601.624 485.989 601.952 485.397 602.608C484.821 603.248 484.477 604.144 484.365 605.296H490.917C490.901 604.208 490.645 603.328 490.149 602.656C489.653 601.968 488.869 601.624 487.797 601.624ZM500.959 613.24C499.359 613.24 498.079 612.688 497.119 611.584C496.159 610.464 495.679 608.8 495.679 606.592C495.679 604.384 496.159 602.72 497.119 601.6C498.095 600.464 499.383 599.896 500.983 599.896C501.975 599.896 502.783 600.08 503.407 600.448C504.047 600.816 504.567 601.264 504.967 601.792H505.111C505.079 601.584 505.047 601.28 505.015 600.88C504.983 600.464 504.967 600.136 504.967 599.896V594.76H507.079V613H505.375L505.063 611.272H504.967C504.583 611.816 504.071 612.28 503.431 612.664C502.791 613.048 501.967 613.24 500.959 613.24ZM501.295 611.488C502.655 611.488 503.607 611.12 504.151 610.384C504.711 609.632 504.991 608.504 504.991 607V606.616C504.991 605.016 504.727 603.792 504.199 602.944C503.671 602.08 502.695 601.648 501.271 601.648C500.135 601.648 499.279 602.104 498.703 603.016C498.143 603.912 497.863 605.12 497.863 606.64C497.863 608.176 498.143 609.368 498.703 610.216C499.279 611.064 500.143 611.488 501.295 611.488ZM517.285 595.864L517.453 596.128C517.245 596.976 516.941 597.912 516.541 598.936C516.157 599.96 515.773 600.896 515.389 601.744H513.805C513.949 601.152 514.101 600.504 514.261 599.8C514.421 599.096 514.565 598.408 514.693 597.736C514.837 597.048 514.949 596.424 515.029 595.864H517.285ZM512.845 595.864L513.013 596.128C512.789 596.976 512.485 597.912 512.101 598.936C511.717 599.96 511.333 600.896 510.949 601.744H509.413C509.573 601.152 509.725 600.504 509.869 599.8C510.013 599.096 510.149 598.408 510.277 597.736C510.405 597.048 510.509 596.424 510.589 595.864H512.845Z" fill="black"/>
+<path d="M151 505C151 500.582 154.582 497 159 497H329.023C331.968 497 334.674 498.618 336.069 501.212L380.693 584.212C381.964 586.577 381.964 589.423 380.693 591.788L336.069 674.788C334.674 677.382 331.968 679 329.023 679H159C154.582 679 151 675.418 151 671V505Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 562.864C222.956 562.864 224.588 563.304 225.612 564.184C226.636 565.064 227.148 566.304 227.148 567.904C227.148 568.848 226.932 569.736 226.5 570.568C226.084 571.384 225.372 572.048 224.364 572.56C223.372 573.072 222.02 573.328 220.308 573.328H218.34V580H216.18V562.864H220.716ZM220.524 564.712H218.34V571.48H220.068C221.7 571.48 222.916 571.216 223.716 570.688C224.516 570.16 224.916 569.264 224.916 568C224.916 566.896 224.556 566.072 223.836 565.528C223.132 564.984 222.028 564.712 220.524 564.712ZM235.295 566.92C236.863 566.92 238.023 567.264 238.775 567.952C239.527 568.64 239.903 569.736 239.903 571.24V580H238.367L237.959 578.176H237.863C237.303 578.88 236.711 579.4 236.087 579.736C235.463 580.072 234.615 580.24 233.543 580.24C232.375 580.24 231.407 579.936 230.639 579.328C229.871 578.704 229.487 577.736 229.487 576.424C229.487 575.144 229.991 574.16 230.999 573.472C232.007 572.768 233.559 572.384 235.655 572.32L237.839 572.248V571.48C237.839 570.408 237.607 569.664 237.143 569.248C236.679 568.832 236.023 568.624 235.175 568.624C234.503 568.624 233.863 568.728 233.255 568.936C232.647 569.128 232.079 569.352 231.551 569.608L230.903 568.024C231.463 567.72 232.127 567.464 232.895 567.256C233.663 567.032 234.463 566.92 235.295 566.92ZM237.815 573.712L235.919 573.784C234.319 573.848 233.207 574.104 232.583 574.552C231.975 575 231.671 575.632 231.671 576.448C231.671 577.168 231.887 577.696 232.319 578.032C232.767 578.368 233.335 578.536 234.023 578.536C235.095 578.536 235.991 578.24 236.711 577.648C237.447 577.04 237.815 576.112 237.815 574.864V573.712ZM248.436 566.896C249.284 566.896 250.044 567.056 250.716 567.376C251.404 567.696 251.988 568.184 252.468 568.84H252.588L252.876 567.136H254.556V580.216C254.556 582.056 254.084 583.44 253.14 584.368C252.212 585.296 250.764 585.76 248.796 585.76C246.908 585.76 245.364 585.488 244.164 584.944V583C245.428 583.672 247.012 584.008 248.916 584.008C250.02 584.008 250.884 583.68 251.508 583.024C252.148 582.384 252.468 581.504 252.468 580.384V579.88C252.468 579.688 252.476 579.416 252.492 579.064C252.508 578.696 252.524 578.44 252.54 578.296H252.444C251.58 579.592 250.252 580.24 248.46 580.24C246.796 580.24 245.492 579.656 244.548 578.488C243.62 577.32 243.156 575.688 243.156 573.592C243.156 571.544 243.62 569.92 244.548 568.72C245.492 567.504 246.788 566.896 248.436 566.896ZM248.724 568.672C247.652 568.672 246.82 569.104 246.228 569.968C245.636 570.816 245.34 572.032 245.34 573.616C245.34 575.2 245.628 576.416 246.204 577.264C246.78 578.096 247.636 578.512 248.772 578.512C250.068 578.512 251.012 578.168 251.604 577.48C252.196 576.776 252.492 575.648 252.492 574.096V573.592C252.492 571.848 252.188 570.592 251.58 569.824C250.972 569.056 250.02 568.672 248.724 568.672ZM263.61 566.896C264.698 566.896 265.642 567.136 266.442 567.616C267.242 568.096 267.85 568.776 268.266 569.656C268.698 570.52 268.914 571.536 268.914 572.704V573.976H260.106C260.138 575.432 260.506 576.544 261.21 577.312C261.93 578.064 262.93 578.44 264.21 578.44C265.026 578.44 265.746 578.368 266.37 578.224C267.01 578.064 267.666 577.84 268.338 577.552V579.4C267.682 579.688 267.034 579.896 266.394 580.024C265.754 580.168 264.994 580.24 264.114 580.24C262.882 580.24 261.802 579.992 260.874 579.496C259.946 579 259.218 578.264 258.69 577.288C258.178 576.312 257.922 575.104 257.922 573.664C257.922 572.256 258.154 571.048 258.618 570.04C259.098 569.032 259.762 568.256 260.61 567.712C261.474 567.168 262.474 566.896 263.61 566.896ZM263.586 568.624C262.578 568.624 261.778 568.952 261.186 569.608C260.61 570.248 260.266 571.144 260.154 572.296H266.706C266.69 571.208 266.434 570.328 265.938 569.656C265.442 568.968 264.658 568.624 263.586 568.624ZM204.683 595.864C204.459 596.792 204.227 597.8 203.987 598.888C203.763 599.96 203.595 600.912 203.483 601.744H201.203L201.035 601.48C201.243 600.648 201.539 599.72 201.923 598.696C202.323 597.672 202.723 596.728 203.123 595.864H204.683ZM200.243 595.864C200.019 596.792 199.795 597.8 199.571 598.888C199.347 599.96 199.171 600.912 199.043 601.744H196.787L196.643 601.48C196.867 600.648 197.163 599.72 197.531 598.696C197.915 597.672 198.307 596.728 198.707 595.864H200.243ZM213.668 595.864L207.284 613H205.22L211.604 595.864H213.668ZM218.062 594.76V600.112C218.062 600.752 218.022 601.352 217.942 601.912H218.086C218.502 601.256 219.062 600.76 219.766 600.424C220.486 600.088 221.262 599.92 222.094 599.92C223.646 599.92 224.814 600.296 225.598 601.048C226.398 601.784 226.798 602.976 226.798 604.624V613H224.71V604.768C224.71 602.704 223.75 601.672 221.83 601.672C220.39 601.672 219.398 602.08 218.854 602.896C218.326 603.696 218.062 604.848 218.062 606.352V613H215.95V594.76H218.062ZM241.97 606.544C241.97 608.672 241.426 610.32 240.338 611.488C239.266 612.656 237.81 613.24 235.97 613.24C234.834 613.24 233.818 612.984 232.922 612.472C232.042 611.944 231.346 611.184 230.834 610.192C230.322 609.184 230.066 607.968 230.066 606.544C230.066 604.416 230.594 602.776 231.65 601.624C232.722 600.472 234.186 599.896 236.042 599.896C237.194 599.896 238.218 600.16 239.114 600.688C240.01 601.2 240.706 601.952 241.202 602.944C241.714 603.92 241.97 605.12 241.97 606.544ZM232.25 606.544C232.25 608.064 232.546 609.272 233.138 610.168C233.746 611.048 234.706 611.488 236.018 611.488C237.314 611.488 238.266 611.048 238.874 610.168C239.482 609.272 239.786 608.064 239.786 606.544C239.786 605.024 239.482 603.832 238.874 602.968C238.266 602.104 237.306 601.672 235.994 601.672C234.682 601.672 233.73 602.104 233.138 602.968C232.546 603.832 232.25 605.024 232.25 606.544ZM259.429 599.896C260.885 599.896 261.973 600.272 262.693 601.024C263.413 601.776 263.773 602.976 263.773 604.624V613H261.685V604.72C261.685 602.688 260.813 601.672 259.069 601.672C257.821 601.672 256.925 602.032 256.381 602.752C255.853 603.472 255.589 604.52 255.589 605.896V613H253.501V604.72C253.501 602.688 252.621 601.672 250.861 601.672C249.565 601.672 248.669 602.072 248.173 602.872C247.677 603.672 247.429 604.824 247.429 606.328V613H245.317V600.136H247.021L247.333 601.888H247.453C247.853 601.216 248.389 600.72 249.061 600.4C249.749 600.064 250.477 599.896 251.245 599.896C253.261 599.896 254.573 600.616 255.181 602.056H255.301C255.733 601.32 256.317 600.776 257.053 600.424C257.789 600.072 258.581 599.896 259.429 599.896ZM272.715 599.896C273.803 599.896 274.747 600.136 275.547 600.616C276.347 601.096 276.955 601.776 277.371 602.656C277.803 603.52 278.019 604.536 278.019 605.704V606.976H269.211C269.243 608.432 269.611 609.544 270.315 610.312C271.035 611.064 272.035 611.44 273.315 611.44C274.131 611.44 274.851 611.368 275.475 611.224C276.115 611.064 276.771 610.84 277.443 610.552V612.4C276.787 612.688 276.139 612.896 275.499 613.024C274.859 613.168 274.099 613.24 273.219 613.24C271.987 613.24 270.907 612.992 269.979 612.496C269.051 612 268.323 611.264 267.795 610.288C267.283 609.312 267.027 608.104 267.027 606.664C267.027 605.256 267.259 604.048 267.723 603.04C268.203 602.032 268.867 601.256 269.715 600.712C270.579 600.168 271.579 599.896 272.715 599.896ZM272.691 601.624C271.683 601.624 270.883 601.952 270.291 602.608C269.715 603.248 269.371 604.144 269.259 605.296H275.811C275.795 604.208 275.539 603.328 275.043 602.656C274.547 601.968 273.763 601.624 272.691 601.624ZM287.18 595.864L287.348 596.128C287.14 596.976 286.836 597.912 286.436 598.936C286.052 599.96 285.668 600.896 285.284 601.744H283.7C283.844 601.152 283.996 600.504 284.156 599.8C284.316 599.096 284.46 598.408 284.588 597.736C284.732 597.048 284.844 596.424 284.924 595.864H287.18ZM282.74 595.864L282.908 596.128C282.684 596.976 282.38 597.912 281.996 598.936C281.612 599.96 281.228 600.896 280.844 601.744H279.308C279.468 601.152 279.62 600.504 279.764 599.8C279.908 599.096 280.044 598.408 280.172 597.736C280.3 597.048 280.404 596.424 280.484 595.864H282.74Z" fill="black"/>
+<path d="M306.501 418.864C308.741 418.864 310.373 419.304 311.397 420.184C312.421 421.064 312.933 422.304 312.933 423.904C312.933 424.848 312.717 425.736 312.285 426.568C311.869 427.384 311.157 428.048 310.149 428.56C309.157 429.072 307.805 429.328 306.093 429.328H304.125V436H301.965V418.864H306.501ZM306.309 420.712H304.125V427.48H305.853C307.485 427.48 308.701 427.216 309.501 426.688C310.301 426.16 310.701 425.264 310.701 424C310.701 422.896 310.341 422.072 309.621 421.528C308.917 420.984 307.813 420.712 306.309 420.712ZM321.08 422.92C322.648 422.92 323.808 423.264 324.56 423.952C325.312 424.64 325.688 425.736 325.688 427.24V436H324.152L323.744 434.176H323.648C323.088 434.88 322.496 435.4 321.872 435.736C321.248 436.072 320.4 436.24 319.328 436.24C318.16 436.24 317.192 435.936 316.424 435.328C315.656 434.704 315.272 433.736 315.272 432.424C315.272 431.144 315.776 430.16 316.784 429.472C317.792 428.768 319.344 428.384 321.44 428.32L323.624 428.248V427.48C323.624 426.408 323.392 425.664 322.928 425.248C322.464 424.832 321.808 424.624 320.96 424.624C320.288 424.624 319.648 424.728 319.04 424.936C318.432 425.128 317.864 425.352 317.336 425.608L316.688 424.024C317.248 423.72 317.912 423.464 318.68 423.256C319.448 423.032 320.248 422.92 321.08 422.92ZM323.6 429.712L321.704 429.784C320.104 429.848 318.992 430.104 318.368 430.552C317.76 431 317.456 431.632 317.456 432.448C317.456 433.168 317.672 433.696 318.104 434.032C318.552 434.368 319.12 434.536 319.808 434.536C320.88 434.536 321.776 434.24 322.496 433.648C323.232 433.04 323.6 432.112 323.6 430.864V429.712ZM334.221 422.896C335.069 422.896 335.829 423.056 336.501 423.376C337.189 423.696 337.773 424.184 338.253 424.84H338.373L338.661 423.136H340.341V436.216C340.341 438.056 339.869 439.44 338.925 440.368C337.997 441.296 336.549 441.76 334.581 441.76C332.693 441.76 331.149 441.488 329.949 440.944V439C331.213 439.672 332.797 440.008 334.701 440.008C335.805 440.008 336.669 439.68 337.293 439.024C337.933 438.384 338.253 437.504 338.253 436.384V435.88C338.253 435.688 338.261 435.416 338.277 435.064C338.293 434.696 338.309 434.44 338.325 434.296H338.229C337.365 435.592 336.037 436.24 334.245 436.24C332.581 436.24 331.277 435.656 330.333 434.488C329.405 433.32 328.941 431.688 328.941 429.592C328.941 427.544 329.405 425.92 330.333 424.72C331.277 423.504 332.573 422.896 334.221 422.896ZM334.509 424.672C333.437 424.672 332.605 425.104 332.013 425.968C331.421 426.816 331.125 428.032 331.125 429.616C331.125 431.2 331.413 432.416 331.989 433.264C332.565 434.096 333.421 434.512 334.557 434.512C335.853 434.512 336.797 434.168 337.389 433.48C337.981 432.776 338.277 431.648 338.277 430.096V429.592C338.277 427.848 337.973 426.592 337.365 425.824C336.757 425.056 335.805 424.672 334.509 424.672ZM349.395 422.896C350.483 422.896 351.427 423.136 352.227 423.616C353.027 424.096 353.635 424.776 354.051 425.656C354.483 426.52 354.699 427.536 354.699 428.704V429.976H345.891C345.923 431.432 346.291 432.544 346.995 433.312C347.715 434.064 348.715 434.44 349.995 434.44C350.811 434.44 351.531 434.368 352.155 434.224C352.795 434.064 353.451 433.84 354.123 433.552V435.4C353.467 435.688 352.819 435.896 352.179 436.024C351.539 436.168 350.779 436.24 349.899 436.24C348.667 436.24 347.587 435.992 346.659 435.496C345.731 435 345.003 434.264 344.475 433.288C343.963 432.312 343.707 431.104 343.707 429.664C343.707 428.256 343.939 427.048 344.403 426.04C344.883 425.032 345.547 424.256 346.395 423.712C347.259 423.168 348.259 422.896 349.395 422.896ZM349.371 424.624C348.363 424.624 347.563 424.952 346.971 425.608C346.395 426.248 346.051 427.144 345.939 428.296H352.491C352.475 427.208 352.219 426.328 351.723 425.656C351.227 424.968 350.443 424.624 349.371 424.624ZM362.99 418.864C365.118 418.864 366.686 419.272 367.694 420.088C368.718 420.888 369.23 422.104 369.23 423.736C369.23 424.648 369.062 425.408 368.726 426.016C368.39 426.624 367.958 427.112 367.43 427.48C366.918 427.848 366.374 428.128 365.798 428.32L370.502 436H367.982L363.83 428.92H360.422V436H358.262V418.864H362.99ZM362.87 420.736H360.422V427.096H362.99C364.382 427.096 365.398 426.824 366.038 426.28C366.678 425.72 366.998 424.904 366.998 423.832C366.998 422.712 366.662 421.92 365.99 421.456C365.318 420.976 364.278 420.736 362.87 420.736ZM384.087 429.544C384.087 431.672 383.543 433.32 382.455 434.488C381.383 435.656 379.927 436.24 378.087 436.24C376.951 436.24 375.935 435.984 375.039 435.472C374.159 434.944 373.463 434.184 372.951 433.192C372.439 432.184 372.183 430.968 372.183 429.544C372.183 427.416 372.711 425.776 373.767 424.624C374.839 423.472 376.303 422.896 378.159 422.896C379.311 422.896 380.335 423.16 381.231 423.688C382.127 424.2 382.823 424.952 383.319 425.944C383.831 426.92 384.087 428.12 384.087 429.544ZM374.367 429.544C374.367 431.064 374.663 432.272 375.255 433.168C375.863 434.048 376.823 434.488 378.135 434.488C379.431 434.488 380.383 434.048 380.991 433.168C381.599 432.272 381.903 431.064 381.903 429.544C381.903 428.024 381.599 426.832 380.991 425.968C380.383 425.104 379.423 424.672 378.111 424.672C376.799 424.672 375.847 425.104 375.255 425.968C374.663 426.832 374.367 428.024 374.367 429.544ZM398.187 423.136V436H396.459L396.147 434.296H396.051C395.635 434.968 395.059 435.464 394.323 435.784C393.587 436.088 392.803 436.24 391.971 436.24C390.419 436.24 389.251 435.872 388.467 435.136C387.683 434.384 387.291 433.192 387.291 431.56V423.136H389.427V431.416C389.427 433.464 390.379 434.488 392.283 434.488C393.707 434.488 394.691 434.088 395.235 433.288C395.795 432.488 396.075 431.336 396.075 429.832V423.136H398.187ZM406.566 434.512C406.886 434.512 407.214 434.488 407.55 434.44C407.886 434.392 408.158 434.328 408.366 434.248V435.856C408.142 435.968 407.822 436.056 407.406 436.12C406.99 436.2 406.59 436.24 406.206 436.24C405.534 436.24 404.91 436.128 404.334 435.904C403.774 435.664 403.318 435.256 402.966 434.68C402.614 434.104 402.438 433.296 402.438 432.256V424.768H400.614V423.76L402.462 422.92L403.302 420.184H404.55V423.136H408.27V424.768H404.55V432.208C404.55 432.992 404.734 433.576 405.102 433.96C405.486 434.328 405.974 434.512 406.566 434.512ZM415.91 422.896C416.998 422.896 417.942 423.136 418.742 423.616C419.542 424.096 420.15 424.776 420.566 425.656C420.998 426.52 421.214 427.536 421.214 428.704V429.976H412.406C412.438 431.432 412.806 432.544 413.51 433.312C414.23 434.064 415.23 434.44 416.51 434.44C417.326 434.44 418.046 434.368 418.67 434.224C419.31 434.064 419.966 433.84 420.638 433.552V435.4C419.982 435.688 419.334 435.896 418.694 436.024C418.054 436.168 417.294 436.24 416.414 436.24C415.182 436.24 414.102 435.992 413.174 435.496C412.246 435 411.518 434.264 410.99 433.288C410.478 432.312 410.222 431.104 410.222 429.664C410.222 428.256 410.454 427.048 410.918 426.04C411.398 425.032 412.062 424.256 412.91 423.712C413.774 423.168 414.774 422.896 415.91 422.896ZM415.886 424.624C414.878 424.624 414.078 424.952 413.486 425.608C412.91 426.248 412.566 427.144 412.454 428.296H419.006C418.99 427.208 418.734 426.328 418.238 425.656C417.742 424.968 416.958 424.624 415.886 424.624ZM430.489 422.896C430.729 422.896 430.985 422.912 431.257 422.944C431.529 422.96 431.777 422.992 432.001 423.04L431.737 424.984C431.529 424.936 431.297 424.896 431.041 424.864C430.785 424.832 430.553 424.816 430.345 424.816C429.689 424.816 429.073 425 428.497 425.368C427.921 425.72 427.457 426.224 427.105 426.88C426.769 427.52 426.601 428.272 426.601 429.136V436H424.489V423.136H426.217L426.457 425.488H426.553C426.953 424.784 427.481 424.176 428.137 423.664C428.809 423.152 429.593 422.896 430.489 422.896ZM254.832 451.864C254.608 452.792 254.376 453.8 254.136 454.888C253.912 455.96 253.744 456.912 253.632 457.744H251.352L251.184 457.48C251.392 456.648 251.688 455.72 252.072 454.696C252.472 453.672 252.872 452.728 253.272 451.864H254.832ZM250.392 451.864C250.168 452.792 249.944 453.8 249.72 454.888C249.496 455.96 249.32 456.912 249.192 457.744H246.936L246.792 457.48C247.016 456.648 247.312 455.72 247.68 454.696C248.064 453.672 248.456 452.728 248.856 451.864H250.392ZM263.817 451.864L257.433 469H255.369L261.753 451.864H263.817ZM268.211 450.76V456.112C268.211 456.752 268.171 457.352 268.091 457.912H268.235C268.651 457.256 269.211 456.76 269.915 456.424C270.635 456.088 271.411 455.92 272.243 455.92C273.795 455.92 274.963 456.296 275.747 457.048C276.547 457.784 276.947 458.976 276.947 460.624V469H274.859V460.768C274.859 458.704 273.899 457.672 271.979 457.672C270.539 457.672 269.547 458.08 269.003 458.896C268.475 459.696 268.211 460.848 268.211 462.352V469H266.099V450.76H268.211ZM292.119 462.544C292.119 464.672 291.575 466.32 290.487 467.488C289.415 468.656 287.959 469.24 286.119 469.24C284.983 469.24 283.967 468.984 283.071 468.472C282.191 467.944 281.495 467.184 280.983 466.192C280.471 465.184 280.215 463.968 280.215 462.544C280.215 460.416 280.743 458.776 281.799 457.624C282.871 456.472 284.335 455.896 286.191 455.896C287.343 455.896 288.367 456.16 289.263 456.688C290.159 457.2 290.855 457.952 291.351 458.944C291.863 459.92 292.119 461.12 292.119 462.544ZM282.399 462.544C282.399 464.064 282.695 465.272 283.287 466.168C283.895 467.048 284.855 467.488 286.167 467.488C287.463 467.488 288.415 467.048 289.023 466.168C289.631 465.272 289.935 464.064 289.935 462.544C289.935 461.024 289.631 459.832 289.023 458.968C288.415 458.104 287.455 457.672 286.143 457.672C284.831 457.672 283.879 458.104 283.287 458.968C282.695 459.832 282.399 461.024 282.399 462.544ZM309.578 455.896C311.034 455.896 312.122 456.272 312.842 457.024C313.562 457.776 313.922 458.976 313.922 460.624V469H311.834V460.72C311.834 458.688 310.962 457.672 309.218 457.672C307.97 457.672 307.074 458.032 306.53 458.752C306.002 459.472 305.738 460.52 305.738 461.896V469H303.65V460.72C303.65 458.688 302.77 457.672 301.01 457.672C299.714 457.672 298.818 458.072 298.322 458.872C297.826 459.672 297.578 460.824 297.578 462.328V469H295.466V456.136H297.17L297.482 457.888H297.602C298.002 457.216 298.538 456.72 299.21 456.4C299.898 456.064 300.626 455.896 301.394 455.896C303.41 455.896 304.722 456.616 305.33 458.056H305.45C305.882 457.32 306.466 456.776 307.202 456.424C307.938 456.072 308.73 455.896 309.578 455.896ZM322.863 455.896C323.951 455.896 324.895 456.136 325.695 456.616C326.495 457.096 327.103 457.776 327.519 458.656C327.951 459.52 328.167 460.536 328.167 461.704V462.976H319.359C319.391 464.432 319.759 465.544 320.463 466.312C321.183 467.064 322.183 467.44 323.463 467.44C324.279 467.44 324.999 467.368 325.623 467.224C326.263 467.064 326.919 466.84 327.591 466.552V468.4C326.935 468.688 326.287 468.896 325.647 469.024C325.007 469.168 324.247 469.24 323.367 469.24C322.135 469.24 321.055 468.992 320.127 468.496C319.199 468 318.471 467.264 317.943 466.288C317.431 465.312 317.175 464.104 317.175 462.664C317.175 461.256 317.407 460.048 317.871 459.04C318.351 458.032 319.015 457.256 319.863 456.712C320.727 456.168 321.727 455.896 322.863 455.896ZM322.839 457.624C321.831 457.624 321.031 457.952 320.439 458.608C319.863 459.248 319.519 460.144 319.407 461.296H325.959C325.943 460.208 325.687 459.328 325.191 458.656C324.695 457.968 323.911 457.624 322.839 457.624ZM337.328 451.864L337.496 452.128C337.288 452.976 336.984 453.912 336.584 454.936C336.2 455.96 335.816 456.896 335.432 457.744H333.848C333.992 457.152 334.144 456.504 334.304 455.8C334.464 455.096 334.608 454.408 334.736 453.736C334.88 453.048 334.992 452.424 335.072 451.864H337.328ZM332.888 451.864L333.056 452.128C332.832 452.976 332.528 453.912 332.144 454.936C331.76 455.96 331.376 456.896 330.992 457.744H329.456C329.616 457.152 329.768 456.504 329.912 455.8C330.056 455.096 330.192 454.408 330.32 453.736C330.448 453.048 330.552 452.424 330.632 451.864H332.888ZM345.227 464.344L354.275 460.576L345.227 456.28V454.408L356.531 460.048V461.248L345.227 466.216V464.344ZM372.324 451.864C372.1 452.792 371.868 453.8 371.628 454.888C371.404 455.96 371.236 456.912 371.124 457.744H368.844L368.676 457.48C368.884 456.648 369.18 455.72 369.564 454.696C369.964 453.672 370.364 452.728 370.764 451.864H372.324ZM367.884 451.864C367.66 452.792 367.436 453.8 367.212 454.888C366.988 455.96 366.812 456.912 366.684 457.744H364.428L364.284 457.48C364.508 456.648 364.804 455.72 365.172 454.696C365.556 453.672 365.948 452.728 366.348 451.864H367.884ZM381.309 451.864L374.925 469H372.861L379.245 451.864H381.309ZM384.671 451.312C384.991 451.312 385.271 451.424 385.511 451.648C385.767 451.856 385.895 452.192 385.895 452.656C385.895 453.12 385.767 453.464 385.511 453.688C385.271 453.896 384.991 454 384.671 454C384.319 454 384.023 453.896 383.783 453.688C383.543 453.464 383.423 453.12 383.423 452.656C383.423 452.192 383.543 451.856 383.783 451.648C384.023 451.424 384.319 451.312 384.671 451.312ZM385.703 456.136V469H383.591V456.136H385.703ZM395.97 455.896C397.506 455.896 398.666 456.272 399.45 457.024C400.234 457.776 400.626 458.976 400.626 460.624V469H398.538V460.768C398.538 458.704 397.578 457.672 395.658 457.672C394.234 457.672 393.25 458.072 392.706 458.872C392.162 459.672 391.89 460.824 391.89 462.328V469H389.778V456.136H391.482L391.794 457.888H391.914C392.33 457.216 392.906 456.72 393.642 456.4C394.378 456.064 395.154 455.896 395.97 455.896ZM412.99 465.448C412.99 466.696 412.526 467.64 411.598 468.28C410.67 468.92 409.422 469.24 407.854 469.24C406.958 469.24 406.182 469.168 405.526 469.024C404.886 468.88 404.318 468.68 403.822 468.424V466.504C404.334 466.76 404.95 467 405.67 467.224C406.406 467.432 407.15 467.536 407.902 467.536C408.974 467.536 409.75 467.368 410.23 467.032C410.71 466.68 410.95 466.216 410.95 465.64C410.95 465.32 410.862 465.032 410.686 464.776C410.51 464.52 410.19 464.264 409.726 464.008C409.278 463.752 408.63 463.464 407.782 463.144C406.95 462.824 406.238 462.504 405.646 462.184C405.054 461.864 404.598 461.48 404.278 461.032C403.958 460.584 403.798 460.008 403.798 459.304C403.798 458.216 404.238 457.376 405.118 456.784C406.014 456.192 407.182 455.896 408.622 455.896C409.406 455.896 410.134 455.976 410.806 456.136C411.494 456.296 412.134 456.504 412.726 456.76L412.006 458.44C411.462 458.216 410.894 458.024 410.302 457.864C409.71 457.704 409.102 457.624 408.478 457.624C407.614 457.624 406.95 457.768 406.486 458.056C406.038 458.328 405.814 458.704 405.814 459.184C405.814 459.552 405.918 459.856 406.126 460.096C406.334 460.336 406.678 460.576 407.158 460.816C407.654 461.056 408.31 461.328 409.126 461.632C409.942 461.936 410.638 462.248 411.214 462.568C411.79 462.888 412.23 463.28 412.534 463.744C412.838 464.192 412.99 464.76 412.99 465.448ZM420.395 467.512C420.715 467.512 421.043 467.488 421.379 467.44C421.715 467.392 421.987 467.328 422.195 467.248V468.856C421.971 468.968 421.651 469.056 421.235 469.12C420.819 469.2 420.419 469.24 420.035 469.24C419.363 469.24 418.739 469.128 418.163 468.904C417.603 468.664 417.147 468.256 416.795 467.68C416.443 467.104 416.267 466.296 416.267 465.256V457.768H414.443V456.76L416.291 455.92L417.131 453.184H418.379V456.136H422.099V457.768H418.379V465.208C418.379 465.992 418.563 466.576 418.931 466.96C419.315 467.328 419.803 467.512 420.395 467.512ZM429.642 455.92C431.21 455.92 432.37 456.264 433.122 456.952C433.874 457.64 434.25 458.736 434.25 460.24V469H432.714L432.306 467.176H432.21C431.65 467.88 431.058 468.4 430.434 468.736C429.81 469.072 428.962 469.24 427.89 469.24C426.722 469.24 425.754 468.936 424.986 468.328C424.218 467.704 423.834 466.736 423.834 465.424C423.834 464.144 424.338 463.16 425.346 462.472C426.354 461.768 427.906 461.384 430.002 461.32L432.186 461.248V460.48C432.186 459.408 431.954 458.664 431.49 458.248C431.026 457.832 430.37 457.624 429.522 457.624C428.85 457.624 428.21 457.728 427.602 457.936C426.994 458.128 426.426 458.352 425.898 458.608L425.25 457.024C425.81 456.72 426.474 456.464 427.242 456.256C428.01 456.032 428.81 455.92 429.642 455.92ZM432.162 462.712L430.266 462.784C428.666 462.848 427.554 463.104 426.93 463.552C426.322 464 426.018 464.632 426.018 465.448C426.018 466.168 426.234 466.696 426.666 467.032C427.114 467.368 427.682 467.536 428.37 467.536C429.442 467.536 430.338 467.24 431.058 466.648C431.794 466.04 432.162 465.112 432.162 463.864V462.712ZM440.336 469H438.224V450.76H440.336V469ZM446.523 469H444.411V450.76H446.523V469ZM455.567 455.896C456.655 455.896 457.599 456.136 458.399 456.616C459.199 457.096 459.807 457.776 460.223 458.656C460.655 459.52 460.871 460.536 460.871 461.704V462.976H452.063C452.095 464.432 452.463 465.544 453.167 466.312C453.887 467.064 454.887 467.44 456.167 467.44C456.983 467.44 457.703 467.368 458.327 467.224C458.967 467.064 459.623 466.84 460.295 466.552V468.4C459.639 468.688 458.991 468.896 458.351 469.024C457.711 469.168 456.951 469.24 456.071 469.24C454.839 469.24 453.759 468.992 452.831 468.496C451.903 468 451.175 467.264 450.647 466.288C450.135 465.312 449.879 464.104 449.879 462.664C449.879 461.256 450.111 460.048 450.575 459.04C451.055 458.032 451.719 457.256 452.567 456.712C453.431 456.168 454.431 455.896 455.567 455.896ZM455.543 457.624C454.535 457.624 453.735 457.952 453.143 458.608C452.567 459.248 452.223 460.144 452.111 461.296H458.663C458.647 460.208 458.391 459.328 457.895 458.656C457.399 457.968 456.615 457.624 455.543 457.624ZM468.705 469.24C467.105 469.24 465.825 468.688 464.865 467.584C463.905 466.464 463.425 464.8 463.425 462.592C463.425 460.384 463.905 458.72 464.865 457.6C465.841 456.464 467.129 455.896 468.729 455.896C469.721 455.896 470.529 456.08 471.153 456.448C471.793 456.816 472.313 457.264 472.713 457.792H472.857C472.825 457.584 472.793 457.28 472.761 456.88C472.729 456.464 472.713 456.136 472.713 455.896V450.76H474.825V469H473.121L472.809 467.272H472.713C472.329 467.816 471.817 468.28 471.177 468.664C470.537 469.048 469.713 469.24 468.705 469.24ZM469.041 467.488C470.401 467.488 471.353 467.12 471.897 466.384C472.457 465.632 472.737 464.504 472.737 463V462.616C472.737 461.016 472.473 459.792 471.945 458.944C471.417 458.08 470.441 457.648 469.017 457.648C467.881 457.648 467.025 458.104 466.449 459.016C465.889 459.912 465.609 461.12 465.609 462.64C465.609 464.176 465.889 465.368 466.449 466.216C467.025 467.064 467.889 467.488 469.041 467.488ZM485.031 451.864L485.199 452.128C484.991 452.976 484.687 453.912 484.287 454.936C483.903 455.96 483.519 456.896 483.135 457.744H481.551C481.695 457.152 481.847 456.504 482.007 455.8C482.167 455.096 482.311 454.408 482.439 453.736C482.583 453.048 482.695 452.424 482.775 451.864H485.031ZM480.591 451.864L480.759 452.128C480.535 452.976 480.231 453.912 479.847 454.936C479.463 455.96 479.079 456.896 478.695 457.744H477.159C477.319 457.152 477.471 456.504 477.615 455.8C477.759 455.096 477.895 454.408 478.023 453.736C478.151 453.048 478.255 452.424 478.335 451.864H480.591Z" fill="black"/>
+<rect x="2" y="2" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M289 124C289 119.582 292.582 116 297 116H527.023C529.968 116 532.674 117.618 534.069 120.212L578.693 203.212C579.964 205.577 579.964 208.423 578.693 210.788L534.069 293.788C532.674 296.382 529.968 298 527.023 298H297C292.582 298 289 294.418 289 290V124Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M435.716 181.864C437.956 181.864 439.588 182.304 440.612 183.184C441.636 184.064 442.148 185.304 442.148 186.904C442.148 187.848 441.932 188.736 441.5 189.568C441.084 190.384 440.372 191.048 439.364 191.56C438.372 192.072 437.02 192.328 435.308 192.328H433.34V199H431.18V181.864H435.716ZM435.524 183.712H433.34V190.48H435.068C436.7 190.48 437.916 190.216 438.716 189.688C439.516 189.16 439.916 188.264 439.916 187C439.916 185.896 439.556 185.072 438.836 184.528C438.132 183.984 437.028 183.712 435.524 183.712ZM450.295 185.92C451.863 185.92 453.023 186.264 453.775 186.952C454.527 187.64 454.903 188.736 454.903 190.24V199H453.367L452.959 197.176H452.863C452.303 197.88 451.711 198.4 451.087 198.736C450.463 199.072 449.615 199.24 448.543 199.24C447.375 199.24 446.407 198.936 445.639 198.328C444.871 197.704 444.487 196.736 444.487 195.424C444.487 194.144 444.991 193.16 445.999 192.472C447.007 191.768 448.559 191.384 450.655 191.32L452.839 191.248V190.48C452.839 189.408 452.607 188.664 452.143 188.248C451.679 187.832 451.023 187.624 450.175 187.624C449.503 187.624 448.863 187.728 448.255 187.936C447.647 188.128 447.079 188.352 446.551 188.608L445.903 187.024C446.463 186.72 447.127 186.464 447.895 186.256C448.663 186.032 449.463 185.92 450.295 185.92ZM452.815 192.712L450.919 192.784C449.319 192.848 448.207 193.104 447.583 193.552C446.975 194 446.671 194.632 446.671 195.448C446.671 196.168 446.887 196.696 447.319 197.032C447.767 197.368 448.335 197.536 449.023 197.536C450.095 197.536 450.991 197.24 451.711 196.648C452.447 196.04 452.815 195.112 452.815 193.864V192.712ZM463.436 185.896C464.284 185.896 465.044 186.056 465.716 186.376C466.404 186.696 466.988 187.184 467.468 187.84H467.588L467.876 186.136H469.556V199.216C469.556 201.056 469.084 202.44 468.14 203.368C467.212 204.296 465.764 204.76 463.796 204.76C461.908 204.76 460.364 204.488 459.164 203.944V202C460.428 202.672 462.012 203.008 463.916 203.008C465.02 203.008 465.884 202.68 466.508 202.024C467.148 201.384 467.468 200.504 467.468 199.384V198.88C467.468 198.688 467.476 198.416 467.492 198.064C467.508 197.696 467.524 197.44 467.54 197.296H467.444C466.58 198.592 465.252 199.24 463.46 199.24C461.796 199.24 460.492 198.656 459.548 197.488C458.62 196.32 458.156 194.688 458.156 192.592C458.156 190.544 458.62 188.92 459.548 187.72C460.492 186.504 461.788 185.896 463.436 185.896ZM463.724 187.672C462.652 187.672 461.82 188.104 461.228 188.968C460.636 189.816 460.34 191.032 460.34 192.616C460.34 194.2 460.628 195.416 461.204 196.264C461.78 197.096 462.636 197.512 463.772 197.512C465.068 197.512 466.012 197.168 466.604 196.48C467.196 195.776 467.492 194.648 467.492 193.096V192.592C467.492 190.848 467.188 189.592 466.58 188.824C465.972 188.056 465.02 187.672 463.724 187.672ZM478.61 185.896C479.698 185.896 480.642 186.136 481.442 186.616C482.242 187.096 482.85 187.776 483.266 188.656C483.698 189.52 483.914 190.536 483.914 191.704V192.976H475.106C475.138 194.432 475.506 195.544 476.21 196.312C476.93 197.064 477.93 197.44 479.21 197.44C480.026 197.44 480.746 197.368 481.37 197.224C482.01 197.064 482.666 196.84 483.338 196.552V198.4C482.682 198.688 482.034 198.896 481.394 199.024C480.754 199.168 479.994 199.24 479.114 199.24C477.882 199.24 476.802 198.992 475.874 198.496C474.946 198 474.218 197.264 473.69 196.288C473.178 195.312 472.922 194.104 472.922 192.664C472.922 191.256 473.154 190.048 473.618 189.04C474.098 188.032 474.762 187.256 475.61 186.712C476.474 186.168 477.474 185.896 478.61 185.896ZM478.586 187.624C477.578 187.624 476.778 187.952 476.186 188.608C475.61 189.248 475.266 190.144 475.154 191.296H481.706C481.69 190.208 481.434 189.328 480.938 188.656C480.442 187.968 479.658 187.624 478.586 187.624ZM424.101 214.864C423.877 215.792 423.645 216.8 423.405 217.888C423.181 218.96 423.013 219.912 422.901 220.744H420.621L420.453 220.48C420.661 219.648 420.957 218.72 421.341 217.696C421.741 216.672 422.141 215.728 422.541 214.864H424.101ZM419.661 214.864C419.437 215.792 419.213 216.8 418.989 217.888C418.765 218.96 418.589 219.912 418.461 220.744H416.205L416.061 220.48C416.285 219.648 416.581 218.72 416.949 217.696C417.333 216.672 417.725 215.728 418.125 214.864H419.661ZM433.086 214.864L426.702 232H424.638L431.022 214.864H433.086ZM437.48 232H435.368V213.76H437.48V232ZM452.74 225.544C452.74 227.672 452.196 229.32 451.108 230.488C450.036 231.656 448.58 232.24 446.74 232.24C445.604 232.24 444.588 231.984 443.692 231.472C442.812 230.944 442.116 230.184 441.604 229.192C441.092 228.184 440.836 226.968 440.836 225.544C440.836 223.416 441.364 221.776 442.42 220.624C443.492 219.472 444.956 218.896 446.812 218.896C447.964 218.896 448.988 219.16 449.884 219.688C450.78 220.2 451.476 220.952 451.972 221.944C452.484 222.92 452.74 224.12 452.74 225.544ZM443.02 225.544C443.02 227.064 443.316 228.272 443.908 229.168C444.516 230.048 445.476 230.488 446.788 230.488C448.084 230.488 449.036 230.048 449.644 229.168C450.252 228.272 450.556 227.064 450.556 225.544C450.556 224.024 450.252 222.832 449.644 221.968C449.036 221.104 448.076 220.672 446.764 220.672C445.452 220.672 444.5 221.104 443.908 221.968C443.316 222.832 443.02 224.024 443.02 225.544ZM460.647 218.896C461.495 218.896 462.255 219.056 462.927 219.376C463.615 219.696 464.199 220.184 464.679 220.84H464.799L465.087 219.136H466.767V232.216C466.767 234.056 466.295 235.44 465.351 236.368C464.423 237.296 462.975 237.76 461.007 237.76C459.119 237.76 457.575 237.488 456.375 236.944V235C457.639 235.672 459.223 236.008 461.127 236.008C462.231 236.008 463.095 235.68 463.719 235.024C464.359 234.384 464.679 233.504 464.679 232.384V231.88C464.679 231.688 464.687 231.416 464.703 231.064C464.719 230.696 464.735 230.44 464.751 230.296H464.655C463.791 231.592 462.463 232.24 460.671 232.24C459.007 232.24 457.703 231.656 456.759 230.488C455.831 229.32 455.367 227.688 455.367 225.592C455.367 223.544 455.831 221.92 456.759 220.72C457.703 219.504 458.999 218.896 460.647 218.896ZM460.935 220.672C459.863 220.672 459.031 221.104 458.439 221.968C457.847 222.816 457.551 224.032 457.551 225.616C457.551 227.2 457.839 228.416 458.415 229.264C458.991 230.096 459.847 230.512 460.983 230.512C462.279 230.512 463.223 230.168 463.815 229.48C464.407 228.776 464.703 227.648 464.703 226.096V225.592C464.703 223.848 464.399 222.592 463.791 221.824C463.183 221.056 462.231 220.672 460.935 220.672ZM471.933 214.312C472.253 214.312 472.533 214.424 472.773 214.648C473.029 214.856 473.157 215.192 473.157 215.656C473.157 216.12 473.029 216.464 472.773 216.688C472.533 216.896 472.253 217 471.933 217C471.581 217 471.285 216.896 471.045 216.688C470.805 216.464 470.685 216.12 470.685 215.656C470.685 215.192 470.805 214.856 471.045 214.648C471.285 214.424 471.581 214.312 471.933 214.312ZM472.965 219.136V232H470.853V219.136H472.965ZM483.232 218.896C484.768 218.896 485.928 219.272 486.712 220.024C487.496 220.776 487.888 221.976 487.888 223.624V232H485.8V223.768C485.8 221.704 484.84 220.672 482.92 220.672C481.496 220.672 480.512 221.072 479.968 221.872C479.424 222.672 479.152 223.824 479.152 225.328V232H477.04V219.136H478.744L479.056 220.888H479.176C479.592 220.216 480.168 219.72 480.904 219.4C481.64 219.064 482.416 218.896 483.232 218.896ZM497.762 214.864L497.93 215.128C497.722 215.976 497.418 216.912 497.018 217.936C496.634 218.96 496.25 219.896 495.866 220.744H494.282C494.426 220.152 494.578 219.504 494.738 218.8C494.898 218.096 495.042 217.408 495.17 216.736C495.314 216.048 495.426 215.424 495.506 214.864H497.762ZM493.322 214.864L493.49 215.128C493.266 215.976 492.962 216.912 492.578 217.936C492.194 218.96 491.81 219.896 491.426 220.744H489.89C490.05 220.152 490.202 219.504 490.346 218.8C490.49 218.096 490.626 217.408 490.754 216.736C490.882 216.048 490.986 215.424 491.066 214.864H493.322Z" fill="black"/>
+<path d="M151 124C151 119.582 154.582 116 159 116H329.023C331.968 116 334.674 117.618 336.069 120.212L380.693 203.212C381.964 205.577 381.964 208.423 380.693 210.788L336.069 293.788C334.674 296.382 331.968 298 329.023 298H159C154.582 298 151 294.418 151 290V124Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 181.864C222.956 181.864 224.588 182.304 225.612 183.184C226.636 184.064 227.148 185.304 227.148 186.904C227.148 187.848 226.932 188.736 226.5 189.568C226.084 190.384 225.372 191.048 224.364 191.56C223.372 192.072 222.02 192.328 220.308 192.328H218.34V199H216.18V181.864H220.716ZM220.524 183.712H218.34V190.48H220.068C221.7 190.48 222.916 190.216 223.716 189.688C224.516 189.16 224.916 188.264 224.916 187C224.916 185.896 224.556 185.072 223.836 184.528C223.132 183.984 222.028 183.712 220.524 183.712ZM235.295 185.92C236.863 185.92 238.023 186.264 238.775 186.952C239.527 187.64 239.903 188.736 239.903 190.24V199H238.367L237.959 197.176H237.863C237.303 197.88 236.711 198.4 236.087 198.736C235.463 199.072 234.615 199.24 233.543 199.24C232.375 199.24 231.407 198.936 230.639 198.328C229.871 197.704 229.487 196.736 229.487 195.424C229.487 194.144 229.991 193.16 230.999 192.472C232.007 191.768 233.559 191.384 235.655 191.32L237.839 191.248V190.48C237.839 189.408 237.607 188.664 237.143 188.248C236.679 187.832 236.023 187.624 235.175 187.624C234.503 187.624 233.863 187.728 233.255 187.936C232.647 188.128 232.079 188.352 231.551 188.608L230.903 187.024C231.463 186.72 232.127 186.464 232.895 186.256C233.663 186.032 234.463 185.92 235.295 185.92ZM237.815 192.712L235.919 192.784C234.319 192.848 233.207 193.104 232.583 193.552C231.975 194 231.671 194.632 231.671 195.448C231.671 196.168 231.887 196.696 232.319 197.032C232.767 197.368 233.335 197.536 234.023 197.536C235.095 197.536 235.991 197.24 236.711 196.648C237.447 196.04 237.815 195.112 237.815 193.864V192.712ZM248.436 185.896C249.284 185.896 250.044 186.056 250.716 186.376C251.404 186.696 251.988 187.184 252.468 187.84H252.588L252.876 186.136H254.556V199.216C254.556 201.056 254.084 202.44 253.14 203.368C252.212 204.296 250.764 204.76 248.796 204.76C246.908 204.76 245.364 204.488 244.164 203.944V202C245.428 202.672 247.012 203.008 248.916 203.008C250.02 203.008 250.884 202.68 251.508 202.024C252.148 201.384 252.468 200.504 252.468 199.384V198.88C252.468 198.688 252.476 198.416 252.492 198.064C252.508 197.696 252.524 197.44 252.54 197.296H252.444C251.58 198.592 250.252 199.24 248.46 199.24C246.796 199.24 245.492 198.656 244.548 197.488C243.62 196.32 243.156 194.688 243.156 192.592C243.156 190.544 243.62 188.92 244.548 187.72C245.492 186.504 246.788 185.896 248.436 185.896ZM248.724 187.672C247.652 187.672 246.82 188.104 246.228 188.968C245.636 189.816 245.34 191.032 245.34 192.616C245.34 194.2 245.628 195.416 246.204 196.264C246.78 197.096 247.636 197.512 248.772 197.512C250.068 197.512 251.012 197.168 251.604 196.48C252.196 195.776 252.492 194.648 252.492 193.096V192.592C252.492 190.848 252.188 189.592 251.58 188.824C250.972 188.056 250.02 187.672 248.724 187.672ZM263.61 185.896C264.698 185.896 265.642 186.136 266.442 186.616C267.242 187.096 267.85 187.776 268.266 188.656C268.698 189.52 268.914 190.536 268.914 191.704V192.976H260.106C260.138 194.432 260.506 195.544 261.21 196.312C261.93 197.064 262.93 197.44 264.21 197.44C265.026 197.44 265.746 197.368 266.37 197.224C267.01 197.064 267.666 196.84 268.338 196.552V198.4C267.682 198.688 267.034 198.896 266.394 199.024C265.754 199.168 264.994 199.24 264.114 199.24C262.882 199.24 261.802 198.992 260.874 198.496C259.946 198 259.218 197.264 258.69 196.288C258.178 195.312 257.922 194.104 257.922 192.664C257.922 191.256 258.154 190.048 258.618 189.04C259.098 188.032 259.762 187.256 260.61 186.712C261.474 186.168 262.474 185.896 263.61 185.896ZM263.586 187.624C262.578 187.624 261.778 187.952 261.186 188.608C260.61 189.248 260.266 190.144 260.154 191.296H266.706C266.69 190.208 266.434 189.328 265.938 188.656C265.442 187.968 264.658 187.624 263.586 187.624ZM215.863 214.864C215.639 215.792 215.407 216.8 215.167 217.888C214.943 218.96 214.775 219.912 214.663 220.744H212.383L212.215 220.48C212.423 219.648 212.719 218.72 213.103 217.696C213.503 216.672 213.903 215.728 214.303 214.864H215.863ZM211.423 214.864C211.199 215.792 210.975 216.8 210.751 217.888C210.527 218.96 210.351 219.912 210.223 220.744H207.967L207.823 220.48C208.047 219.648 208.343 218.72 208.711 217.696C209.095 216.672 209.487 215.728 209.887 214.864H211.423ZM224.848 214.864L218.464 232H216.4L222.784 214.864H224.848ZM232.002 218.92C233.57 218.92 234.73 219.264 235.482 219.952C236.234 220.64 236.61 221.736 236.61 223.24V232H235.074L234.666 230.176H234.57C234.01 230.88 233.418 231.4 232.794 231.736C232.17 232.072 231.322 232.24 230.25 232.24C229.082 232.24 228.114 231.936 227.346 231.328C226.578 230.704 226.194 229.736 226.194 228.424C226.194 227.144 226.698 226.16 227.706 225.472C228.714 224.768 230.266 224.384 232.362 224.32L234.546 224.248V223.48C234.546 222.408 234.314 221.664 233.85 221.248C233.386 220.832 232.73 220.624 231.882 220.624C231.21 220.624 230.57 220.728 229.962 220.936C229.354 221.128 228.786 221.352 228.258 221.608L227.61 220.024C228.17 219.72 228.834 219.464 229.602 219.256C230.37 219.032 231.17 218.92 232.002 218.92ZM234.522 225.712L232.626 225.784C231.026 225.848 229.914 226.104 229.29 226.552C228.682 227 228.378 227.632 228.378 228.448C228.378 229.168 228.594 229.696 229.026 230.032C229.474 230.368 230.042 230.536 230.73 230.536C231.802 230.536 232.698 230.24 233.418 229.648C234.154 229.04 234.522 228.112 234.522 226.864V225.712ZM246.703 218.896C248.287 218.896 249.559 219.448 250.519 220.552C251.495 221.656 251.983 223.32 251.983 225.544C251.983 227.736 251.495 229.4 250.519 230.536C249.559 231.672 248.279 232.24 246.679 232.24C245.687 232.24 244.863 232.056 244.207 231.688C243.567 231.32 243.063 230.88 242.695 230.368H242.551C242.567 230.64 242.591 230.984 242.623 231.4C242.671 231.816 242.695 232.176 242.695 232.48V237.76H240.583V219.136H242.311L242.599 220.888H242.695C243.079 220.328 243.583 219.856 244.207 219.472C244.831 219.088 245.663 218.896 246.703 218.896ZM246.319 220.672C245.007 220.672 244.079 221.04 243.535 221.776C242.991 222.512 242.711 223.632 242.695 225.136V225.544C242.695 227.128 242.951 228.352 243.463 229.216C243.991 230.064 244.959 230.488 246.367 230.488C247.135 230.488 247.775 230.28 248.287 229.864C248.799 229.432 249.175 228.84 249.415 228.088C249.671 227.336 249.799 226.48 249.799 225.52C249.799 224.048 249.511 222.872 248.935 221.992C248.375 221.112 247.503 220.672 246.319 220.672ZM261.469 218.896C263.053 218.896 264.325 219.448 265.285 220.552C266.261 221.656 266.749 223.32 266.749 225.544C266.749 227.736 266.261 229.4 265.285 230.536C264.325 231.672 263.045 232.24 261.445 232.24C260.453 232.24 259.629 232.056 258.973 231.688C258.333 231.32 257.829 230.88 257.461 230.368H257.317C257.333 230.64 257.357 230.984 257.389 231.4C257.437 231.816 257.461 232.176 257.461 232.48V237.76H255.349V219.136H257.077L257.365 220.888H257.461C257.845 220.328 258.349 219.856 258.973 219.472C259.597 219.088 260.429 218.896 261.469 218.896ZM261.085 220.672C259.773 220.672 258.845 221.04 258.301 221.776C257.757 222.512 257.477 223.632 257.461 225.136V225.544C257.461 227.128 257.717 228.352 258.229 229.216C258.757 230.064 259.725 230.488 261.133 230.488C261.901 230.488 262.541 230.28 263.053 229.864C263.565 229.432 263.941 228.84 264.181 228.088C264.437 227.336 264.565 226.48 264.565 225.52C264.565 224.048 264.277 222.872 263.701 221.992C263.141 221.112 262.269 220.672 261.085 220.672ZM276 214.864L276.168 215.128C275.96 215.976 275.656 216.912 275.256 217.936C274.872 218.96 274.488 219.896 274.104 220.744H272.52C272.664 220.152 272.816 219.504 272.976 218.8C273.136 218.096 273.28 217.408 273.408 216.736C273.552 216.048 273.664 215.424 273.744 214.864H276ZM271.56 214.864L271.728 215.128C271.504 215.976 271.2 216.912 270.816 217.936C270.432 218.96 270.048 219.896 269.664 220.744H268.128C268.288 220.152 268.44 219.504 268.584 218.8C268.728 218.096 268.864 217.408 268.992 216.736C269.12 216.048 269.224 215.424 269.304 214.864H271.56Z" fill="black"/>
+<path d="M306.501 37.864C308.741 37.864 310.373 38.304 311.397 39.184C312.421 40.064 312.933 41.304 312.933 42.904C312.933 43.848 312.717 44.736 312.285 45.568C311.869 46.384 311.157 47.048 310.149 47.56C309.157 48.072 307.805 48.328 306.093 48.328H304.125V55H301.965V37.864H306.501ZM306.309 39.712H304.125V46.48H305.853C307.485 46.48 308.701 46.216 309.501 45.688C310.301 45.16 310.701 44.264 310.701 43C310.701 41.896 310.341 41.072 309.621 40.528C308.917 39.984 307.813 39.712 306.309 39.712ZM321.08 41.92C322.648 41.92 323.808 42.264 324.56 42.952C325.312 43.64 325.688 44.736 325.688 46.24V55H324.152L323.744 53.176H323.648C323.088 53.88 322.496 54.4 321.872 54.736C321.248 55.072 320.4 55.24 319.328 55.24C318.16 55.24 317.192 54.936 316.424 54.328C315.656 53.704 315.272 52.736 315.272 51.424C315.272 50.144 315.776 49.16 316.784 48.472C317.792 47.768 319.344 47.384 321.44 47.32L323.624 47.248V46.48C323.624 45.408 323.392 44.664 322.928 44.248C322.464 43.832 321.808 43.624 320.96 43.624C320.288 43.624 319.648 43.728 319.04 43.936C318.432 44.128 317.864 44.352 317.336 44.608L316.688 43.024C317.248 42.72 317.912 42.464 318.68 42.256C319.448 42.032 320.248 41.92 321.08 41.92ZM323.6 48.712L321.704 48.784C320.104 48.848 318.992 49.104 318.368 49.552C317.76 50 317.456 50.632 317.456 51.448C317.456 52.168 317.672 52.696 318.104 53.032C318.552 53.368 319.12 53.536 319.808 53.536C320.88 53.536 321.776 53.24 322.496 52.648C323.232 52.04 323.6 51.112 323.6 49.864V48.712ZM334.221 41.896C335.069 41.896 335.829 42.056 336.501 42.376C337.189 42.696 337.773 43.184 338.253 43.84H338.373L338.661 42.136H340.341V55.216C340.341 57.056 339.869 58.44 338.925 59.368C337.997 60.296 336.549 60.76 334.581 60.76C332.693 60.76 331.149 60.488 329.949 59.944V58C331.213 58.672 332.797 59.008 334.701 59.008C335.805 59.008 336.669 58.68 337.293 58.024C337.933 57.384 338.253 56.504 338.253 55.384V54.88C338.253 54.688 338.261 54.416 338.277 54.064C338.293 53.696 338.309 53.44 338.325 53.296H338.229C337.365 54.592 336.037 55.24 334.245 55.24C332.581 55.24 331.277 54.656 330.333 53.488C329.405 52.32 328.941 50.688 328.941 48.592C328.941 46.544 329.405 44.92 330.333 43.72C331.277 42.504 332.573 41.896 334.221 41.896ZM334.509 43.672C333.437 43.672 332.605 44.104 332.013 44.968C331.421 45.816 331.125 47.032 331.125 48.616C331.125 50.2 331.413 51.416 331.989 52.264C332.565 53.096 333.421 53.512 334.557 53.512C335.853 53.512 336.797 53.168 337.389 52.48C337.981 51.776 338.277 50.648 338.277 49.096V48.592C338.277 46.848 337.973 45.592 337.365 44.824C336.757 44.056 335.805 43.672 334.509 43.672ZM349.395 41.896C350.483 41.896 351.427 42.136 352.227 42.616C353.027 43.096 353.635 43.776 354.051 44.656C354.483 45.52 354.699 46.536 354.699 47.704V48.976H345.891C345.923 50.432 346.291 51.544 346.995 52.312C347.715 53.064 348.715 53.44 349.995 53.44C350.811 53.44 351.531 53.368 352.155 53.224C352.795 53.064 353.451 52.84 354.123 52.552V54.4C353.467 54.688 352.819 54.896 352.179 55.024C351.539 55.168 350.779 55.24 349.899 55.24C348.667 55.24 347.587 54.992 346.659 54.496C345.731 54 345.003 53.264 344.475 52.288C343.963 51.312 343.707 50.104 343.707 48.664C343.707 47.256 343.939 46.048 344.403 45.04C344.883 44.032 345.547 43.256 346.395 42.712C347.259 42.168 348.259 41.896 349.395 41.896ZM349.371 43.624C348.363 43.624 347.563 43.952 346.971 44.608C346.395 45.248 346.051 46.144 345.939 47.296H352.491C352.475 46.208 352.219 45.328 351.723 44.656C351.227 43.968 350.443 43.624 349.371 43.624ZM362.99 37.864C365.118 37.864 366.686 38.272 367.694 39.088C368.718 39.888 369.23 41.104 369.23 42.736C369.23 43.648 369.062 44.408 368.726 45.016C368.39 45.624 367.958 46.112 367.43 46.48C366.918 46.848 366.374 47.128 365.798 47.32L370.502 55H367.982L363.83 47.92H360.422V55H358.262V37.864H362.99ZM362.87 39.736H360.422V46.096H362.99C364.382 46.096 365.398 45.824 366.038 45.28C366.678 44.72 366.998 43.904 366.998 42.832C366.998 41.712 366.662 40.92 365.99 40.456C365.318 39.976 364.278 39.736 362.87 39.736ZM384.087 48.544C384.087 50.672 383.543 52.32 382.455 53.488C381.383 54.656 379.927 55.24 378.087 55.24C376.951 55.24 375.935 54.984 375.039 54.472C374.159 53.944 373.463 53.184 372.951 52.192C372.439 51.184 372.183 49.968 372.183 48.544C372.183 46.416 372.711 44.776 373.767 43.624C374.839 42.472 376.303 41.896 378.159 41.896C379.311 41.896 380.335 42.16 381.231 42.688C382.127 43.2 382.823 43.952 383.319 44.944C383.831 45.92 384.087 47.12 384.087 48.544ZM374.367 48.544C374.367 50.064 374.663 51.272 375.255 52.168C375.863 53.048 376.823 53.488 378.135 53.488C379.431 53.488 380.383 53.048 380.991 52.168C381.599 51.272 381.903 50.064 381.903 48.544C381.903 47.024 381.599 45.832 380.991 44.968C380.383 44.104 379.423 43.672 378.111 43.672C376.799 43.672 375.847 44.104 375.255 44.968C374.663 45.832 374.367 47.024 374.367 48.544ZM398.187 42.136V55H396.459L396.147 53.296H396.051C395.635 53.968 395.059 54.464 394.323 54.784C393.587 55.088 392.803 55.24 391.971 55.24C390.419 55.24 389.251 54.872 388.467 54.136C387.683 53.384 387.291 52.192 387.291 50.56V42.136H389.427V50.416C389.427 52.464 390.379 53.488 392.283 53.488C393.707 53.488 394.691 53.088 395.235 52.288C395.795 51.488 396.075 50.336 396.075 48.832V42.136H398.187ZM406.566 53.512C406.886 53.512 407.214 53.488 407.55 53.44C407.886 53.392 408.158 53.328 408.366 53.248V54.856C408.142 54.968 407.822 55.056 407.406 55.12C406.99 55.2 406.59 55.24 406.206 55.24C405.534 55.24 404.91 55.128 404.334 54.904C403.774 54.664 403.318 54.256 402.966 53.68C402.614 53.104 402.438 52.296 402.438 51.256V43.768H400.614V42.76L402.462 41.92L403.302 39.184H404.55V42.136H408.27V43.768H404.55V51.208C404.55 51.992 404.734 52.576 405.102 52.96C405.486 53.328 405.974 53.512 406.566 53.512ZM415.91 41.896C416.998 41.896 417.942 42.136 418.742 42.616C419.542 43.096 420.15 43.776 420.566 44.656C420.998 45.52 421.214 46.536 421.214 47.704V48.976H412.406C412.438 50.432 412.806 51.544 413.51 52.312C414.23 53.064 415.23 53.44 416.51 53.44C417.326 53.44 418.046 53.368 418.67 53.224C419.31 53.064 419.966 52.84 420.638 52.552V54.4C419.982 54.688 419.334 54.896 418.694 55.024C418.054 55.168 417.294 55.24 416.414 55.24C415.182 55.24 414.102 54.992 413.174 54.496C412.246 54 411.518 53.264 410.99 52.288C410.478 51.312 410.222 50.104 410.222 48.664C410.222 47.256 410.454 46.048 410.918 45.04C411.398 44.032 412.062 43.256 412.91 42.712C413.774 42.168 414.774 41.896 415.91 41.896ZM415.886 43.624C414.878 43.624 414.078 43.952 413.486 44.608C412.91 45.248 412.566 46.144 412.454 47.296H419.006C418.99 46.208 418.734 45.328 418.238 44.656C417.742 43.968 416.958 43.624 415.886 43.624ZM430.489 41.896C430.729 41.896 430.985 41.912 431.257 41.944C431.529 41.96 431.777 41.992 432.001 42.04L431.737 43.984C431.529 43.936 431.297 43.896 431.041 43.864C430.785 43.832 430.553 43.816 430.345 43.816C429.689 43.816 429.073 44 428.497 44.368C427.921 44.72 427.457 45.224 427.105 45.88C426.769 46.52 426.601 47.272 426.601 48.136V55H424.489V42.136H426.217L426.457 44.488H426.553C426.953 43.784 427.481 43.176 428.137 42.664C428.809 42.152 429.593 41.896 430.489 41.896ZM285.535 70.864C285.311 71.792 285.079 72.8 284.839 73.888C284.615 74.96 284.447 75.912 284.335 76.744H282.055L281.887 76.48C282.095 75.648 282.391 74.72 282.775 73.696C283.175 72.672 283.575 71.728 283.975 70.864H285.535ZM281.095 70.864C280.871 71.792 280.647 72.8 280.423 73.888C280.199 74.96 280.023 75.912 279.895 76.744H277.639L277.495 76.48C277.719 75.648 278.015 74.72 278.383 73.696C278.767 72.672 279.159 71.728 279.559 70.864H281.095ZM294.52 70.864L288.136 88H286.072L292.456 70.864H294.52ZM301.674 74.92C303.242 74.92 304.402 75.264 305.154 75.952C305.906 76.64 306.282 77.736 306.282 79.24V88H304.746L304.338 86.176H304.242C303.682 86.88 303.09 87.4 302.466 87.736C301.842 88.072 300.994 88.24 299.922 88.24C298.754 88.24 297.786 87.936 297.018 87.328C296.25 86.704 295.866 85.736 295.866 84.424C295.866 83.144 296.37 82.16 297.378 81.472C298.386 80.768 299.938 80.384 302.034 80.32L304.218 80.248V79.48C304.218 78.408 303.986 77.664 303.522 77.248C303.058 76.832 302.402 76.624 301.554 76.624C300.882 76.624 300.242 76.728 299.634 76.936C299.026 77.128 298.458 77.352 297.93 77.608L297.282 76.024C297.842 75.72 298.506 75.464 299.274 75.256C300.042 75.032 300.842 74.92 301.674 74.92ZM304.194 81.712L302.298 81.784C300.698 81.848 299.586 82.104 298.962 82.552C298.354 83 298.05 83.632 298.05 84.448C298.05 85.168 298.266 85.696 298.698 86.032C299.146 86.368 299.714 86.536 300.402 86.536C301.474 86.536 302.37 86.24 303.09 85.648C303.826 85.04 304.194 84.112 304.194 82.864V81.712ZM316.375 74.896C317.959 74.896 319.231 75.448 320.191 76.552C321.167 77.656 321.655 79.32 321.655 81.544C321.655 83.736 321.167 85.4 320.191 86.536C319.231 87.672 317.951 88.24 316.351 88.24C315.359 88.24 314.535 88.056 313.879 87.688C313.239 87.32 312.735 86.88 312.367 86.368H312.223C312.239 86.64 312.263 86.984 312.295 87.4C312.343 87.816 312.367 88.176 312.367 88.48V93.76H310.255V75.136H311.983L312.271 76.888H312.367C312.751 76.328 313.255 75.856 313.879 75.472C314.503 75.088 315.335 74.896 316.375 74.896ZM315.991 76.672C314.679 76.672 313.751 77.04 313.207 77.776C312.663 78.512 312.383 79.632 312.367 81.136V81.544C312.367 83.128 312.623 84.352 313.135 85.216C313.663 86.064 314.631 86.488 316.039 86.488C316.807 86.488 317.447 86.28 317.959 85.864C318.471 85.432 318.847 84.84 319.087 84.088C319.343 83.336 319.471 82.48 319.471 81.52C319.471 80.048 319.183 78.872 318.607 77.992C318.047 77.112 317.175 76.672 315.991 76.672ZM331.14 74.896C332.724 74.896 333.996 75.448 334.956 76.552C335.932 77.656 336.42 79.32 336.42 81.544C336.42 83.736 335.932 85.4 334.956 86.536C333.996 87.672 332.716 88.24 331.116 88.24C330.124 88.24 329.3 88.056 328.644 87.688C328.004 87.32 327.5 86.88 327.132 86.368H326.988C327.004 86.64 327.028 86.984 327.06 87.4C327.108 87.816 327.132 88.176 327.132 88.48V93.76H325.02V75.136H326.748L327.036 76.888H327.132C327.516 76.328 328.02 75.856 328.644 75.472C329.268 75.088 330.1 74.896 331.14 74.896ZM330.756 76.672C329.444 76.672 328.516 77.04 327.972 77.776C327.428 78.512 327.148 79.632 327.132 81.136V81.544C327.132 83.128 327.388 84.352 327.9 85.216C328.428 86.064 329.396 86.488 330.804 86.488C331.572 86.488 332.212 86.28 332.724 85.864C333.236 85.432 333.612 84.84 333.852 84.088C334.108 83.336 334.236 82.48 334.236 81.52C334.236 80.048 333.948 78.872 333.372 77.992C332.812 77.112 331.94 76.672 330.756 76.672ZM345.672 70.864L345.84 71.128C345.632 71.976 345.328 72.912 344.928 73.936C344.544 74.96 344.16 75.896 343.776 76.744H342.192C342.336 76.152 342.488 75.504 342.648 74.8C342.808 74.096 342.952 73.408 343.08 72.736C343.224 72.048 343.336 71.424 343.416 70.864H345.672ZM341.232 70.864L341.4 71.128C341.176 71.976 340.872 72.912 340.488 73.936C340.104 74.96 339.72 75.896 339.336 76.744H337.8C337.96 76.152 338.112 75.504 338.256 74.8C338.4 74.096 338.536 73.408 338.664 72.736C338.792 72.048 338.896 71.424 338.976 70.864H341.232ZM353.571 83.344L362.619 79.576L353.571 75.28V73.408L364.875 79.048V80.248L353.571 85.216V83.344ZM380.668 70.864C380.444 71.792 380.212 72.8 379.972 73.888C379.748 74.96 379.58 75.912 379.468 76.744H377.188L377.02 76.48C377.228 75.648 377.524 74.72 377.908 73.696C378.308 72.672 378.708 71.728 379.108 70.864H380.668ZM376.228 70.864C376.004 71.792 375.78 72.8 375.556 73.888C375.332 74.96 375.156 75.912 375.028 76.744H372.772L372.628 76.48C372.852 75.648 373.148 74.72 373.516 73.696C373.9 72.672 374.292 71.728 374.692 70.864H376.228ZM389.653 70.864L383.269 88H381.205L387.589 70.864H389.653ZM394.047 88H391.935V69.76H394.047V88ZM409.306 81.544C409.306 83.672 408.762 85.32 407.674 86.488C406.602 87.656 405.146 88.24 403.306 88.24C402.17 88.24 401.154 87.984 400.258 87.472C399.378 86.944 398.682 86.184 398.17 85.192C397.658 84.184 397.402 82.968 397.402 81.544C397.402 79.416 397.93 77.776 398.986 76.624C400.058 75.472 401.522 74.896 403.378 74.896C404.53 74.896 405.554 75.16 406.45 75.688C407.346 76.2 408.042 76.952 408.538 77.944C409.05 78.92 409.306 80.12 409.306 81.544ZM399.586 81.544C399.586 83.064 399.882 84.272 400.474 85.168C401.082 86.048 402.042 86.488 403.354 86.488C404.65 86.488 405.602 86.048 406.21 85.168C406.818 84.272 407.122 83.064 407.122 81.544C407.122 80.024 406.818 78.832 406.21 77.968C405.602 77.104 404.642 76.672 403.33 76.672C402.018 76.672 401.066 77.104 400.474 77.968C399.882 78.832 399.586 80.024 399.586 81.544ZM417.213 74.896C418.061 74.896 418.821 75.056 419.493 75.376C420.181 75.696 420.765 76.184 421.245 76.84H421.365L421.653 75.136H423.333V88.216C423.333 90.056 422.861 91.44 421.917 92.368C420.989 93.296 419.541 93.76 417.573 93.76C415.685 93.76 414.141 93.488 412.941 92.944V91C414.205 91.672 415.789 92.008 417.693 92.008C418.797 92.008 419.661 91.68 420.285 91.024C420.925 90.384 421.245 89.504 421.245 88.384V87.88C421.245 87.688 421.253 87.416 421.269 87.064C421.285 86.696 421.301 86.44 421.317 86.296H421.221C420.357 87.592 419.029 88.24 417.237 88.24C415.573 88.24 414.269 87.656 413.325 86.488C412.397 85.32 411.933 83.688 411.933 81.592C411.933 79.544 412.397 77.92 413.325 76.72C414.269 75.504 415.565 74.896 417.213 74.896ZM417.501 76.672C416.429 76.672 415.597 77.104 415.005 77.968C414.413 78.816 414.117 80.032 414.117 81.616C414.117 83.2 414.405 84.416 414.981 85.264C415.557 86.096 416.413 86.512 417.549 86.512C418.845 86.512 419.789 86.168 420.381 85.48C420.973 84.776 421.269 83.648 421.269 82.096V81.592C421.269 79.848 420.965 78.592 420.357 77.824C419.749 77.056 418.797 76.672 417.501 76.672ZM428.499 70.312C428.819 70.312 429.099 70.424 429.339 70.648C429.595 70.856 429.723 71.192 429.723 71.656C429.723 72.12 429.595 72.464 429.339 72.688C429.099 72.896 428.819 73 428.499 73C428.147 73 427.851 72.896 427.611 72.688C427.371 72.464 427.251 72.12 427.251 71.656C427.251 71.192 427.371 70.856 427.611 70.648C427.851 70.424 428.147 70.312 428.499 70.312ZM429.531 75.136V88H427.419V75.136H429.531ZM439.798 74.896C441.334 74.896 442.494 75.272 443.278 76.024C444.062 76.776 444.454 77.976 444.454 79.624V88H442.366V79.768C442.366 77.704 441.406 76.672 439.486 76.672C438.062 76.672 437.078 77.072 436.534 77.872C435.99 78.672 435.718 79.824 435.718 81.328V88H433.606V75.136H435.31L435.622 76.888H435.742C436.158 76.216 436.734 75.72 437.47 75.4C438.206 75.064 438.982 74.896 439.798 74.896ZM454.328 70.864L454.496 71.128C454.288 71.976 453.984 72.912 453.584 73.936C453.2 74.96 452.816 75.896 452.432 76.744H450.848C450.992 76.152 451.144 75.504 451.304 74.8C451.464 74.096 451.608 73.408 451.736 72.736C451.88 72.048 451.992 71.424 452.072 70.864H454.328ZM449.888 70.864L450.056 71.128C449.832 71.976 449.528 72.912 449.144 73.936C448.76 74.96 448.376 75.896 447.992 76.744H446.456C446.616 76.152 446.768 75.504 446.912 74.8C447.056 74.096 447.192 73.408 447.32 72.736C447.448 72.048 447.552 71.424 447.632 70.864H449.888Z" fill="black"/>
+<path d="M536 205C534.895 205 534 205.895 534 207C534 208.105 534.895 209 536 209V205ZM759.5 207H761.5V205H759.5V207ZM759.5 588V590H761.5V588H759.5ZM534.586 586.586C533.805 587.367 533.805 588.633 534.586 589.414L547.314 602.142C548.095 602.923 549.361 602.923 550.142 602.142C550.923 601.361 550.923 600.095 550.142 599.314L538.828 588L550.142 576.686C550.923 575.905 550.923 574.639 550.142 573.858C549.361 573.077 548.095 573.077 547.314 573.858L534.586 586.586ZM536 209H759.5V205H536V209ZM757.5 207V588H761.5V207H757.5ZM759.5 586H536V590H759.5V586Z" fill="#232629"/>
+<path d="M791.579 413H789.035L782.963 404.816L781.211 406.352V413H779.051V395.864H781.211V404.312C781.691 403.768 782.179 403.224 782.675 402.68C783.171 402.136 783.667 401.592 784.163 401.048L788.795 395.864H791.315L784.523 403.328L791.579 413ZM794.702 395.312C795.022 395.312 795.302 395.424 795.542 395.648C795.798 395.856 795.926 396.192 795.926 396.656C795.926 397.12 795.798 397.464 795.542 397.688C795.302 397.896 795.022 398 794.702 398C794.35 398 794.054 397.896 793.814 397.688C793.574 397.464 793.454 397.12 793.454 396.656C793.454 396.192 793.574 395.856 793.814 395.648C794.054 395.424 794.35 395.312 794.702 395.312ZM795.734 400.136V413H793.622V400.136H795.734ZM805.81 399.896C806.05 399.896 806.306 399.912 806.578 399.944C806.85 399.96 807.098 399.992 807.322 400.04L807.058 401.984C806.85 401.936 806.618 401.896 806.362 401.864C806.106 401.832 805.874 401.816 805.666 401.816C805.01 401.816 804.394 402 803.818 402.368C803.242 402.72 802.778 403.224 802.426 403.88C802.09 404.52 801.922 405.272 801.922 406.136V413H799.81V400.136H801.538L801.778 402.488H801.874C802.274 401.784 802.802 401.176 803.458 400.664C804.13 400.152 804.914 399.896 805.81 399.896ZM810.804 395.312C811.124 395.312 811.404 395.424 811.644 395.648C811.9 395.856 812.028 396.192 812.028 396.656C812.028 397.12 811.9 397.464 811.644 397.688C811.404 397.896 811.124 398 810.804 398C810.452 398 810.156 397.896 809.916 397.688C809.676 397.464 809.556 397.12 809.556 396.656C809.556 396.192 809.676 395.856 809.916 395.648C810.156 395.424 810.452 395.312 810.804 395.312ZM811.836 400.136V413H809.724V400.136H811.836ZM820.471 399.896C821.319 399.896 822.079 400.056 822.751 400.376C823.439 400.696 824.023 401.184 824.503 401.84H824.623L824.911 400.136H826.591V413.216C826.591 415.056 826.119 416.44 825.175 417.368C824.247 418.296 822.799 418.76 820.831 418.76C818.943 418.76 817.399 418.488 816.199 417.944V416C817.463 416.672 819.047 417.008 820.951 417.008C822.055 417.008 822.919 416.68 823.543 416.024C824.183 415.384 824.503 414.504 824.503 413.384V412.88C824.503 412.688 824.511 412.416 824.527 412.064C824.543 411.696 824.559 411.44 824.575 411.296H824.479C823.615 412.592 822.287 413.24 820.495 413.24C818.831 413.24 817.527 412.656 816.583 411.488C815.655 410.32 815.191 408.688 815.191 406.592C815.191 404.544 815.655 402.92 816.583 401.72C817.527 400.504 818.823 399.896 820.471 399.896ZM820.759 401.672C819.687 401.672 818.855 402.104 818.263 402.968C817.671 403.816 817.375 405.032 817.375 406.616C817.375 408.2 817.663 409.416 818.239 410.264C818.815 411.096 819.671 411.512 820.807 411.512C822.103 411.512 823.047 411.168 823.639 410.48C824.231 409.776 824.527 408.648 824.527 407.096V406.592C824.527 404.848 824.223 403.592 823.615 402.824C823.007 402.056 822.055 401.672 820.759 401.672ZM835.549 399.92C837.117 399.92 838.277 400.264 839.029 400.952C839.781 401.64 840.157 402.736 840.157 404.24V413H838.621L838.213 411.176H838.117C837.557 411.88 836.965 412.4 836.341 412.736C835.717 413.072 834.869 413.24 833.797 413.24C832.629 413.24 831.661 412.936 830.893 412.328C830.125 411.704 829.741 410.736 829.741 409.424C829.741 408.144 830.245 407.16 831.253 406.472C832.261 405.768 833.813 405.384 835.909 405.32L838.093 405.248V404.48C838.093 403.408 837.861 402.664 837.397 402.248C836.933 401.832 836.277 401.624 835.429 401.624C834.757 401.624 834.117 401.728 833.509 401.936C832.901 402.128 832.333 402.352 831.805 402.608L831.157 401.024C831.717 400.72 832.381 400.464 833.149 400.256C833.917 400.032 834.717 399.92 835.549 399.92ZM838.069 406.712L836.173 406.784C834.573 406.848 833.461 407.104 832.837 407.552C832.229 408 831.925 408.632 831.925 409.448C831.925 410.168 832.141 410.696 832.573 411.032C833.021 411.368 833.589 411.536 834.277 411.536C835.349 411.536 836.245 411.24 836.965 410.648C837.701 410.04 838.069 409.112 838.069 407.864V406.712ZM858.242 399.896C859.698 399.896 860.786 400.272 861.506 401.024C862.226 401.776 862.586 402.976 862.586 404.624V413H860.498V404.72C860.498 402.688 859.626 401.672 857.882 401.672C856.634 401.672 855.738 402.032 855.194 402.752C854.666 403.472 854.402 404.52 854.402 405.896V413H852.314V404.72C852.314 402.688 851.434 401.672 849.674 401.672C848.378 401.672 847.482 402.072 846.986 402.872C846.49 403.672 846.242 404.824 846.242 406.328V413H844.13V400.136H845.834L846.146 401.888H846.266C846.666 401.216 847.202 400.72 847.874 400.4C848.562 400.064 849.29 399.896 850.058 399.896C852.074 399.896 853.386 400.616 853.994 402.056H854.114C854.546 401.32 855.13 400.776 855.866 400.424C856.602 400.072 857.394 399.896 858.242 399.896ZM867.64 395.312C867.96 395.312 868.24 395.424 868.48 395.648C868.736 395.856 868.864 396.192 868.864 396.656C868.864 397.12 868.736 397.464 868.48 397.688C868.24 397.896 867.96 398 867.64 398C867.288 398 866.992 397.896 866.752 397.688C866.512 397.464 866.392 397.12 866.392 396.656C866.392 396.192 866.512 395.856 866.752 395.648C866.992 395.424 867.288 395.312 867.64 395.312ZM868.672 400.136V413H866.56V400.136H868.672ZM872.435 411.704C872.435 411.112 872.579 410.696 872.867 410.456C873.155 410.216 873.499 410.096 873.899 410.096C874.299 410.096 874.651 410.216 874.955 410.456C875.259 410.696 875.411 411.112 875.411 411.704C875.411 412.28 875.259 412.696 874.955 412.952C874.651 413.208 874.299 413.336 873.899 413.336C873.499 413.336 873.155 413.208 872.867 412.952C872.579 412.696 872.435 412.28 872.435 411.704ZM883.993 395.864C886.233 395.864 887.865 396.304 888.889 397.184C889.913 398.064 890.425 399.304 890.425 400.904C890.425 401.848 890.209 402.736 889.777 403.568C889.361 404.384 888.649 405.048 887.641 405.56C886.649 406.072 885.297 406.328 883.585 406.328H881.617V413H879.457V395.864H883.993ZM883.801 397.712H881.617V404.48H883.345C884.977 404.48 886.193 404.216 886.993 403.688C887.793 403.16 888.193 402.264 888.193 401C888.193 399.896 887.833 399.072 887.113 398.528C886.409 397.984 885.305 397.712 883.801 397.712ZM898.572 399.92C900.14 399.92 901.3 400.264 902.052 400.952C902.804 401.64 903.18 402.736 903.18 404.24V413H901.644L901.236 411.176H901.14C900.58 411.88 899.988 412.4 899.364 412.736C898.74 413.072 897.892 413.24 896.82 413.24C895.652 413.24 894.684 412.936 893.916 412.328C893.148 411.704 892.764 410.736 892.764 409.424C892.764 408.144 893.268 407.16 894.276 406.472C895.284 405.768 896.836 405.384 898.932 405.32L901.116 405.248V404.48C901.116 403.408 900.884 402.664 900.42 402.248C899.956 401.832 899.3 401.624 898.452 401.624C897.78 401.624 897.14 401.728 896.532 401.936C895.924 402.128 895.356 402.352 894.828 402.608L894.18 401.024C894.74 400.72 895.404 400.464 896.172 400.256C896.94 400.032 897.74 399.92 898.572 399.92ZM901.092 406.712L899.196 406.784C897.596 406.848 896.484 407.104 895.86 407.552C895.252 408 894.948 408.632 894.948 409.448C894.948 410.168 895.164 410.696 895.596 411.032C896.044 411.368 896.612 411.536 897.3 411.536C898.372 411.536 899.268 411.24 899.988 410.648C900.724 410.04 901.092 409.112 901.092 407.864V406.712ZM911.713 399.896C912.561 399.896 913.321 400.056 913.993 400.376C914.681 400.696 915.265 401.184 915.745 401.84H915.865L916.153 400.136H917.833V413.216C917.833 415.056 917.361 416.44 916.417 417.368C915.489 418.296 914.041 418.76 912.073 418.76C910.185 418.76 908.641 418.488 907.441 417.944V416C908.705 416.672 910.289 417.008 912.193 417.008C913.297 417.008 914.161 416.68 914.785 416.024C915.425 415.384 915.745 414.504 915.745 413.384V412.88C915.745 412.688 915.753 412.416 915.769 412.064C915.785 411.696 915.801 411.44 915.817 411.296H915.721C914.857 412.592 913.529 413.24 911.737 413.24C910.073 413.24 908.769 412.656 907.825 411.488C906.897 410.32 906.433 408.688 906.433 406.592C906.433 404.544 906.897 402.92 907.825 401.72C908.769 400.504 910.065 399.896 911.713 399.896ZM912.001 401.672C910.929 401.672 910.097 402.104 909.505 402.968C908.913 403.816 908.617 405.032 908.617 406.616C908.617 408.2 908.905 409.416 909.481 410.264C910.057 411.096 910.913 411.512 912.049 411.512C913.345 411.512 914.289 411.168 914.881 410.48C915.473 409.776 915.769 408.648 915.769 407.096V406.592C915.769 404.848 915.465 403.592 914.857 402.824C914.249 402.056 913.297 401.672 912.001 401.672ZM926.887 399.896C927.975 399.896 928.919 400.136 929.719 400.616C930.519 401.096 931.127 401.776 931.543 402.656C931.975 403.52 932.191 404.536 932.191 405.704V406.976H923.383C923.415 408.432 923.783 409.544 924.487 410.312C925.207 411.064 926.207 411.44 927.487 411.44C928.303 411.44 929.023 411.368 929.647 411.224C930.287 411.064 930.943 410.84 931.615 410.552V412.4C930.959 412.688 930.311 412.896 929.671 413.024C929.031 413.168 928.271 413.24 927.391 413.24C926.159 413.24 925.079 412.992 924.151 412.496C923.223 412 922.495 411.264 921.967 410.288C921.455 409.312 921.199 408.104 921.199 406.664C921.199 405.256 921.431 404.048 921.895 403.04C922.375 402.032 923.039 401.256 923.887 400.712C924.751 400.168 925.751 399.896 926.887 399.896ZM926.863 401.624C925.855 401.624 925.055 401.952 924.463 402.608C923.887 403.248 923.543 404.144 923.431 405.296H929.983C929.967 404.208 929.711 403.328 929.215 402.656C928.719 401.968 927.935 401.624 926.863 401.624ZM940.482 395.864C942.61 395.864 944.178 396.272 945.186 397.088C946.21 397.888 946.722 399.104 946.722 400.736C946.722 401.648 946.554 402.408 946.218 403.016C945.882 403.624 945.45 404.112 944.922 404.48C944.41 404.848 943.866 405.128 943.29 405.32L947.994 413H945.474L941.322 405.92H937.914V413H935.754V395.864H940.482ZM940.362 397.736H937.914V404.096H940.482C941.874 404.096 942.89 403.824 943.53 403.28C944.17 402.72 944.49 401.904 944.49 400.832C944.49 399.712 944.154 398.92 943.482 398.456C942.81 397.976 941.77 397.736 940.362 397.736ZM961.579 406.544C961.579 408.672 961.035 410.32 959.947 411.488C958.875 412.656 957.419 413.24 955.579 413.24C954.443 413.24 953.427 412.984 952.531 412.472C951.651 411.944 950.955 411.184 950.443 410.192C949.931 409.184 949.675 407.968 949.675 406.544C949.675 404.416 950.203 402.776 951.259 401.624C952.331 400.472 953.795 399.896 955.651 399.896C956.803 399.896 957.827 400.16 958.723 400.688C959.619 401.2 960.315 401.952 960.811 402.944C961.323 403.92 961.579 405.12 961.579 406.544ZM951.859 406.544C951.859 408.064 952.155 409.272 952.747 410.168C953.355 411.048 954.315 411.488 955.627 411.488C956.923 411.488 957.875 411.048 958.483 410.168C959.091 409.272 959.395 408.064 959.395 406.544C959.395 405.024 959.091 403.832 958.483 402.968C957.875 402.104 956.915 401.672 955.603 401.672C954.291 401.672 953.339 402.104 952.747 402.968C952.155 403.832 951.859 405.024 951.859 406.544ZM975.679 400.136V413H973.951L973.639 411.296H973.543C973.127 411.968 972.551 412.464 971.815 412.784C971.079 413.088 970.295 413.24 969.463 413.24C967.911 413.24 966.743 412.872 965.959 412.136C965.175 411.384 964.783 410.192 964.783 408.56V400.136H966.919V408.416C966.919 410.464 967.871 411.488 969.775 411.488C971.199 411.488 972.183 411.088 972.727 410.288C973.287 409.488 973.567 408.336 973.567 406.832V400.136H975.679ZM984.059 411.512C984.379 411.512 984.707 411.488 985.043 411.44C985.379 411.392 985.651 411.328 985.859 411.248V412.856C985.635 412.968 985.315 413.056 984.899 413.12C984.483 413.2 984.083 413.24 983.699 413.24C983.027 413.24 982.403 413.128 981.827 412.904C981.267 412.664 980.811 412.256 980.459 411.68C980.107 411.104 979.931 410.296 979.931 409.256V401.768H978.107V400.76L979.955 399.92L980.795 397.184H982.043V400.136H985.763V401.768H982.043V409.208C982.043 409.992 982.227 410.576 982.595 410.96C982.979 411.328 983.467 411.512 984.059 411.512ZM993.403 399.896C994.491 399.896 995.435 400.136 996.235 400.616C997.035 401.096 997.643 401.776 998.059 402.656C998.491 403.52 998.707 404.536 998.707 405.704V406.976H989.899C989.931 408.432 990.299 409.544 991.003 410.312C991.723 411.064 992.723 411.44 994.003 411.44C994.819 411.44 995.539 411.368 996.163 411.224C996.803 411.064 997.459 410.84 998.131 410.552V412.4C997.475 412.688 996.827 412.896 996.187 413.024C995.547 413.168 994.787 413.24 993.907 413.24C992.675 413.24 991.595 412.992 990.667 412.496C989.739 412 989.011 411.264 988.483 410.288C987.971 409.312 987.715 408.104 987.715 406.664C987.715 405.256 987.947 404.048 988.411 403.04C988.891 402.032 989.555 401.256 990.403 400.712C991.267 400.168 992.267 399.896 993.403 399.896ZM993.379 401.624C992.371 401.624 991.571 401.952 990.979 402.608C990.403 403.248 990.059 404.144 989.947 405.296H996.499C996.483 404.208 996.227 403.328 995.731 402.656C995.235 401.968 994.451 401.624 993.379 401.624ZM1007.98 399.896C1008.22 399.896 1008.48 399.912 1008.75 399.944C1009.02 399.96 1009.27 399.992 1009.49 400.04L1009.23 401.984C1009.02 401.936 1008.79 401.896 1008.53 401.864C1008.28 401.832 1008.05 401.816 1007.84 401.816C1007.18 401.816 1006.57 402 1005.99 402.368C1005.41 402.72 1004.95 403.224 1004.6 403.88C1004.26 404.52 1004.09 405.272 1004.09 406.136V413H1001.98V400.136H1003.71L1003.95 402.488H1004.05C1004.45 401.784 1004.97 401.176 1005.63 400.664C1006.3 400.152 1007.09 399.896 1007.98 399.896ZM1010.15 411.704C1010.15 411.112 1010.3 410.696 1010.59 410.456C1010.87 410.216 1011.22 410.096 1011.62 410.096C1012.02 410.096 1012.37 410.216 1012.67 410.456C1012.98 410.696 1013.13 411.112 1013.13 411.704C1013.13 412.28 1012.98 412.696 1012.67 412.952C1012.37 413.208 1012.02 413.336 1011.62 413.336C1011.22 413.336 1010.87 413.208 1010.59 412.952C1010.3 412.696 1010.15 412.28 1010.15 411.704ZM1023.08 399.896C1024.62 399.896 1025.78 400.272 1026.56 401.024C1027.34 401.776 1027.74 402.976 1027.74 404.624V413H1025.65V404.768C1025.65 402.704 1024.69 401.672 1022.77 401.672C1021.34 401.672 1020.36 402.072 1019.82 402.872C1019.27 403.672 1019 404.824 1019 406.328V413H1016.89V400.136H1018.59L1018.9 401.888H1019.02C1019.44 401.216 1020.02 400.72 1020.75 400.4C1021.49 400.064 1022.26 399.896 1023.08 399.896ZM1036.6 399.92C1038.16 399.92 1039.32 400.264 1040.08 400.952C1040.83 401.64 1041.2 402.736 1041.2 404.24V413H1039.67L1039.26 411.176H1039.16C1038.6 411.88 1038.01 412.4 1037.39 412.736C1036.76 413.072 1035.92 413.24 1034.84 413.24C1033.68 413.24 1032.71 412.936 1031.94 412.328C1031.17 411.704 1030.79 410.736 1030.79 409.424C1030.79 408.144 1031.29 407.16 1032.3 406.472C1033.31 405.768 1034.86 405.384 1036.96 405.32L1039.14 405.248V404.48C1039.14 403.408 1038.91 402.664 1038.44 402.248C1037.98 401.832 1037.32 401.624 1036.48 401.624C1035.8 401.624 1035.16 401.728 1034.56 401.936C1033.95 402.128 1033.38 402.352 1032.85 402.608L1032.2 401.024C1032.76 400.72 1033.43 400.464 1034.2 400.256C1034.96 400.032 1035.76 399.92 1036.6 399.92ZM1039.12 406.712L1037.22 406.784C1035.62 406.848 1034.51 407.104 1033.88 407.552C1033.28 408 1032.97 408.632 1032.97 409.448C1032.97 410.168 1033.19 410.696 1033.62 411.032C1034.07 411.368 1034.64 411.536 1035.32 411.536C1036.4 411.536 1037.29 411.24 1038.01 410.648C1038.75 410.04 1039.12 409.112 1039.12 407.864V406.712ZM1048.01 413L1043.14 400.136H1045.39L1048.13 407.72C1048.26 408.072 1048.39 408.464 1048.54 408.896C1048.68 409.328 1048.81 409.744 1048.92 410.144C1049.03 410.544 1049.11 410.872 1049.16 411.128H1049.26C1049.3 410.872 1049.39 410.544 1049.52 410.144C1049.65 409.728 1049.78 409.312 1049.93 408.896C1050.07 408.464 1050.21 408.072 1050.34 407.72L1053.07 400.136H1055.33L1050.43 413H1048.01ZM1058.44 395.312C1058.76 395.312 1059.04 395.424 1059.28 395.648C1059.54 395.856 1059.67 396.192 1059.67 396.656C1059.67 397.12 1059.54 397.464 1059.28 397.688C1059.04 397.896 1058.76 398 1058.44 398C1058.09 398 1057.8 397.896 1057.56 397.688C1057.32 397.464 1057.2 397.12 1057.2 396.656C1057.2 396.192 1057.32 395.856 1057.56 395.648C1057.8 395.424 1058.09 395.312 1058.44 395.312ZM1059.48 400.136V413H1057.36V400.136H1059.48ZM1068.11 399.896C1068.96 399.896 1069.72 400.056 1070.39 400.376C1071.08 400.696 1071.66 401.184 1072.14 401.84H1072.26L1072.55 400.136H1074.23V413.216C1074.23 415.056 1073.76 416.44 1072.82 417.368C1071.89 418.296 1070.44 418.76 1068.47 418.76C1066.58 418.76 1065.04 418.488 1063.84 417.944V416C1065.1 416.672 1066.69 417.008 1068.59 417.008C1069.7 417.008 1070.56 416.68 1071.18 416.024C1071.82 415.384 1072.14 414.504 1072.14 413.384V412.88C1072.14 412.688 1072.15 412.416 1072.17 412.064C1072.18 411.696 1072.2 411.44 1072.22 411.296H1072.12C1071.26 412.592 1069.93 413.24 1068.14 413.24C1066.47 413.24 1065.17 412.656 1064.22 411.488C1063.3 410.32 1062.83 408.688 1062.83 406.592C1062.83 404.544 1063.3 402.92 1064.22 401.72C1065.17 400.504 1066.46 399.896 1068.11 399.896ZM1068.4 401.672C1067.33 401.672 1066.5 402.104 1065.9 402.968C1065.31 403.816 1065.02 405.032 1065.02 406.616C1065.02 408.2 1065.3 409.416 1065.88 410.264C1066.46 411.096 1067.31 411.512 1068.45 411.512C1069.74 411.512 1070.69 411.168 1071.28 410.48C1071.87 409.776 1072.17 408.648 1072.17 407.096V406.592C1072.17 404.848 1071.86 403.592 1071.26 402.824C1070.65 402.056 1069.7 401.672 1068.4 401.672ZM1083.19 399.92C1084.76 399.92 1085.92 400.264 1086.67 400.952C1087.42 401.64 1087.8 402.736 1087.8 404.24V413H1086.26L1085.85 411.176H1085.76C1085.2 411.88 1084.61 412.4 1083.98 412.736C1083.36 413.072 1082.51 413.24 1081.44 413.24C1080.27 413.24 1079.3 412.936 1078.53 412.328C1077.77 411.704 1077.38 410.736 1077.38 409.424C1077.38 408.144 1077.89 407.16 1078.89 406.472C1079.9 405.768 1081.45 405.384 1083.55 405.32L1085.73 405.248V404.48C1085.73 403.408 1085.5 402.664 1085.04 402.248C1084.57 401.832 1083.92 401.624 1083.07 401.624C1082.4 401.624 1081.76 401.728 1081.15 401.936C1080.54 402.128 1079.97 402.352 1079.45 402.608L1078.8 401.024C1079.36 400.72 1080.02 400.464 1080.79 400.256C1081.56 400.032 1082.36 399.92 1083.19 399.92ZM1085.71 406.712L1083.81 406.784C1082.21 406.848 1081.1 407.104 1080.48 407.552C1079.87 408 1079.57 408.632 1079.57 409.448C1079.57 410.168 1079.78 410.696 1080.21 411.032C1080.66 411.368 1081.23 411.536 1081.92 411.536C1082.99 411.536 1083.89 411.24 1084.61 410.648C1085.34 410.04 1085.71 409.112 1085.71 407.864V406.712ZM1096.07 411.512C1096.39 411.512 1096.71 411.488 1097.05 411.44C1097.39 411.392 1097.66 411.328 1097.87 411.248V412.856C1097.64 412.968 1097.32 413.056 1096.91 413.12C1096.49 413.2 1096.09 413.24 1095.71 413.24C1095.03 413.24 1094.41 413.128 1093.83 412.904C1093.27 412.664 1092.82 412.256 1092.47 411.68C1092.11 411.104 1091.94 410.296 1091.94 409.256V401.768H1090.11V400.76L1091.96 399.92L1092.8 397.184H1094.05V400.136H1097.77V401.768H1094.05V409.208C1094.05 409.992 1094.23 410.576 1094.6 410.96C1094.99 411.328 1095.47 411.512 1096.07 411.512ZM1105.41 399.896C1106.5 399.896 1107.44 400.136 1108.24 400.616C1109.04 401.096 1109.65 401.776 1110.07 402.656C1110.5 403.52 1110.71 404.536 1110.71 405.704V406.976H1101.91C1101.94 408.432 1102.31 409.544 1103.01 410.312C1103.73 411.064 1104.73 411.44 1106.01 411.44C1106.83 411.44 1107.55 411.368 1108.17 411.224C1108.81 411.064 1109.47 410.84 1110.14 410.552V412.4C1109.48 412.688 1108.83 412.896 1108.19 413.024C1107.55 413.168 1106.79 413.24 1105.91 413.24C1104.68 413.24 1103.6 412.992 1102.67 412.496C1101.75 412 1101.02 411.264 1100.49 410.288C1099.98 409.312 1099.72 408.104 1099.72 406.664C1099.72 405.256 1099.95 404.048 1100.42 403.04C1100.9 402.032 1101.56 401.256 1102.41 400.712C1103.27 400.168 1104.27 399.896 1105.41 399.896ZM1105.39 401.624C1104.38 401.624 1103.58 401.952 1102.99 402.608C1102.41 403.248 1102.07 404.144 1101.95 405.296H1108.51C1108.49 404.208 1108.23 403.328 1107.74 402.656C1107.24 401.968 1106.46 401.624 1105.39 401.624ZM1119.7 413H1117.54V397.76H1112.19V395.864H1125.03V397.76H1119.7V413ZM1136.82 406.544C1136.82 408.672 1136.28 410.32 1135.19 411.488C1134.12 412.656 1132.66 413.24 1130.82 413.24C1129.69 413.24 1128.67 412.984 1127.77 412.472C1126.89 411.944 1126.2 411.184 1125.69 410.192C1125.17 409.184 1124.92 407.968 1124.92 406.544C1124.92 404.416 1125.45 402.776 1126.5 401.624C1127.57 400.472 1129.04 399.896 1130.89 399.896C1132.05 399.896 1133.07 400.16 1133.97 400.688C1134.86 401.2 1135.56 401.952 1136.05 402.944C1136.57 403.92 1136.82 405.12 1136.82 406.544ZM1127.1 406.544C1127.1 408.064 1127.4 409.272 1127.99 410.168C1128.6 411.048 1129.56 411.488 1130.87 411.488C1132.17 411.488 1133.12 411.048 1133.73 410.168C1134.33 409.272 1134.64 408.064 1134.64 406.544C1134.64 405.024 1134.33 403.832 1133.73 402.968C1133.12 402.104 1132.16 401.672 1130.85 401.672C1129.53 401.672 1128.58 402.104 1127.99 402.968C1127.4 403.832 1127.1 405.024 1127.1 406.544ZM1145.18 395.864C1147.31 395.864 1148.88 396.272 1149.89 397.088C1150.91 397.888 1151.42 399.104 1151.42 400.736C1151.42 401.648 1151.26 402.408 1150.92 403.016C1150.58 403.624 1150.15 404.112 1149.62 404.48C1149.11 404.848 1148.57 405.128 1147.99 405.32L1152.7 413H1150.18L1146.02 405.92H1142.62V413H1140.46V395.864H1145.18ZM1145.06 397.736H1142.62V404.096H1145.18C1146.58 404.096 1147.59 403.824 1148.23 403.28C1148.87 402.72 1149.19 401.904 1149.19 400.832C1149.19 399.712 1148.86 398.92 1148.18 398.456C1147.51 397.976 1146.47 397.736 1145.06 397.736ZM1166.28 406.544C1166.28 408.672 1165.74 410.32 1164.65 411.488C1163.58 412.656 1162.12 413.24 1160.28 413.24C1159.15 413.24 1158.13 412.984 1157.23 412.472C1156.35 411.944 1155.66 411.184 1155.15 410.192C1154.63 409.184 1154.38 407.968 1154.38 406.544C1154.38 404.416 1154.91 402.776 1155.96 401.624C1157.03 400.472 1158.5 399.896 1160.35 399.896C1161.51 399.896 1162.53 400.16 1163.43 400.688C1164.32 401.2 1165.02 401.952 1165.51 402.944C1166.03 403.92 1166.28 405.12 1166.28 406.544ZM1156.56 406.544C1156.56 408.064 1156.86 409.272 1157.45 410.168C1158.06 411.048 1159.02 411.488 1160.33 411.488C1161.63 411.488 1162.58 411.048 1163.19 410.168C1163.79 409.272 1164.1 408.064 1164.1 406.544C1164.1 405.024 1163.79 403.832 1163.19 402.968C1162.58 402.104 1161.62 401.672 1160.31 401.672C1158.99 401.672 1158.04 402.104 1157.45 402.968C1156.86 403.832 1156.56 405.024 1156.56 406.544ZM1180.38 400.136V413H1178.65L1178.34 411.296H1178.25C1177.83 411.968 1177.25 412.464 1176.52 412.784C1175.78 413.088 1175 413.24 1174.17 413.24C1172.61 413.24 1171.45 412.872 1170.66 412.136C1169.88 411.384 1169.49 410.192 1169.49 408.56V400.136H1171.62V408.416C1171.62 410.464 1172.57 411.488 1174.48 411.488C1175.9 411.488 1176.89 411.088 1177.43 410.288C1177.99 409.488 1178.27 408.336 1178.27 406.832V400.136H1180.38ZM1188.76 411.512C1189.08 411.512 1189.41 411.488 1189.75 411.44C1190.08 411.392 1190.35 411.328 1190.56 411.248V412.856C1190.34 412.968 1190.02 413.056 1189.6 413.12C1189.19 413.2 1188.79 413.24 1188.4 413.24C1187.73 413.24 1187.11 413.128 1186.53 412.904C1185.97 412.664 1185.51 412.256 1185.16 411.68C1184.81 411.104 1184.63 410.296 1184.63 409.256V401.768H1182.81V400.76L1184.66 399.92L1185.5 397.184H1186.75V400.136H1190.47V401.768H1186.75V409.208C1186.75 409.992 1186.93 410.576 1187.3 410.96C1187.68 411.328 1188.17 411.512 1188.76 411.512ZM1198.11 399.896C1199.19 399.896 1200.14 400.136 1200.94 400.616C1201.74 401.096 1202.35 401.776 1202.76 402.656C1203.19 403.52 1203.41 404.536 1203.41 405.704V406.976H1194.6C1194.63 408.432 1195 409.544 1195.71 410.312C1196.43 411.064 1197.43 411.44 1198.71 411.44C1199.52 411.44 1200.24 411.368 1200.87 411.224C1201.51 411.064 1202.16 410.84 1202.83 410.552V412.4C1202.18 412.688 1201.53 412.896 1200.89 413.024C1200.25 413.168 1199.49 413.24 1198.61 413.24C1197.38 413.24 1196.3 412.992 1195.37 412.496C1194.44 412 1193.71 411.264 1193.19 410.288C1192.67 409.312 1192.42 408.104 1192.42 406.664C1192.42 405.256 1192.65 404.048 1193.11 403.04C1193.59 402.032 1194.26 401.256 1195.11 400.712C1195.97 400.168 1196.97 399.896 1198.11 399.896ZM1198.08 401.624C1197.07 401.624 1196.27 401.952 1195.68 402.608C1195.11 403.248 1194.76 404.144 1194.65 405.296H1201.2C1201.19 404.208 1200.93 403.328 1200.43 402.656C1199.94 401.968 1199.15 401.624 1198.08 401.624ZM1205.6 406.424C1205.6 404.472 1205.88 402.592 1206.44 400.784C1207.02 398.96 1207.92 397.32 1209.13 395.864H1211.12C1210 397.368 1209.16 399.024 1208.58 400.832C1208.02 402.64 1207.74 404.496 1207.74 406.4C1207.74 408.256 1208.02 410.08 1208.58 411.872C1209.16 413.648 1210 415.288 1211.1 416.792H1209.13C1207.92 415.384 1207.02 413.792 1206.44 412.016C1205.88 410.224 1205.6 408.36 1205.6 406.424ZM1219.14 416.792H1213.76V395.864H1219.14V397.592H1215.82V415.064H1219.14V416.792ZM1228.07 395.864C1227.84 396.792 1227.61 397.8 1227.37 398.888C1227.15 399.96 1226.98 400.912 1226.87 401.744H1224.59L1224.42 401.48C1224.63 400.648 1224.92 399.72 1225.31 398.696C1225.71 397.672 1226.11 396.728 1226.51 395.864H1228.07ZM1223.63 395.864C1223.4 396.792 1223.18 397.8 1222.95 398.888C1222.73 399.96 1222.55 400.912 1222.43 401.744H1220.17L1220.03 401.48C1220.25 400.648 1220.55 399.72 1220.91 398.696C1221.3 397.672 1221.69 396.728 1222.09 395.864H1223.63ZM1237.05 395.864L1230.67 413H1228.6L1234.99 395.864H1237.05ZM1241.44 394.76V400.112C1241.44 400.752 1241.4 401.352 1241.32 401.912H1241.47C1241.88 401.256 1242.44 400.76 1243.15 400.424C1243.87 400.088 1244.64 399.92 1245.48 399.92C1247.03 399.92 1248.2 400.296 1248.98 401.048C1249.78 401.784 1250.18 402.976 1250.18 404.624V413H1248.09V404.768C1248.09 402.704 1247.13 401.672 1245.21 401.672C1243.77 401.672 1242.78 402.08 1242.24 402.896C1241.71 403.696 1241.44 404.848 1241.44 406.352V413H1239.33V394.76H1241.44ZM1265.35 406.544C1265.35 408.672 1264.81 410.32 1263.72 411.488C1262.65 412.656 1261.19 413.24 1259.35 413.24C1258.22 413.24 1257.2 412.984 1256.3 412.472C1255.42 411.944 1254.73 411.184 1254.22 410.192C1253.7 409.184 1253.45 407.968 1253.45 406.544C1253.45 404.416 1253.98 402.776 1255.03 401.624C1256.1 400.472 1257.57 399.896 1259.42 399.896C1260.58 399.896 1261.6 400.16 1262.5 400.688C1263.39 401.2 1264.09 401.952 1264.58 402.944C1265.1 403.92 1265.35 405.12 1265.35 406.544ZM1255.63 406.544C1255.63 408.064 1255.93 409.272 1256.52 410.168C1257.13 411.048 1258.09 411.488 1259.4 411.488C1260.7 411.488 1261.65 411.048 1262.26 410.168C1262.86 409.272 1263.17 408.064 1263.17 406.544C1263.17 405.024 1262.86 403.832 1262.26 402.968C1261.65 402.104 1260.69 401.672 1259.38 401.672C1258.06 401.672 1257.11 402.104 1256.52 402.968C1255.93 403.832 1255.63 405.024 1255.63 406.544ZM1282.81 399.896C1284.27 399.896 1285.36 400.272 1286.08 401.024C1286.8 401.776 1287.16 402.976 1287.16 404.624V413H1285.07V404.72C1285.07 402.688 1284.2 401.672 1282.45 401.672C1281.2 401.672 1280.31 402.032 1279.76 402.752C1279.24 403.472 1278.97 404.52 1278.97 405.896V413H1276.88V404.72C1276.88 402.688 1276 401.672 1274.24 401.672C1272.95 401.672 1272.05 402.072 1271.56 402.872C1271.06 403.672 1270.81 404.824 1270.81 406.328V413H1268.7V400.136H1270.4L1270.72 401.888H1270.84C1271.24 401.216 1271.77 400.72 1272.44 400.4C1273.13 400.064 1273.86 399.896 1274.63 399.896C1276.64 399.896 1277.96 400.616 1278.56 402.056H1278.68C1279.12 401.32 1279.7 400.776 1280.44 400.424C1281.17 400.072 1281.96 399.896 1282.81 399.896ZM1296.1 399.896C1297.19 399.896 1298.13 400.136 1298.93 400.616C1299.73 401.096 1300.34 401.776 1300.75 402.656C1301.19 403.52 1301.4 404.536 1301.4 405.704V406.976H1292.59C1292.63 408.432 1292.99 409.544 1293.7 410.312C1294.42 411.064 1295.42 411.44 1296.7 411.44C1297.51 411.44 1298.23 411.368 1298.86 411.224C1299.5 411.064 1300.15 410.84 1300.83 410.552V412.4C1300.17 412.688 1299.52 412.896 1298.88 413.024C1298.24 413.168 1297.48 413.24 1296.6 413.24C1295.37 413.24 1294.29 412.992 1293.36 412.496C1292.43 412 1291.71 411.264 1291.18 410.288C1290.67 409.312 1290.41 408.104 1290.41 406.664C1290.41 405.256 1290.64 404.048 1291.11 403.04C1291.59 402.032 1292.25 401.256 1293.1 400.712C1293.96 400.168 1294.96 399.896 1296.1 399.896ZM1296.07 401.624C1295.07 401.624 1294.27 401.952 1293.67 402.608C1293.1 403.248 1292.75 404.144 1292.64 405.296H1299.19C1299.18 404.208 1298.92 403.328 1298.43 402.656C1297.93 401.968 1297.15 401.624 1296.07 401.624ZM1310.56 395.864L1310.73 396.128C1310.52 396.976 1310.22 397.912 1309.82 398.936C1309.43 399.96 1309.05 400.896 1308.67 401.744H1307.08C1307.23 401.152 1307.38 400.504 1307.54 399.8C1307.7 399.096 1307.84 398.408 1307.97 397.736C1308.11 397.048 1308.23 396.424 1308.31 395.864H1310.56ZM1306.12 395.864L1306.29 396.128C1306.07 396.976 1305.76 397.912 1305.38 398.936C1304.99 399.96 1304.61 400.896 1304.23 401.744H1302.69C1302.85 401.152 1303 400.504 1303.15 399.8C1303.29 399.096 1303.43 398.408 1303.55 397.736C1303.68 397.048 1303.79 396.424 1303.87 395.864H1306.12ZM1315.47 410.216L1315.64 410.48C1315.41 411.328 1315.11 412.256 1314.72 413.264C1314.34 414.288 1313.96 415.232 1313.57 416.096H1312.01C1312.24 415.184 1312.46 414.184 1312.68 413.096C1312.92 412.008 1313.1 411.048 1313.21 410.216H1315.47ZM1332.01 395.864C1331.79 396.792 1331.56 397.8 1331.32 398.888C1331.09 399.96 1330.92 400.912 1330.81 401.744H1328.53L1328.36 401.48C1328.57 400.648 1328.87 399.72 1329.25 398.696C1329.65 397.672 1330.05 396.728 1330.45 395.864H1332.01ZM1327.57 395.864C1327.35 396.792 1327.12 397.8 1326.9 398.888C1326.68 399.96 1326.5 400.912 1326.37 401.744H1324.12L1323.97 401.48C1324.2 400.648 1324.49 399.72 1324.86 398.696C1325.24 397.672 1325.64 396.728 1326.04 395.864H1327.57ZM1341 395.864L1334.61 413H1332.55L1338.93 395.864H1341ZM1344.36 395.312C1344.68 395.312 1344.96 395.424 1345.2 395.648C1345.45 395.856 1345.58 396.192 1345.58 396.656C1345.58 397.12 1345.45 397.464 1345.2 397.688C1344.96 397.896 1344.68 398 1344.36 398C1344.01 398 1343.71 397.896 1343.47 397.688C1343.23 397.464 1343.11 397.12 1343.11 396.656C1343.11 396.192 1343.23 395.856 1343.47 395.648C1343.71 395.424 1344.01 395.312 1344.36 395.312ZM1345.39 400.136V413H1343.28V400.136H1345.39ZM1355.66 399.896C1357.19 399.896 1358.35 400.272 1359.14 401.024C1359.92 401.776 1360.31 402.976 1360.31 404.624V413H1358.23V404.768C1358.23 402.704 1357.27 401.672 1355.35 401.672C1353.92 401.672 1352.94 402.072 1352.39 402.872C1351.85 403.672 1351.58 404.824 1351.58 406.328V413H1349.47V400.136H1351.17L1351.48 401.888H1351.6C1352.02 401.216 1352.59 400.72 1353.33 400.4C1354.07 400.064 1354.84 399.896 1355.66 399.896ZM1372.68 409.448C1372.68 410.696 1372.21 411.64 1371.29 412.28C1370.36 412.92 1369.11 413.24 1367.54 413.24C1366.65 413.24 1365.87 413.168 1365.21 413.024C1364.57 412.88 1364.01 412.68 1363.51 412.424V410.504C1364.02 410.76 1364.64 411 1365.36 411.224C1366.09 411.432 1366.84 411.536 1367.59 411.536C1368.66 411.536 1369.44 411.368 1369.92 411.032C1370.4 410.68 1370.64 410.216 1370.64 409.64C1370.64 409.32 1370.55 409.032 1370.37 408.776C1370.2 408.52 1369.88 408.264 1369.41 408.008C1368.97 407.752 1368.32 407.464 1367.47 407.144C1366.64 406.824 1365.93 406.504 1365.33 406.184C1364.74 405.864 1364.29 405.48 1363.97 405.032C1363.65 404.584 1363.49 404.008 1363.49 403.304C1363.49 402.216 1363.93 401.376 1364.81 400.784C1365.7 400.192 1366.87 399.896 1368.31 399.896C1369.09 399.896 1369.82 399.976 1370.49 400.136C1371.18 400.296 1371.82 400.504 1372.41 400.76L1371.69 402.44C1371.15 402.216 1370.58 402.024 1369.99 401.864C1369.4 401.704 1368.79 401.624 1368.17 401.624C1367.3 401.624 1366.64 401.768 1366.17 402.056C1365.73 402.328 1365.5 402.704 1365.5 403.184C1365.5 403.552 1365.61 403.856 1365.81 404.096C1366.02 404.336 1366.37 404.576 1366.85 404.816C1367.34 405.056 1368 405.328 1368.81 405.632C1369.63 405.936 1370.33 406.248 1370.9 406.568C1371.48 406.888 1371.92 407.28 1372.22 407.744C1372.53 408.192 1372.68 408.76 1372.68 409.448ZM1380.08 411.512C1380.4 411.512 1380.73 411.488 1381.07 411.44C1381.4 411.392 1381.67 411.328 1381.88 411.248V412.856C1381.66 412.968 1381.34 413.056 1380.92 413.12C1380.51 413.2 1380.11 413.24 1379.72 413.24C1379.05 413.24 1378.43 413.128 1377.85 412.904C1377.29 412.664 1376.83 412.256 1376.48 411.68C1376.13 411.104 1375.95 410.296 1375.95 409.256V401.768H1374.13V400.76L1375.98 399.92L1376.82 397.184H1378.07V400.136H1381.79V401.768H1378.07V409.208C1378.07 409.992 1378.25 410.576 1378.62 410.96C1379 411.328 1379.49 411.512 1380.08 411.512ZM1389.33 399.92C1390.9 399.92 1392.06 400.264 1392.81 400.952C1393.56 401.64 1393.94 402.736 1393.94 404.24V413H1392.4L1391.99 411.176H1391.9C1391.34 411.88 1390.75 412.4 1390.12 412.736C1389.5 413.072 1388.65 413.24 1387.58 413.24C1386.41 413.24 1385.44 412.936 1384.67 412.328C1383.91 411.704 1383.52 410.736 1383.52 409.424C1383.52 408.144 1384.03 407.16 1385.03 406.472C1386.04 405.768 1387.59 405.384 1389.69 405.32L1391.87 405.248V404.48C1391.87 403.408 1391.64 402.664 1391.18 402.248C1390.71 401.832 1390.06 401.624 1389.21 401.624C1388.54 401.624 1387.9 401.728 1387.29 401.936C1386.68 402.128 1386.11 402.352 1385.59 402.608L1384.94 401.024C1385.5 400.72 1386.16 400.464 1386.93 400.256C1387.7 400.032 1388.5 399.92 1389.33 399.92ZM1391.85 406.712L1389.95 406.784C1388.35 406.848 1387.24 407.104 1386.62 407.552C1386.01 408 1385.71 408.632 1385.71 409.448C1385.71 410.168 1385.92 410.696 1386.35 411.032C1386.8 411.368 1387.37 411.536 1388.06 411.536C1389.13 411.536 1390.03 411.24 1390.75 410.648C1391.48 410.04 1391.85 409.112 1391.85 407.864V406.712ZM1400.02 413H1397.91V394.76H1400.02V413ZM1406.21 413H1404.1V394.76H1406.21V413ZM1415.25 399.896C1416.34 399.896 1417.29 400.136 1418.09 400.616C1418.89 401.096 1419.49 401.776 1419.91 402.656C1420.34 403.52 1420.56 404.536 1420.56 405.704V406.976H1411.75C1411.78 408.432 1412.15 409.544 1412.85 410.312C1413.57 411.064 1414.57 411.44 1415.85 411.44C1416.67 411.44 1417.39 411.368 1418.01 411.224C1418.65 411.064 1419.31 410.84 1419.98 410.552V412.4C1419.33 412.688 1418.68 412.896 1418.04 413.024C1417.4 413.168 1416.64 413.24 1415.76 413.24C1414.53 413.24 1413.45 412.992 1412.52 412.496C1411.59 412 1410.86 411.264 1410.33 410.288C1409.82 409.312 1409.57 408.104 1409.57 406.664C1409.57 405.256 1409.8 404.048 1410.26 403.04C1410.74 402.032 1411.41 401.256 1412.25 400.712C1413.12 400.168 1414.12 399.896 1415.25 399.896ZM1415.23 401.624C1414.22 401.624 1413.42 401.952 1412.83 402.608C1412.25 403.248 1411.91 404.144 1411.8 405.296H1418.35C1418.33 404.208 1418.08 403.328 1417.58 402.656C1417.09 401.968 1416.3 401.624 1415.23 401.624ZM1428.39 413.24C1426.79 413.24 1425.51 412.688 1424.55 411.584C1423.59 410.464 1423.11 408.8 1423.11 406.592C1423.11 404.384 1423.59 402.72 1424.55 401.6C1425.53 400.464 1426.82 399.896 1428.42 399.896C1429.41 399.896 1430.22 400.08 1430.84 400.448C1431.48 400.816 1432 401.264 1432.4 401.792H1432.54C1432.51 401.584 1432.48 401.28 1432.45 400.88C1432.42 400.464 1432.4 400.136 1432.4 399.896V394.76H1434.51V413H1432.81L1432.5 411.272H1432.4C1432.02 411.816 1431.5 412.28 1430.86 412.664C1430.22 413.048 1429.4 413.24 1428.39 413.24ZM1428.73 411.488C1430.09 411.488 1431.04 411.12 1431.58 410.384C1432.14 409.632 1432.42 408.504 1432.42 407V406.616C1432.42 405.016 1432.16 403.792 1431.63 402.944C1431.1 402.08 1430.13 401.648 1428.7 401.648C1427.57 401.648 1426.71 402.104 1426.14 403.016C1425.58 403.912 1425.3 405.12 1425.3 406.64C1425.3 408.176 1425.58 409.368 1426.14 410.216C1426.71 411.064 1427.58 411.488 1428.73 411.488ZM1444.72 395.864L1444.89 396.128C1444.68 396.976 1444.37 397.912 1443.97 398.936C1443.59 399.96 1443.21 400.896 1442.82 401.744H1441.24C1441.38 401.152 1441.53 400.504 1441.69 399.8C1441.85 399.096 1442 398.408 1442.13 397.736C1442.27 397.048 1442.38 396.424 1442.46 395.864H1444.72ZM1440.28 395.864L1440.45 396.128C1440.22 396.976 1439.92 397.912 1439.53 398.936C1439.15 399.96 1438.77 400.896 1438.38 401.744H1436.85C1437.01 401.152 1437.16 400.504 1437.3 399.8C1437.45 399.096 1437.58 398.408 1437.71 397.736C1437.84 397.048 1437.94 396.424 1438.02 395.864H1440.28ZM1445.78 415.064H1449.1V397.592H1445.78V395.864H1451.16V416.792H1445.78V415.064ZM1459.32 406.424C1459.32 408.36 1459.03 410.224 1458.46 412.016C1457.9 413.792 1457.01 415.384 1455.79 416.792H1453.83C1454.93 415.288 1455.76 413.648 1456.32 411.872C1456.9 410.08 1457.19 408.256 1457.19 406.4C1457.19 404.496 1456.9 402.64 1456.32 400.832C1455.76 399.024 1454.92 397.368 1453.8 395.864H1455.79C1457.01 397.32 1457.9 398.96 1458.46 400.784C1459.03 402.592 1459.32 404.472 1459.32 406.424Z" fill="black"/>
+</svg>
diff --git a/docs/pics/PageRouterPop.svg b/docs/pics/PageRouterPop.svg
new file mode 100644 (file)
index 0000000..ee9bb4d
--- /dev/null
@@ -0,0 +1,14 @@
+<svg width="1224" height="726" viewBox="0 0 1224 726" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="383" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M159 497H329.023C331.968 497 334.674 498.618 336.069 501.212L380.693 584.212C381.964 586.577 381.964 589.423 380.693 591.788L336.069 674.788C334.674 677.382 331.968 679 329.023 679H159C154.582 679 151 675.418 151 671V505C151 500.582 154.582 497 159 497Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 562.864C222.956 562.864 224.588 563.304 225.612 564.184C226.636 565.064 227.148 566.304 227.148 567.904C227.148 568.848 226.932 569.736 226.5 570.568C226.084 571.384 225.372 572.048 224.364 572.56C223.372 573.072 222.02 573.328 220.308 573.328H218.34V580H216.18V562.864H220.716ZM220.524 564.712H218.34V571.48H220.068C221.7 571.48 222.916 571.216 223.716 570.688C224.516 570.16 224.916 569.264 224.916 568C224.916 566.896 224.556 566.072 223.836 565.528C223.132 564.984 222.028 564.712 220.524 564.712ZM235.295 566.92C236.863 566.92 238.023 567.264 238.775 567.952C239.527 568.64 239.903 569.736 239.903 571.24V580H238.367L237.959 578.176H237.863C237.303 578.88 236.711 579.4 236.087 579.736C235.463 580.072 234.615 580.24 233.543 580.24C232.375 580.24 231.407 579.936 230.639 579.328C229.871 578.704 229.487 577.736 229.487 576.424C229.487 575.144 229.991 574.16 230.999 573.472C232.007 572.768 233.559 572.384 235.655 572.32L237.839 572.248V571.48C237.839 570.408 237.607 569.664 237.143 569.248C236.679 568.832 236.023 568.624 235.175 568.624C234.503 568.624 233.863 568.728 233.255 568.936C232.647 569.128 232.079 569.352 231.551 569.608L230.903 568.024C231.463 567.72 232.127 567.464 232.895 567.256C233.663 567.032 234.463 566.92 235.295 566.92ZM237.815 573.712L235.919 573.784C234.319 573.848 233.207 574.104 232.583 574.552C231.975 575 231.671 575.632 231.671 576.448C231.671 577.168 231.887 577.696 232.319 578.032C232.767 578.368 233.335 578.536 234.023 578.536C235.095 578.536 235.991 578.24 236.711 577.648C237.447 577.04 237.815 576.112 237.815 574.864V573.712ZM248.436 566.896C249.284 566.896 250.044 567.056 250.716 567.376C251.404 567.696 251.988 568.184 252.468 568.84H252.588L252.876 567.136H254.556V580.216C254.556 582.056 254.084 583.44 253.14 584.368C252.212 585.296 250.764 585.76 248.796 585.76C246.908 585.76 245.364 585.488 244.164 584.944V583C245.428 583.672 247.012 584.008 248.916 584.008C250.02 584.008 250.884 583.68 251.508 583.024C252.148 582.384 252.468 581.504 252.468 580.384V579.88C252.468 579.688 252.476 579.416 252.492 579.064C252.508 578.696 252.524 578.44 252.54 578.296H252.444C251.58 579.592 250.252 580.24 248.46 580.24C246.796 580.24 245.492 579.656 244.548 578.488C243.62 577.32 243.156 575.688 243.156 573.592C243.156 571.544 243.62 569.92 244.548 568.72C245.492 567.504 246.788 566.896 248.436 566.896ZM248.724 568.672C247.652 568.672 246.82 569.104 246.228 569.968C245.636 570.816 245.34 572.032 245.34 573.616C245.34 575.2 245.628 576.416 246.204 577.264C246.78 578.096 247.636 578.512 248.772 578.512C250.068 578.512 251.012 578.168 251.604 577.48C252.196 576.776 252.492 575.648 252.492 574.096V573.592C252.492 571.848 252.188 570.592 251.58 569.824C250.972 569.056 250.02 568.672 248.724 568.672ZM263.61 566.896C264.698 566.896 265.642 567.136 266.442 567.616C267.242 568.096 267.85 568.776 268.266 569.656C268.698 570.52 268.914 571.536 268.914 572.704V573.976H260.106C260.138 575.432 260.506 576.544 261.21 577.312C261.93 578.064 262.93 578.44 264.21 578.44C265.026 578.44 265.746 578.368 266.37 578.224C267.01 578.064 267.666 577.84 268.338 577.552V579.4C267.682 579.688 267.034 579.896 266.394 580.024C265.754 580.168 264.994 580.24 264.114 580.24C262.882 580.24 261.802 579.992 260.874 579.496C259.946 579 259.218 578.264 258.69 577.288C258.178 576.312 257.922 575.104 257.922 573.664C257.922 572.256 258.154 571.048 258.618 570.04C259.098 569.032 259.762 568.256 260.61 567.712C261.474 567.168 262.474 566.896 263.61 566.896ZM263.586 568.624C262.578 568.624 261.778 568.952 261.186 569.608C260.61 570.248 260.266 571.144 260.154 572.296H266.706C266.69 571.208 266.434 570.328 265.938 569.656C265.442 568.968 264.658 568.624 263.586 568.624ZM204.683 595.864C204.459 596.792 204.227 597.8 203.987 598.888C203.763 599.96 203.595 600.912 203.483 601.744H201.203L201.035 601.48C201.243 600.648 201.539 599.72 201.923 598.696C202.323 597.672 202.723 596.728 203.123 595.864H204.683ZM200.243 595.864C200.019 596.792 199.795 597.8 199.571 598.888C199.347 599.96 199.171 600.912 199.043 601.744H196.787L196.643 601.48C196.867 600.648 197.163 599.72 197.531 598.696C197.915 597.672 198.307 596.728 198.707 595.864H200.243ZM213.668 595.864L207.284 613H205.22L211.604 595.864H213.668ZM218.062 594.76V600.112C218.062 600.752 218.022 601.352 217.942 601.912H218.086C218.502 601.256 219.062 600.76 219.766 600.424C220.486 600.088 221.262 599.92 222.094 599.92C223.646 599.92 224.814 600.296 225.598 601.048C226.398 601.784 226.798 602.976 226.798 604.624V613H224.71V604.768C224.71 602.704 223.75 601.672 221.83 601.672C220.39 601.672 219.398 602.08 218.854 602.896C218.326 603.696 218.062 604.848 218.062 606.352V613H215.95V594.76H218.062ZM241.97 606.544C241.97 608.672 241.426 610.32 240.338 611.488C239.266 612.656 237.81 613.24 235.97 613.24C234.834 613.24 233.818 612.984 232.922 612.472C232.042 611.944 231.346 611.184 230.834 610.192C230.322 609.184 230.066 607.968 230.066 606.544C230.066 604.416 230.594 602.776 231.65 601.624C232.722 600.472 234.186 599.896 236.042 599.896C237.194 599.896 238.218 600.16 239.114 600.688C240.01 601.2 240.706 601.952 241.202 602.944C241.714 603.92 241.97 605.12 241.97 606.544ZM232.25 606.544C232.25 608.064 232.546 609.272 233.138 610.168C233.746 611.048 234.706 611.488 236.018 611.488C237.314 611.488 238.266 611.048 238.874 610.168C239.482 609.272 239.786 608.064 239.786 606.544C239.786 605.024 239.482 603.832 238.874 602.968C238.266 602.104 237.306 601.672 235.994 601.672C234.682 601.672 233.73 602.104 233.138 602.968C232.546 603.832 232.25 605.024 232.25 606.544ZM259.429 599.896C260.885 599.896 261.973 600.272 262.693 601.024C263.413 601.776 263.773 602.976 263.773 604.624V613H261.685V604.72C261.685 602.688 260.813 601.672 259.069 601.672C257.821 601.672 256.925 602.032 256.381 602.752C255.853 603.472 255.589 604.52 255.589 605.896V613H253.501V604.72C253.501 602.688 252.621 601.672 250.861 601.672C249.565 601.672 248.669 602.072 248.173 602.872C247.677 603.672 247.429 604.824 247.429 606.328V613H245.317V600.136H247.021L247.333 601.888H247.453C247.853 601.216 248.389 600.72 249.061 600.4C249.749 600.064 250.477 599.896 251.245 599.896C253.261 599.896 254.573 600.616 255.181 602.056H255.301C255.733 601.32 256.317 600.776 257.053 600.424C257.789 600.072 258.581 599.896 259.429 599.896ZM272.715 599.896C273.803 599.896 274.747 600.136 275.547 600.616C276.347 601.096 276.955 601.776 277.371 602.656C277.803 603.52 278.019 604.536 278.019 605.704V606.976H269.211C269.243 608.432 269.611 609.544 270.315 610.312C271.035 611.064 272.035 611.44 273.315 611.44C274.131 611.44 274.851 611.368 275.475 611.224C276.115 611.064 276.771 610.84 277.443 610.552V612.4C276.787 612.688 276.139 612.896 275.499 613.024C274.859 613.168 274.099 613.24 273.219 613.24C271.987 613.24 270.907 612.992 269.979 612.496C269.051 612 268.323 611.264 267.795 610.288C267.283 609.312 267.027 608.104 267.027 606.664C267.027 605.256 267.259 604.048 267.723 603.04C268.203 602.032 268.867 601.256 269.715 600.712C270.579 600.168 271.579 599.896 272.715 599.896ZM272.691 601.624C271.683 601.624 270.883 601.952 270.291 602.608C269.715 603.248 269.371 604.144 269.259 605.296H275.811C275.795 604.208 275.539 603.328 275.043 602.656C274.547 601.968 273.763 601.624 272.691 601.624ZM287.18 595.864L287.348 596.128C287.14 596.976 286.836 597.912 286.436 598.936C286.052 599.96 285.668 600.896 285.284 601.744H283.7C283.844 601.152 283.996 600.504 284.156 599.8C284.316 599.096 284.46 598.408 284.588 597.736C284.732 597.048 284.844 596.424 284.924 595.864H287.18ZM282.74 595.864L282.908 596.128C282.684 596.976 282.38 597.912 281.996 598.936C281.612 599.96 281.228 600.896 280.844 601.744H279.308C279.468 601.152 279.62 600.504 279.764 599.8C279.908 599.096 280.044 598.408 280.172 597.736C280.3 597.048 280.404 596.424 280.484 595.864H282.74Z" fill="black"/>
+<path d="M306.501 418.864C308.741 418.864 310.373 419.304 311.397 420.184C312.421 421.064 312.933 422.304 312.933 423.904C312.933 424.848 312.717 425.736 312.285 426.568C311.869 427.384 311.157 428.048 310.149 428.56C309.157 429.072 307.805 429.328 306.093 429.328H304.125V436H301.965V418.864H306.501ZM306.309 420.712H304.125V427.48H305.853C307.485 427.48 308.701 427.216 309.501 426.688C310.301 426.16 310.701 425.264 310.701 424C310.701 422.896 310.341 422.072 309.621 421.528C308.917 420.984 307.813 420.712 306.309 420.712ZM321.08 422.92C322.648 422.92 323.808 423.264 324.56 423.952C325.312 424.64 325.688 425.736 325.688 427.24V436H324.152L323.744 434.176H323.648C323.088 434.88 322.496 435.4 321.872 435.736C321.248 436.072 320.4 436.24 319.328 436.24C318.16 436.24 317.192 435.936 316.424 435.328C315.656 434.704 315.272 433.736 315.272 432.424C315.272 431.144 315.776 430.16 316.784 429.472C317.792 428.768 319.344 428.384 321.44 428.32L323.624 428.248V427.48C323.624 426.408 323.392 425.664 322.928 425.248C322.464 424.832 321.808 424.624 320.96 424.624C320.288 424.624 319.648 424.728 319.04 424.936C318.432 425.128 317.864 425.352 317.336 425.608L316.688 424.024C317.248 423.72 317.912 423.464 318.68 423.256C319.448 423.032 320.248 422.92 321.08 422.92ZM323.6 429.712L321.704 429.784C320.104 429.848 318.992 430.104 318.368 430.552C317.76 431 317.456 431.632 317.456 432.448C317.456 433.168 317.672 433.696 318.104 434.032C318.552 434.368 319.12 434.536 319.808 434.536C320.88 434.536 321.776 434.24 322.496 433.648C323.232 433.04 323.6 432.112 323.6 430.864V429.712ZM334.221 422.896C335.069 422.896 335.829 423.056 336.501 423.376C337.189 423.696 337.773 424.184 338.253 424.84H338.373L338.661 423.136H340.341V436.216C340.341 438.056 339.869 439.44 338.925 440.368C337.997 441.296 336.549 441.76 334.581 441.76C332.693 441.76 331.149 441.488 329.949 440.944V439C331.213 439.672 332.797 440.008 334.701 440.008C335.805 440.008 336.669 439.68 337.293 439.024C337.933 438.384 338.253 437.504 338.253 436.384V435.88C338.253 435.688 338.261 435.416 338.277 435.064C338.293 434.696 338.309 434.44 338.325 434.296H338.229C337.365 435.592 336.037 436.24 334.245 436.24C332.581 436.24 331.277 435.656 330.333 434.488C329.405 433.32 328.941 431.688 328.941 429.592C328.941 427.544 329.405 425.92 330.333 424.72C331.277 423.504 332.573 422.896 334.221 422.896ZM334.509 424.672C333.437 424.672 332.605 425.104 332.013 425.968C331.421 426.816 331.125 428.032 331.125 429.616C331.125 431.2 331.413 432.416 331.989 433.264C332.565 434.096 333.421 434.512 334.557 434.512C335.853 434.512 336.797 434.168 337.389 433.48C337.981 432.776 338.277 431.648 338.277 430.096V429.592C338.277 427.848 337.973 426.592 337.365 425.824C336.757 425.056 335.805 424.672 334.509 424.672ZM349.395 422.896C350.483 422.896 351.427 423.136 352.227 423.616C353.027 424.096 353.635 424.776 354.051 425.656C354.483 426.52 354.699 427.536 354.699 428.704V429.976H345.891C345.923 431.432 346.291 432.544 346.995 433.312C347.715 434.064 348.715 434.44 349.995 434.44C350.811 434.44 351.531 434.368 352.155 434.224C352.795 434.064 353.451 433.84 354.123 433.552V435.4C353.467 435.688 352.819 435.896 352.179 436.024C351.539 436.168 350.779 436.24 349.899 436.24C348.667 436.24 347.587 435.992 346.659 435.496C345.731 435 345.003 434.264 344.475 433.288C343.963 432.312 343.707 431.104 343.707 429.664C343.707 428.256 343.939 427.048 344.403 426.04C344.883 425.032 345.547 424.256 346.395 423.712C347.259 423.168 348.259 422.896 349.395 422.896ZM349.371 424.624C348.363 424.624 347.563 424.952 346.971 425.608C346.395 426.248 346.051 427.144 345.939 428.296H352.491C352.475 427.208 352.219 426.328 351.723 425.656C351.227 424.968 350.443 424.624 349.371 424.624ZM362.99 418.864C365.118 418.864 366.686 419.272 367.694 420.088C368.718 420.888 369.23 422.104 369.23 423.736C369.23 424.648 369.062 425.408 368.726 426.016C368.39 426.624 367.958 427.112 367.43 427.48C366.918 427.848 366.374 428.128 365.798 428.32L370.502 436H367.982L363.83 428.92H360.422V436H358.262V418.864H362.99ZM362.87 420.736H360.422V427.096H362.99C364.382 427.096 365.398 426.824 366.038 426.28C366.678 425.72 366.998 424.904 366.998 423.832C366.998 422.712 366.662 421.92 365.99 421.456C365.318 420.976 364.278 420.736 362.87 420.736ZM384.087 429.544C384.087 431.672 383.543 433.32 382.455 434.488C381.383 435.656 379.927 436.24 378.087 436.24C376.951 436.24 375.935 435.984 375.039 435.472C374.159 434.944 373.463 434.184 372.951 433.192C372.439 432.184 372.183 430.968 372.183 429.544C372.183 427.416 372.711 425.776 373.767 424.624C374.839 423.472 376.303 422.896 378.159 422.896C379.311 422.896 380.335 423.16 381.231 423.688C382.127 424.2 382.823 424.952 383.319 425.944C383.831 426.92 384.087 428.12 384.087 429.544ZM374.367 429.544C374.367 431.064 374.663 432.272 375.255 433.168C375.863 434.048 376.823 434.488 378.135 434.488C379.431 434.488 380.383 434.048 380.991 433.168C381.599 432.272 381.903 431.064 381.903 429.544C381.903 428.024 381.599 426.832 380.991 425.968C380.383 425.104 379.423 424.672 378.111 424.672C376.799 424.672 375.847 425.104 375.255 425.968C374.663 426.832 374.367 428.024 374.367 429.544ZM398.187 423.136V436H396.459L396.147 434.296H396.051C395.635 434.968 395.059 435.464 394.323 435.784C393.587 436.088 392.803 436.24 391.971 436.24C390.419 436.24 389.251 435.872 388.467 435.136C387.683 434.384 387.291 433.192 387.291 431.56V423.136H389.427V431.416C389.427 433.464 390.379 434.488 392.283 434.488C393.707 434.488 394.691 434.088 395.235 433.288C395.795 432.488 396.075 431.336 396.075 429.832V423.136H398.187ZM406.566 434.512C406.886 434.512 407.214 434.488 407.55 434.44C407.886 434.392 408.158 434.328 408.366 434.248V435.856C408.142 435.968 407.822 436.056 407.406 436.12C406.99 436.2 406.59 436.24 406.206 436.24C405.534 436.24 404.91 436.128 404.334 435.904C403.774 435.664 403.318 435.256 402.966 434.68C402.614 434.104 402.438 433.296 402.438 432.256V424.768H400.614V423.76L402.462 422.92L403.302 420.184H404.55V423.136H408.27V424.768H404.55V432.208C404.55 432.992 404.734 433.576 405.102 433.96C405.486 434.328 405.974 434.512 406.566 434.512ZM415.91 422.896C416.998 422.896 417.942 423.136 418.742 423.616C419.542 424.096 420.15 424.776 420.566 425.656C420.998 426.52 421.214 427.536 421.214 428.704V429.976H412.406C412.438 431.432 412.806 432.544 413.51 433.312C414.23 434.064 415.23 434.44 416.51 434.44C417.326 434.44 418.046 434.368 418.67 434.224C419.31 434.064 419.966 433.84 420.638 433.552V435.4C419.982 435.688 419.334 435.896 418.694 436.024C418.054 436.168 417.294 436.24 416.414 436.24C415.182 436.24 414.102 435.992 413.174 435.496C412.246 435 411.518 434.264 410.99 433.288C410.478 432.312 410.222 431.104 410.222 429.664C410.222 428.256 410.454 427.048 410.918 426.04C411.398 425.032 412.062 424.256 412.91 423.712C413.774 423.168 414.774 422.896 415.91 422.896ZM415.886 424.624C414.878 424.624 414.078 424.952 413.486 425.608C412.91 426.248 412.566 427.144 412.454 428.296H419.006C418.99 427.208 418.734 426.328 418.238 425.656C417.742 424.968 416.958 424.624 415.886 424.624ZM430.489 422.896C430.729 422.896 430.985 422.912 431.257 422.944C431.529 422.96 431.777 422.992 432.001 423.04L431.737 424.984C431.529 424.936 431.297 424.896 431.041 424.864C430.785 424.832 430.553 424.816 430.345 424.816C429.689 424.816 429.073 425 428.497 425.368C427.921 425.72 427.457 426.224 427.105 426.88C426.769 427.52 426.601 428.272 426.601 429.136V436H424.489V423.136H426.217L426.457 425.488H426.553C426.953 424.784 427.481 424.176 428.137 423.664C428.809 423.152 429.593 422.896 430.489 422.896ZM328.683 451.864C328.459 452.792 328.227 453.8 327.987 454.888C327.763 455.96 327.595 456.912 327.483 457.744H325.203L325.035 457.48C325.243 456.648 325.539 455.72 325.923 454.696C326.323 453.672 326.723 452.728 327.123 451.864H328.683ZM324.243 451.864C324.019 452.792 323.795 453.8 323.571 454.888C323.347 455.96 323.171 456.912 323.043 457.744H320.787L320.643 457.48C320.867 456.648 321.163 455.72 321.531 454.696C321.915 453.672 322.307 452.728 322.707 451.864H324.243ZM337.668 451.864L331.284 469H329.22L335.604 451.864H337.668ZM342.062 450.76V456.112C342.062 456.752 342.022 457.352 341.942 457.912H342.086C342.502 457.256 343.062 456.76 343.766 456.424C344.486 456.088 345.262 455.92 346.094 455.92C347.646 455.92 348.814 456.296 349.598 457.048C350.398 457.784 350.798 458.976 350.798 460.624V469H348.71V460.768C348.71 458.704 347.75 457.672 345.83 457.672C344.39 457.672 343.398 458.08 342.854 458.896C342.326 459.696 342.062 460.848 342.062 462.352V469H339.95V450.76H342.062ZM365.97 462.544C365.97 464.672 365.426 466.32 364.338 467.488C363.266 468.656 361.81 469.24 359.97 469.24C358.834 469.24 357.818 468.984 356.922 468.472C356.042 467.944 355.346 467.184 354.834 466.192C354.322 465.184 354.066 463.968 354.066 462.544C354.066 460.416 354.594 458.776 355.65 457.624C356.722 456.472 358.186 455.896 360.042 455.896C361.194 455.896 362.218 456.16 363.114 456.688C364.01 457.2 364.706 457.952 365.202 458.944C365.714 459.92 365.97 461.12 365.97 462.544ZM356.25 462.544C356.25 464.064 356.546 465.272 357.138 466.168C357.746 467.048 358.706 467.488 360.018 467.488C361.314 467.488 362.266 467.048 362.874 466.168C363.482 465.272 363.786 464.064 363.786 462.544C363.786 461.024 363.482 459.832 362.874 458.968C362.266 458.104 361.306 457.672 359.994 457.672C358.682 457.672 357.73 458.104 357.138 458.968C356.546 459.832 356.25 461.024 356.25 462.544ZM383.429 455.896C384.885 455.896 385.973 456.272 386.693 457.024C387.413 457.776 387.773 458.976 387.773 460.624V469H385.685V460.72C385.685 458.688 384.813 457.672 383.069 457.672C381.821 457.672 380.925 458.032 380.381 458.752C379.853 459.472 379.589 460.52 379.589 461.896V469H377.501V460.72C377.501 458.688 376.621 457.672 374.861 457.672C373.565 457.672 372.669 458.072 372.173 458.872C371.677 459.672 371.429 460.824 371.429 462.328V469H369.317V456.136H371.021L371.333 457.888H371.453C371.853 457.216 372.389 456.72 373.061 456.4C373.749 456.064 374.477 455.896 375.245 455.896C377.261 455.896 378.573 456.616 379.181 458.056H379.301C379.733 457.32 380.317 456.776 381.053 456.424C381.789 456.072 382.581 455.896 383.429 455.896ZM396.715 455.896C397.803 455.896 398.747 456.136 399.547 456.616C400.347 457.096 400.955 457.776 401.371 458.656C401.803 459.52 402.019 460.536 402.019 461.704V462.976H393.211C393.243 464.432 393.611 465.544 394.315 466.312C395.035 467.064 396.035 467.44 397.315 467.44C398.131 467.44 398.851 467.368 399.475 467.224C400.115 467.064 400.771 466.84 401.443 466.552V468.4C400.787 468.688 400.139 468.896 399.499 469.024C398.859 469.168 398.099 469.24 397.219 469.24C395.987 469.24 394.907 468.992 393.979 468.496C393.051 468 392.323 467.264 391.795 466.288C391.283 465.312 391.027 464.104 391.027 462.664C391.027 461.256 391.259 460.048 391.723 459.04C392.203 458.032 392.867 457.256 393.715 456.712C394.579 456.168 395.579 455.896 396.715 455.896ZM396.691 457.624C395.683 457.624 394.883 457.952 394.291 458.608C393.715 459.248 393.371 460.144 393.259 461.296H399.811C399.795 460.208 399.539 459.328 399.043 458.656C398.547 457.968 397.763 457.624 396.691 457.624ZM411.18 451.864L411.348 452.128C411.14 452.976 410.836 453.912 410.436 454.936C410.052 455.96 409.668 456.896 409.284 457.744H407.7C407.844 457.152 407.996 456.504 408.156 455.8C408.316 455.096 408.46 454.408 408.588 453.736C408.732 453.048 408.844 452.424 408.924 451.864H411.18ZM406.74 451.864L406.908 452.128C406.684 452.976 406.38 453.912 405.996 454.936C405.612 455.96 405.228 456.896 404.844 457.744H403.308C403.468 457.152 403.62 456.504 403.764 455.8C403.908 455.096 404.044 454.408 404.172 453.736C404.3 453.048 404.404 452.424 404.484 451.864H406.74Z" fill="black"/>
+<rect x="2" y="2" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M297 116H527.023C529.968 116 532.674 117.618 534.069 120.212L578.693 203.212C579.964 205.577 579.964 208.423 578.693 210.788L534.069 293.788C532.674 296.382 529.968 298 527.023 298H297C292.582 298 289 294.418 289 290V124C289 119.582 292.582 116 297 116Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M435.716 181.864C437.956 181.864 439.588 182.304 440.612 183.184C441.636 184.064 442.148 185.304 442.148 186.904C442.148 187.848 441.932 188.736 441.5 189.568C441.084 190.384 440.372 191.048 439.364 191.56C438.372 192.072 437.02 192.328 435.308 192.328H433.34V199H431.18V181.864H435.716ZM435.524 183.712H433.34V190.48H435.068C436.7 190.48 437.916 190.216 438.716 189.688C439.516 189.16 439.916 188.264 439.916 187C439.916 185.896 439.556 185.072 438.836 184.528C438.132 183.984 437.028 183.712 435.524 183.712ZM450.295 185.92C451.863 185.92 453.023 186.264 453.775 186.952C454.527 187.64 454.903 188.736 454.903 190.24V199H453.367L452.959 197.176H452.863C452.303 197.88 451.711 198.4 451.087 198.736C450.463 199.072 449.615 199.24 448.543 199.24C447.375 199.24 446.407 198.936 445.639 198.328C444.871 197.704 444.487 196.736 444.487 195.424C444.487 194.144 444.991 193.16 445.999 192.472C447.007 191.768 448.559 191.384 450.655 191.32L452.839 191.248V190.48C452.839 189.408 452.607 188.664 452.143 188.248C451.679 187.832 451.023 187.624 450.175 187.624C449.503 187.624 448.863 187.728 448.255 187.936C447.647 188.128 447.079 188.352 446.551 188.608L445.903 187.024C446.463 186.72 447.127 186.464 447.895 186.256C448.663 186.032 449.463 185.92 450.295 185.92ZM452.815 192.712L450.919 192.784C449.319 192.848 448.207 193.104 447.583 193.552C446.975 194 446.671 194.632 446.671 195.448C446.671 196.168 446.887 196.696 447.319 197.032C447.767 197.368 448.335 197.536 449.023 197.536C450.095 197.536 450.991 197.24 451.711 196.648C452.447 196.04 452.815 195.112 452.815 193.864V192.712ZM463.436 185.896C464.284 185.896 465.044 186.056 465.716 186.376C466.404 186.696 466.988 187.184 467.468 187.84H467.588L467.876 186.136H469.556V199.216C469.556 201.056 469.084 202.44 468.14 203.368C467.212 204.296 465.764 204.76 463.796 204.76C461.908 204.76 460.364 204.488 459.164 203.944V202C460.428 202.672 462.012 203.008 463.916 203.008C465.02 203.008 465.884 202.68 466.508 202.024C467.148 201.384 467.468 200.504 467.468 199.384V198.88C467.468 198.688 467.476 198.416 467.492 198.064C467.508 197.696 467.524 197.44 467.54 197.296H467.444C466.58 198.592 465.252 199.24 463.46 199.24C461.796 199.24 460.492 198.656 459.548 197.488C458.62 196.32 458.156 194.688 458.156 192.592C458.156 190.544 458.62 188.92 459.548 187.72C460.492 186.504 461.788 185.896 463.436 185.896ZM463.724 187.672C462.652 187.672 461.82 188.104 461.228 188.968C460.636 189.816 460.34 191.032 460.34 192.616C460.34 194.2 460.628 195.416 461.204 196.264C461.78 197.096 462.636 197.512 463.772 197.512C465.068 197.512 466.012 197.168 466.604 196.48C467.196 195.776 467.492 194.648 467.492 193.096V192.592C467.492 190.848 467.188 189.592 466.58 188.824C465.972 188.056 465.02 187.672 463.724 187.672ZM478.61 185.896C479.698 185.896 480.642 186.136 481.442 186.616C482.242 187.096 482.85 187.776 483.266 188.656C483.698 189.52 483.914 190.536 483.914 191.704V192.976H475.106C475.138 194.432 475.506 195.544 476.21 196.312C476.93 197.064 477.93 197.44 479.21 197.44C480.026 197.44 480.746 197.368 481.37 197.224C482.01 197.064 482.666 196.84 483.338 196.552V198.4C482.682 198.688 482.034 198.896 481.394 199.024C480.754 199.168 479.994 199.24 479.114 199.24C477.882 199.24 476.802 198.992 475.874 198.496C474.946 198 474.218 197.264 473.69 196.288C473.178 195.312 472.922 194.104 472.922 192.664C472.922 191.256 473.154 190.048 473.618 189.04C474.098 188.032 474.762 187.256 475.61 186.712C476.474 186.168 477.474 185.896 478.61 185.896ZM478.586 187.624C477.578 187.624 476.778 187.952 476.186 188.608C475.61 189.248 475.266 190.144 475.154 191.296H481.706C481.69 190.208 481.434 189.328 480.938 188.656C480.442 187.968 479.658 187.624 478.586 187.624ZM430.863 214.864C430.639 215.792 430.407 216.8 430.167 217.888C429.943 218.96 429.775 219.912 429.663 220.744H427.383L427.215 220.48C427.423 219.648 427.719 218.72 428.103 217.696C428.503 216.672 428.903 215.728 429.303 214.864H430.863ZM426.423 214.864C426.199 215.792 425.975 216.8 425.751 217.888C425.527 218.96 425.351 219.912 425.223 220.744H422.967L422.823 220.48C423.047 219.648 423.343 218.72 423.711 217.696C424.095 216.672 424.487 215.728 424.887 214.864H426.423ZM439.848 214.864L433.464 232H431.4L437.784 214.864H439.848ZM447.002 218.92C448.57 218.92 449.73 219.264 450.482 219.952C451.234 220.64 451.61 221.736 451.61 223.24V232H450.074L449.666 230.176H449.57C449.01 230.88 448.418 231.4 447.794 231.736C447.17 232.072 446.322 232.24 445.25 232.24C444.082 232.24 443.114 231.936 442.346 231.328C441.578 230.704 441.194 229.736 441.194 228.424C441.194 227.144 441.698 226.16 442.706 225.472C443.714 224.768 445.266 224.384 447.362 224.32L449.546 224.248V223.48C449.546 222.408 449.314 221.664 448.85 221.248C448.386 220.832 447.73 220.624 446.882 220.624C446.21 220.624 445.57 220.728 444.962 220.936C444.354 221.128 443.786 221.352 443.258 221.608L442.61 220.024C443.17 219.72 443.834 219.464 444.602 219.256C445.37 219.032 446.17 218.92 447.002 218.92ZM449.522 225.712L447.626 225.784C446.026 225.848 444.914 226.104 444.29 226.552C443.682 227 443.378 227.632 443.378 228.448C443.378 229.168 443.594 229.696 444.026 230.032C444.474 230.368 445.042 230.536 445.73 230.536C446.802 230.536 447.698 230.24 448.418 229.648C449.154 229.04 449.522 228.112 449.522 226.864V225.712ZM461.703 218.896C463.287 218.896 464.559 219.448 465.519 220.552C466.495 221.656 466.983 223.32 466.983 225.544C466.983 227.736 466.495 229.4 465.519 230.536C464.559 231.672 463.279 232.24 461.679 232.24C460.687 232.24 459.863 232.056 459.207 231.688C458.567 231.32 458.063 230.88 457.695 230.368H457.551C457.567 230.64 457.591 230.984 457.623 231.4C457.671 231.816 457.695 232.176 457.695 232.48V237.76H455.583V219.136H457.311L457.599 220.888H457.695C458.079 220.328 458.583 219.856 459.207 219.472C459.831 219.088 460.663 218.896 461.703 218.896ZM461.319 220.672C460.007 220.672 459.079 221.04 458.535 221.776C457.991 222.512 457.711 223.632 457.695 225.136V225.544C457.695 227.128 457.951 228.352 458.463 229.216C458.991 230.064 459.959 230.488 461.367 230.488C462.135 230.488 462.775 230.28 463.287 229.864C463.799 229.432 464.175 228.84 464.415 228.088C464.671 227.336 464.799 226.48 464.799 225.52C464.799 224.048 464.511 222.872 463.935 221.992C463.375 221.112 462.503 220.672 461.319 220.672ZM476.469 218.896C478.053 218.896 479.325 219.448 480.285 220.552C481.261 221.656 481.749 223.32 481.749 225.544C481.749 227.736 481.261 229.4 480.285 230.536C479.325 231.672 478.045 232.24 476.445 232.24C475.453 232.24 474.629 232.056 473.973 231.688C473.333 231.32 472.829 230.88 472.461 230.368H472.317C472.333 230.64 472.357 230.984 472.389 231.4C472.437 231.816 472.461 232.176 472.461 232.48V237.76H470.349V219.136H472.077L472.365 220.888H472.461C472.845 220.328 473.349 219.856 473.973 219.472C474.597 219.088 475.429 218.896 476.469 218.896ZM476.085 220.672C474.773 220.672 473.845 221.04 473.301 221.776C472.757 222.512 472.477 223.632 472.461 225.136V225.544C472.461 227.128 472.717 228.352 473.229 229.216C473.757 230.064 474.725 230.488 476.133 230.488C476.901 230.488 477.541 230.28 478.053 229.864C478.565 229.432 478.941 228.84 479.181 228.088C479.437 227.336 479.565 226.48 479.565 225.52C479.565 224.048 479.277 222.872 478.701 221.992C478.141 221.112 477.269 220.672 476.085 220.672ZM491 214.864L491.168 215.128C490.96 215.976 490.656 216.912 490.256 217.936C489.872 218.96 489.488 219.896 489.104 220.744H487.52C487.664 220.152 487.816 219.504 487.976 218.8C488.136 218.096 488.28 217.408 488.408 216.736C488.552 216.048 488.664 215.424 488.744 214.864H491ZM486.56 214.864L486.728 215.128C486.504 215.976 486.2 216.912 485.816 217.936C485.432 218.96 485.048 219.896 484.664 220.744H483.128C483.288 220.152 483.44 219.504 483.584 218.8C483.728 218.096 483.864 217.408 483.992 216.736C484.12 216.048 484.224 215.424 484.304 214.864H486.56Z" fill="black"/>
+<path d="M159 116H329.023C331.968 116 334.674 117.618 336.069 120.212L380.693 203.212C381.964 205.577 381.964 208.423 380.693 210.788L336.069 293.788C334.674 296.382 331.968 298 329.023 298H159C154.582 298 151 294.418 151 290V124C151 119.582 154.582 116 159 116Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 181.864C222.956 181.864 224.588 182.304 225.612 183.184C226.636 184.064 227.148 185.304 227.148 186.904C227.148 187.848 226.932 188.736 226.5 189.568C226.084 190.384 225.372 191.048 224.364 191.56C223.372 192.072 222.02 192.328 220.308 192.328H218.34V199H216.18V181.864H220.716ZM220.524 183.712H218.34V190.48H220.068C221.7 190.48 222.916 190.216 223.716 189.688C224.516 189.16 224.916 188.264 224.916 187C224.916 185.896 224.556 185.072 223.836 184.528C223.132 183.984 222.028 183.712 220.524 183.712ZM235.295 185.92C236.863 185.92 238.023 186.264 238.775 186.952C239.527 187.64 239.903 188.736 239.903 190.24V199H238.367L237.959 197.176H237.863C237.303 197.88 236.711 198.4 236.087 198.736C235.463 199.072 234.615 199.24 233.543 199.24C232.375 199.24 231.407 198.936 230.639 198.328C229.871 197.704 229.487 196.736 229.487 195.424C229.487 194.144 229.991 193.16 230.999 192.472C232.007 191.768 233.559 191.384 235.655 191.32L237.839 191.248V190.48C237.839 189.408 237.607 188.664 237.143 188.248C236.679 187.832 236.023 187.624 235.175 187.624C234.503 187.624 233.863 187.728 233.255 187.936C232.647 188.128 232.079 188.352 231.551 188.608L230.903 187.024C231.463 186.72 232.127 186.464 232.895 186.256C233.663 186.032 234.463 185.92 235.295 185.92ZM237.815 192.712L235.919 192.784C234.319 192.848 233.207 193.104 232.583 193.552C231.975 194 231.671 194.632 231.671 195.448C231.671 196.168 231.887 196.696 232.319 197.032C232.767 197.368 233.335 197.536 234.023 197.536C235.095 197.536 235.991 197.24 236.711 196.648C237.447 196.04 237.815 195.112 237.815 193.864V192.712ZM248.436 185.896C249.284 185.896 250.044 186.056 250.716 186.376C251.404 186.696 251.988 187.184 252.468 187.84H252.588L252.876 186.136H254.556V199.216C254.556 201.056 254.084 202.44 253.14 203.368C252.212 204.296 250.764 204.76 248.796 204.76C246.908 204.76 245.364 204.488 244.164 203.944V202C245.428 202.672 247.012 203.008 248.916 203.008C250.02 203.008 250.884 202.68 251.508 202.024C252.148 201.384 252.468 200.504 252.468 199.384V198.88C252.468 198.688 252.476 198.416 252.492 198.064C252.508 197.696 252.524 197.44 252.54 197.296H252.444C251.58 198.592 250.252 199.24 248.46 199.24C246.796 199.24 245.492 198.656 244.548 197.488C243.62 196.32 243.156 194.688 243.156 192.592C243.156 190.544 243.62 188.92 244.548 187.72C245.492 186.504 246.788 185.896 248.436 185.896ZM248.724 187.672C247.652 187.672 246.82 188.104 246.228 188.968C245.636 189.816 245.34 191.032 245.34 192.616C245.34 194.2 245.628 195.416 246.204 196.264C246.78 197.096 247.636 197.512 248.772 197.512C250.068 197.512 251.012 197.168 251.604 196.48C252.196 195.776 252.492 194.648 252.492 193.096V192.592C252.492 190.848 252.188 189.592 251.58 188.824C250.972 188.056 250.02 187.672 248.724 187.672ZM263.61 185.896C264.698 185.896 265.642 186.136 266.442 186.616C267.242 187.096 267.85 187.776 268.266 188.656C268.698 189.52 268.914 190.536 268.914 191.704V192.976H260.106C260.138 194.432 260.506 195.544 261.21 196.312C261.93 197.064 262.93 197.44 264.21 197.44C265.026 197.44 265.746 197.368 266.37 197.224C267.01 197.064 267.666 196.84 268.338 196.552V198.4C267.682 198.688 267.034 198.896 266.394 199.024C265.754 199.168 264.994 199.24 264.114 199.24C262.882 199.24 261.802 198.992 260.874 198.496C259.946 198 259.218 197.264 258.69 196.288C258.178 195.312 257.922 194.104 257.922 192.664C257.922 191.256 258.154 190.048 258.618 189.04C259.098 188.032 259.762 187.256 260.61 186.712C261.474 186.168 262.474 185.896 263.61 185.896ZM263.586 187.624C262.578 187.624 261.778 187.952 261.186 188.608C260.61 189.248 260.266 190.144 260.154 191.296H266.706C266.69 190.208 266.434 189.328 265.938 188.656C265.442 187.968 264.658 187.624 263.586 187.624ZM204.683 214.864C204.459 215.792 204.227 216.8 203.987 217.888C203.763 218.96 203.595 219.912 203.483 220.744H201.203L201.035 220.48C201.243 219.648 201.539 218.72 201.923 217.696C202.323 216.672 202.723 215.728 203.123 214.864H204.683ZM200.243 214.864C200.019 215.792 199.795 216.8 199.571 217.888C199.347 218.96 199.171 219.912 199.043 220.744H196.787L196.643 220.48C196.867 219.648 197.163 218.72 197.531 217.696C197.915 216.672 198.307 215.728 198.707 214.864H200.243ZM213.668 214.864L207.284 232H205.22L211.604 214.864H213.668ZM218.062 213.76V219.112C218.062 219.752 218.022 220.352 217.942 220.912H218.086C218.502 220.256 219.062 219.76 219.766 219.424C220.486 219.088 221.262 218.92 222.094 218.92C223.646 218.92 224.814 219.296 225.598 220.048C226.398 220.784 226.798 221.976 226.798 223.624V232H224.71V223.768C224.71 221.704 223.75 220.672 221.83 220.672C220.39 220.672 219.398 221.08 218.854 221.896C218.326 222.696 218.062 223.848 218.062 225.352V232H215.95V213.76H218.062ZM241.97 225.544C241.97 227.672 241.426 229.32 240.338 230.488C239.266 231.656 237.81 232.24 235.97 232.24C234.834 232.24 233.818 231.984 232.922 231.472C232.042 230.944 231.346 230.184 230.834 229.192C230.322 228.184 230.066 226.968 230.066 225.544C230.066 223.416 230.594 221.776 231.65 220.624C232.722 219.472 234.186 218.896 236.042 218.896C237.194 218.896 238.218 219.16 239.114 219.688C240.01 220.2 240.706 220.952 241.202 221.944C241.714 222.92 241.97 224.12 241.97 225.544ZM232.25 225.544C232.25 227.064 232.546 228.272 233.138 229.168C233.746 230.048 234.706 230.488 236.018 230.488C237.314 230.488 238.266 230.048 238.874 229.168C239.482 228.272 239.786 227.064 239.786 225.544C239.786 224.024 239.482 222.832 238.874 221.968C238.266 221.104 237.306 220.672 235.994 220.672C234.682 220.672 233.73 221.104 233.138 221.968C232.546 222.832 232.25 224.024 232.25 225.544ZM259.429 218.896C260.885 218.896 261.973 219.272 262.693 220.024C263.413 220.776 263.773 221.976 263.773 223.624V232H261.685V223.72C261.685 221.688 260.813 220.672 259.069 220.672C257.821 220.672 256.925 221.032 256.381 221.752C255.853 222.472 255.589 223.52 255.589 224.896V232H253.501V223.72C253.501 221.688 252.621 220.672 250.861 220.672C249.565 220.672 248.669 221.072 248.173 221.872C247.677 222.672 247.429 223.824 247.429 225.328V232H245.317V219.136H247.021L247.333 220.888H247.453C247.853 220.216 248.389 219.72 249.061 219.4C249.749 219.064 250.477 218.896 251.245 218.896C253.261 218.896 254.573 219.616 255.181 221.056H255.301C255.733 220.32 256.317 219.776 257.053 219.424C257.789 219.072 258.581 218.896 259.429 218.896ZM272.715 218.896C273.803 218.896 274.747 219.136 275.547 219.616C276.347 220.096 276.955 220.776 277.371 221.656C277.803 222.52 278.019 223.536 278.019 224.704V225.976H269.211C269.243 227.432 269.611 228.544 270.315 229.312C271.035 230.064 272.035 230.44 273.315 230.44C274.131 230.44 274.851 230.368 275.475 230.224C276.115 230.064 276.771 229.84 277.443 229.552V231.4C276.787 231.688 276.139 231.896 275.499 232.024C274.859 232.168 274.099 232.24 273.219 232.24C271.987 232.24 270.907 231.992 269.979 231.496C269.051 231 268.323 230.264 267.795 229.288C267.283 228.312 267.027 227.104 267.027 225.664C267.027 224.256 267.259 223.048 267.723 222.04C268.203 221.032 268.867 220.256 269.715 219.712C270.579 219.168 271.579 218.896 272.715 218.896ZM272.691 220.624C271.683 220.624 270.883 220.952 270.291 221.608C269.715 222.248 269.371 223.144 269.259 224.296H275.811C275.795 223.208 275.539 222.328 275.043 221.656C274.547 220.968 273.763 220.624 272.691 220.624ZM287.18 214.864L287.348 215.128C287.14 215.976 286.836 216.912 286.436 217.936C286.052 218.96 285.668 219.896 285.284 220.744H283.7C283.844 220.152 283.996 219.504 284.156 218.8C284.316 218.096 284.46 217.408 284.588 216.736C284.732 216.048 284.844 215.424 284.924 214.864H287.18ZM282.74 214.864L282.908 215.128C282.684 215.976 282.38 216.912 281.996 217.936C281.612 218.96 281.228 219.896 280.844 220.744H279.308C279.468 220.152 279.62 219.504 279.764 218.8C279.908 218.096 280.044 217.408 280.172 216.736C280.3 216.048 280.404 215.424 280.484 214.864H282.74Z" fill="black"/>
+<path d="M306.501 37.864C308.741 37.864 310.373 38.304 311.397 39.184C312.421 40.064 312.933 41.304 312.933 42.904C312.933 43.848 312.717 44.736 312.285 45.568C311.869 46.384 311.157 47.048 310.149 47.56C309.157 48.072 307.805 48.328 306.093 48.328H304.125V55H301.965V37.864H306.501ZM306.309 39.712H304.125V46.48H305.853C307.485 46.48 308.701 46.216 309.501 45.688C310.301 45.16 310.701 44.264 310.701 43C310.701 41.896 310.341 41.072 309.621 40.528C308.917 39.984 307.813 39.712 306.309 39.712ZM321.08 41.92C322.648 41.92 323.808 42.264 324.56 42.952C325.312 43.64 325.688 44.736 325.688 46.24V55H324.152L323.744 53.176H323.648C323.088 53.88 322.496 54.4 321.872 54.736C321.248 55.072 320.4 55.24 319.328 55.24C318.16 55.24 317.192 54.936 316.424 54.328C315.656 53.704 315.272 52.736 315.272 51.424C315.272 50.144 315.776 49.16 316.784 48.472C317.792 47.768 319.344 47.384 321.44 47.32L323.624 47.248V46.48C323.624 45.408 323.392 44.664 322.928 44.248C322.464 43.832 321.808 43.624 320.96 43.624C320.288 43.624 319.648 43.728 319.04 43.936C318.432 44.128 317.864 44.352 317.336 44.608L316.688 43.024C317.248 42.72 317.912 42.464 318.68 42.256C319.448 42.032 320.248 41.92 321.08 41.92ZM323.6 48.712L321.704 48.784C320.104 48.848 318.992 49.104 318.368 49.552C317.76 50 317.456 50.632 317.456 51.448C317.456 52.168 317.672 52.696 318.104 53.032C318.552 53.368 319.12 53.536 319.808 53.536C320.88 53.536 321.776 53.24 322.496 52.648C323.232 52.04 323.6 51.112 323.6 49.864V48.712ZM334.221 41.896C335.069 41.896 335.829 42.056 336.501 42.376C337.189 42.696 337.773 43.184 338.253 43.84H338.373L338.661 42.136H340.341V55.216C340.341 57.056 339.869 58.44 338.925 59.368C337.997 60.296 336.549 60.76 334.581 60.76C332.693 60.76 331.149 60.488 329.949 59.944V58C331.213 58.672 332.797 59.008 334.701 59.008C335.805 59.008 336.669 58.68 337.293 58.024C337.933 57.384 338.253 56.504 338.253 55.384V54.88C338.253 54.688 338.261 54.416 338.277 54.064C338.293 53.696 338.309 53.44 338.325 53.296H338.229C337.365 54.592 336.037 55.24 334.245 55.24C332.581 55.24 331.277 54.656 330.333 53.488C329.405 52.32 328.941 50.688 328.941 48.592C328.941 46.544 329.405 44.92 330.333 43.72C331.277 42.504 332.573 41.896 334.221 41.896ZM334.509 43.672C333.437 43.672 332.605 44.104 332.013 44.968C331.421 45.816 331.125 47.032 331.125 48.616C331.125 50.2 331.413 51.416 331.989 52.264C332.565 53.096 333.421 53.512 334.557 53.512C335.853 53.512 336.797 53.168 337.389 52.48C337.981 51.776 338.277 50.648 338.277 49.096V48.592C338.277 46.848 337.973 45.592 337.365 44.824C336.757 44.056 335.805 43.672 334.509 43.672ZM349.395 41.896C350.483 41.896 351.427 42.136 352.227 42.616C353.027 43.096 353.635 43.776 354.051 44.656C354.483 45.52 354.699 46.536 354.699 47.704V48.976H345.891C345.923 50.432 346.291 51.544 346.995 52.312C347.715 53.064 348.715 53.44 349.995 53.44C350.811 53.44 351.531 53.368 352.155 53.224C352.795 53.064 353.451 52.84 354.123 52.552V54.4C353.467 54.688 352.819 54.896 352.179 55.024C351.539 55.168 350.779 55.24 349.899 55.24C348.667 55.24 347.587 54.992 346.659 54.496C345.731 54 345.003 53.264 344.475 52.288C343.963 51.312 343.707 50.104 343.707 48.664C343.707 47.256 343.939 46.048 344.403 45.04C344.883 44.032 345.547 43.256 346.395 42.712C347.259 42.168 348.259 41.896 349.395 41.896ZM349.371 43.624C348.363 43.624 347.563 43.952 346.971 44.608C346.395 45.248 346.051 46.144 345.939 47.296H352.491C352.475 46.208 352.219 45.328 351.723 44.656C351.227 43.968 350.443 43.624 349.371 43.624ZM362.99 37.864C365.118 37.864 366.686 38.272 367.694 39.088C368.718 39.888 369.23 41.104 369.23 42.736C369.23 43.648 369.062 44.408 368.726 45.016C368.39 45.624 367.958 46.112 367.43 46.48C366.918 46.848 366.374 47.128 365.798 47.32L370.502 55H367.982L363.83 47.92H360.422V55H358.262V37.864H362.99ZM362.87 39.736H360.422V46.096H362.99C364.382 46.096 365.398 45.824 366.038 45.28C366.678 44.72 366.998 43.904 366.998 42.832C366.998 41.712 366.662 40.92 365.99 40.456C365.318 39.976 364.278 39.736 362.87 39.736ZM384.087 48.544C384.087 50.672 383.543 52.32 382.455 53.488C381.383 54.656 379.927 55.24 378.087 55.24C376.951 55.24 375.935 54.984 375.039 54.472C374.159 53.944 373.463 53.184 372.951 52.192C372.439 51.184 372.183 49.968 372.183 48.544C372.183 46.416 372.711 44.776 373.767 43.624C374.839 42.472 376.303 41.896 378.159 41.896C379.311 41.896 380.335 42.16 381.231 42.688C382.127 43.2 382.823 43.952 383.319 44.944C383.831 45.92 384.087 47.12 384.087 48.544ZM374.367 48.544C374.367 50.064 374.663 51.272 375.255 52.168C375.863 53.048 376.823 53.488 378.135 53.488C379.431 53.488 380.383 53.048 380.991 52.168C381.599 51.272 381.903 50.064 381.903 48.544C381.903 47.024 381.599 45.832 380.991 44.968C380.383 44.104 379.423 43.672 378.111 43.672C376.799 43.672 375.847 44.104 375.255 44.968C374.663 45.832 374.367 47.024 374.367 48.544ZM398.187 42.136V55H396.459L396.147 53.296H396.051C395.635 53.968 395.059 54.464 394.323 54.784C393.587 55.088 392.803 55.24 391.971 55.24C390.419 55.24 389.251 54.872 388.467 54.136C387.683 53.384 387.291 52.192 387.291 50.56V42.136H389.427V50.416C389.427 52.464 390.379 53.488 392.283 53.488C393.707 53.488 394.691 53.088 395.235 52.288C395.795 51.488 396.075 50.336 396.075 48.832V42.136H398.187ZM406.566 53.512C406.886 53.512 407.214 53.488 407.55 53.44C407.886 53.392 408.158 53.328 408.366 53.248V54.856C408.142 54.968 407.822 55.056 407.406 55.12C406.99 55.2 406.59 55.24 406.206 55.24C405.534 55.24 404.91 55.128 404.334 54.904C403.774 54.664 403.318 54.256 402.966 53.68C402.614 53.104 402.438 52.296 402.438 51.256V43.768H400.614V42.76L402.462 41.92L403.302 39.184H404.55V42.136H408.27V43.768H404.55V51.208C404.55 51.992 404.734 52.576 405.102 52.96C405.486 53.328 405.974 53.512 406.566 53.512ZM415.91 41.896C416.998 41.896 417.942 42.136 418.742 42.616C419.542 43.096 420.15 43.776 420.566 44.656C420.998 45.52 421.214 46.536 421.214 47.704V48.976H412.406C412.438 50.432 412.806 51.544 413.51 52.312C414.23 53.064 415.23 53.44 416.51 53.44C417.326 53.44 418.046 53.368 418.67 53.224C419.31 53.064 419.966 52.84 420.638 52.552V54.4C419.982 54.688 419.334 54.896 418.694 55.024C418.054 55.168 417.294 55.24 416.414 55.24C415.182 55.24 414.102 54.992 413.174 54.496C412.246 54 411.518 53.264 410.99 52.288C410.478 51.312 410.222 50.104 410.222 48.664C410.222 47.256 410.454 46.048 410.918 45.04C411.398 44.032 412.062 43.256 412.91 42.712C413.774 42.168 414.774 41.896 415.91 41.896ZM415.886 43.624C414.878 43.624 414.078 43.952 413.486 44.608C412.91 45.248 412.566 46.144 412.454 47.296H419.006C418.99 46.208 418.734 45.328 418.238 44.656C417.742 43.968 416.958 43.624 415.886 43.624ZM430.489 41.896C430.729 41.896 430.985 41.912 431.257 41.944C431.529 41.96 431.777 41.992 432.001 42.04L431.737 43.984C431.529 43.936 431.297 43.896 431.041 43.864C430.785 43.832 430.553 43.816 430.345 43.816C429.689 43.816 429.073 44 428.497 44.368C427.921 44.72 427.457 45.224 427.105 45.88C426.769 46.52 426.601 47.272 426.601 48.136V55H424.489V42.136H426.217L426.457 44.488H426.553C426.953 43.784 427.481 43.176 428.137 42.664C428.809 42.152 429.593 41.896 430.489 41.896ZM281.117 70.864C280.893 71.792 280.661 72.8 280.421 73.888C280.197 74.96 280.029 75.912 279.917 76.744H277.637L277.469 76.48C277.677 75.648 277.973 74.72 278.357 73.696C278.757 72.672 279.157 71.728 279.557 70.864H281.117ZM276.677 70.864C276.453 71.792 276.229 72.8 276.005 73.888C275.781 74.96 275.605 75.912 275.477 76.744H273.221L273.077 76.48C273.301 75.648 273.597 74.72 273.965 73.696C274.349 72.672 274.741 71.728 275.141 70.864H276.677ZM290.102 70.864L283.718 88H281.654L288.038 70.864H290.102ZM294.496 69.76V75.112C294.496 75.752 294.456 76.352 294.376 76.912H294.52C294.936 76.256 295.496 75.76 296.2 75.424C296.92 75.088 297.696 74.92 298.528 74.92C300.08 74.92 301.248 75.296 302.032 76.048C302.832 76.784 303.232 77.976 303.232 79.624V88H301.144V79.768C301.144 77.704 300.184 76.672 298.264 76.672C296.824 76.672 295.832 77.08 295.288 77.896C294.76 78.696 294.496 79.848 294.496 81.352V88H292.384V69.76H294.496ZM318.404 81.544C318.404 83.672 317.86 85.32 316.772 86.488C315.7 87.656 314.244 88.24 312.404 88.24C311.268 88.24 310.252 87.984 309.356 87.472C308.476 86.944 307.78 86.184 307.268 85.192C306.756 84.184 306.5 82.968 306.5 81.544C306.5 79.416 307.028 77.776 308.084 76.624C309.156 75.472 310.62 74.896 312.476 74.896C313.628 74.896 314.652 75.16 315.548 75.688C316.444 76.2 317.14 76.952 317.636 77.944C318.148 78.92 318.404 80.12 318.404 81.544ZM308.684 81.544C308.684 83.064 308.98 84.272 309.572 85.168C310.18 86.048 311.14 86.488 312.452 86.488C313.748 86.488 314.7 86.048 315.308 85.168C315.916 84.272 316.22 83.064 316.22 81.544C316.22 80.024 315.916 78.832 315.308 77.968C314.7 77.104 313.74 76.672 312.428 76.672C311.116 76.672 310.164 77.104 309.572 77.968C308.98 78.832 308.684 80.024 308.684 81.544ZM335.863 74.896C337.319 74.896 338.407 75.272 339.127 76.024C339.847 76.776 340.207 77.976 340.207 79.624V88H338.119V79.72C338.119 77.688 337.247 76.672 335.503 76.672C334.255 76.672 333.359 77.032 332.815 77.752C332.287 78.472 332.023 79.52 332.023 80.896V88H329.935V79.72C329.935 77.688 329.055 76.672 327.295 76.672C325.999 76.672 325.103 77.072 324.607 77.872C324.111 78.672 323.863 79.824 323.863 81.328V88H321.751V75.136H323.455L323.767 76.888H323.887C324.287 76.216 324.823 75.72 325.495 75.4C326.183 75.064 326.911 74.896 327.679 74.896C329.695 74.896 331.007 75.616 331.615 77.056H331.735C332.167 76.32 332.751 75.776 333.487 75.424C334.223 75.072 335.015 74.896 335.863 74.896ZM349.149 74.896C350.237 74.896 351.181 75.136 351.981 75.616C352.781 76.096 353.389 76.776 353.805 77.656C354.237 78.52 354.453 79.536 354.453 80.704V81.976H345.645C345.677 83.432 346.045 84.544 346.749 85.312C347.469 86.064 348.469 86.44 349.749 86.44C350.565 86.44 351.285 86.368 351.909 86.224C352.549 86.064 353.205 85.84 353.877 85.552V87.4C353.221 87.688 352.573 87.896 351.933 88.024C351.293 88.168 350.533 88.24 349.653 88.24C348.421 88.24 347.341 87.992 346.413 87.496C345.485 87 344.757 86.264 344.229 85.288C343.717 84.312 343.461 83.104 343.461 81.664C343.461 80.256 343.693 79.048 344.157 78.04C344.637 77.032 345.301 76.256 346.149 75.712C347.013 75.168 348.013 74.896 349.149 74.896ZM349.125 76.624C348.117 76.624 347.317 76.952 346.725 77.608C346.149 78.248 345.805 79.144 345.693 80.296H352.245C352.229 79.208 351.973 78.328 351.477 77.656C350.981 76.968 350.197 76.624 349.125 76.624ZM363.613 70.864L363.781 71.128C363.573 71.976 363.269 72.912 362.869 73.936C362.485 74.96 362.101 75.896 361.717 76.744H360.133C360.277 76.152 360.429 75.504 360.589 74.8C360.749 74.096 360.893 73.408 361.021 72.736C361.165 72.048 361.277 71.424 361.357 70.864H363.613ZM359.173 70.864L359.341 71.128C359.117 71.976 358.813 72.912 358.429 73.936C358.045 74.96 357.661 75.896 357.277 76.744H355.741C355.901 76.152 356.053 75.504 356.197 74.8C356.341 74.096 356.477 73.408 356.605 72.736C356.733 72.048 356.837 71.424 356.917 70.864H359.173ZM371.513 83.344L380.561 79.576L371.513 75.28V73.408L382.817 79.048V80.248L371.513 85.216V83.344ZM398.609 70.864C398.385 71.792 398.153 72.8 397.913 73.888C397.689 74.96 397.521 75.912 397.409 76.744H395.129L394.961 76.48C395.169 75.648 395.465 74.72 395.849 73.696C396.249 72.672 396.649 71.728 397.049 70.864H398.609ZM394.169 70.864C393.945 71.792 393.721 72.8 393.497 73.888C393.273 74.96 393.097 75.912 392.969 76.744H390.713L390.569 76.48C390.793 75.648 391.089 74.72 391.457 73.696C391.841 72.672 392.233 71.728 392.633 70.864H394.169ZM407.594 70.864L401.21 88H399.146L405.53 70.864H407.594ZM414.748 74.92C416.316 74.92 417.476 75.264 418.228 75.952C418.98 76.64 419.356 77.736 419.356 79.24V88H417.82L417.412 86.176H417.316C416.756 86.88 416.164 87.4 415.54 87.736C414.916 88.072 414.068 88.24 412.996 88.24C411.828 88.24 410.86 87.936 410.092 87.328C409.324 86.704 408.94 85.736 408.94 84.424C408.94 83.144 409.444 82.16 410.452 81.472C411.46 80.768 413.012 80.384 415.108 80.32L417.292 80.248V79.48C417.292 78.408 417.06 77.664 416.596 77.248C416.132 76.832 415.476 76.624 414.628 76.624C413.956 76.624 413.316 76.728 412.708 76.936C412.1 77.128 411.532 77.352 411.004 77.608L410.356 76.024C410.916 75.72 411.58 75.464 412.348 75.256C413.116 75.032 413.916 74.92 414.748 74.92ZM417.268 81.712L415.372 81.784C413.772 81.848 412.66 82.104 412.036 82.552C411.428 83 411.124 83.632 411.124 84.448C411.124 85.168 411.34 85.696 411.772 86.032C412.22 86.368 412.788 86.536 413.476 86.536C414.548 86.536 415.444 86.24 416.164 85.648C416.9 85.04 417.268 84.112 417.268 82.864V81.712ZM429.449 74.896C431.033 74.896 432.305 75.448 433.265 76.552C434.241 77.656 434.729 79.32 434.729 81.544C434.729 83.736 434.241 85.4 433.265 86.536C432.305 87.672 431.025 88.24 429.425 88.24C428.433 88.24 427.609 88.056 426.953 87.688C426.313 87.32 425.809 86.88 425.441 86.368H425.297C425.313 86.64 425.337 86.984 425.369 87.4C425.417 87.816 425.441 88.176 425.441 88.48V93.76H423.329V75.136H425.057L425.345 76.888H425.441C425.825 76.328 426.329 75.856 426.953 75.472C427.577 75.088 428.409 74.896 429.449 74.896ZM429.065 76.672C427.753 76.672 426.825 77.04 426.281 77.776C425.737 78.512 425.457 79.632 425.441 81.136V81.544C425.441 83.128 425.697 84.352 426.209 85.216C426.737 86.064 427.705 86.488 429.113 86.488C429.881 86.488 430.521 86.28 431.033 85.864C431.545 85.432 431.921 84.84 432.161 84.088C432.417 83.336 432.545 82.48 432.545 81.52C432.545 80.048 432.257 78.872 431.681 77.992C431.121 77.112 430.249 76.672 429.065 76.672ZM444.215 74.896C445.799 74.896 447.071 75.448 448.031 76.552C449.007 77.656 449.495 79.32 449.495 81.544C449.495 83.736 449.007 85.4 448.031 86.536C447.071 87.672 445.791 88.24 444.191 88.24C443.199 88.24 442.375 88.056 441.719 87.688C441.079 87.32 440.575 86.88 440.207 86.368H440.063C440.079 86.64 440.103 86.984 440.135 87.4C440.183 87.816 440.207 88.176 440.207 88.48V93.76H438.095V75.136H439.823L440.111 76.888H440.207C440.591 76.328 441.095 75.856 441.719 75.472C442.343 75.088 443.175 74.896 444.215 74.896ZM443.831 76.672C442.519 76.672 441.591 77.04 441.047 77.776C440.503 78.512 440.223 79.632 440.207 81.136V81.544C440.207 83.128 440.463 84.352 440.975 85.216C441.503 86.064 442.471 86.488 443.879 86.488C444.647 86.488 445.287 86.28 445.799 85.864C446.311 85.432 446.687 84.84 446.927 84.088C447.183 83.336 447.311 82.48 447.311 81.52C447.311 80.048 447.023 78.872 446.447 77.992C445.887 77.112 445.015 76.672 443.831 76.672ZM458.746 70.864L458.914 71.128C458.706 71.976 458.402 72.912 458.002 73.936C457.618 74.96 457.234 75.896 456.85 76.744H455.266C455.41 76.152 455.562 75.504 455.722 74.8C455.882 74.096 456.026 73.408 456.154 72.736C456.298 72.048 456.41 71.424 456.49 70.864H458.746ZM454.306 70.864L454.474 71.128C454.25 71.976 453.946 72.912 453.562 73.936C453.178 74.96 452.794 75.896 452.41 76.744H450.874C451.034 76.152 451.186 75.504 451.33 74.8C451.474 74.096 451.61 73.408 451.738 72.736C451.866 72.048 451.97 71.424 452.05 70.864H454.306Z" fill="black"/>
+<path d="M536 205C534.895 205 534 205.895 534 207C534 208.105 534.895 209 536 209V205ZM759.5 207H761.5V205H759.5V207ZM759.5 588V590H761.5V588H759.5ZM425.586 586.586C424.805 587.367 424.805 588.633 425.586 589.414L438.314 602.142C439.095 602.923 440.361 602.923 441.142 602.142C441.923 601.361 441.923 600.095 441.142 599.314L429.828 588L441.142 576.686C441.923 575.905 441.923 574.639 441.142 573.858C440.361 573.077 439.095 573.077 438.314 573.858L425.586 586.586ZM536 209H759.5V205H536V209ZM757.5 207V588H761.5V207H757.5ZM759.5 586H427V590H759.5V586Z" fill="#232629"/>
+<path d="M825.309 413H822.765L816.693 404.816L814.941 406.352V413H812.781V395.864H814.941V404.312C815.421 403.768 815.909 403.224 816.405 402.68C816.901 402.136 817.397 401.592 817.893 401.048L822.525 395.864H825.045L818.253 403.328L825.309 413ZM828.433 395.312C828.753 395.312 829.033 395.424 829.273 395.648C829.529 395.856 829.657 396.192 829.657 396.656C829.657 397.12 829.529 397.464 829.273 397.688C829.033 397.896 828.753 398 828.433 398C828.081 398 827.785 397.896 827.545 397.688C827.305 397.464 827.185 397.12 827.185 396.656C827.185 396.192 827.305 395.856 827.545 395.648C827.785 395.424 828.081 395.312 828.433 395.312ZM829.465 400.136V413H827.353V400.136H829.465ZM839.54 399.896C839.78 399.896 840.036 399.912 840.308 399.944C840.58 399.96 840.828 399.992 841.052 400.04L840.788 401.984C840.58 401.936 840.348 401.896 840.092 401.864C839.836 401.832 839.604 401.816 839.396 401.816C838.74 401.816 838.124 402 837.548 402.368C836.972 402.72 836.508 403.224 836.156 403.88C835.82 404.52 835.652 405.272 835.652 406.136V413H833.54V400.136H835.268L835.508 402.488H835.604C836.004 401.784 836.532 401.176 837.188 400.664C837.86 400.152 838.644 399.896 839.54 399.896ZM844.534 395.312C844.854 395.312 845.134 395.424 845.374 395.648C845.63 395.856 845.758 396.192 845.758 396.656C845.758 397.12 845.63 397.464 845.374 397.688C845.134 397.896 844.854 398 844.534 398C844.182 398 843.886 397.896 843.646 397.688C843.406 397.464 843.286 397.12 843.286 396.656C843.286 396.192 843.406 395.856 843.646 395.648C843.886 395.424 844.182 395.312 844.534 395.312ZM845.566 400.136V413H843.454V400.136H845.566ZM854.202 399.896C855.05 399.896 855.81 400.056 856.482 400.376C857.17 400.696 857.754 401.184 858.234 401.84H858.354L858.642 400.136H860.322V413.216C860.322 415.056 859.85 416.44 858.906 417.368C857.978 418.296 856.53 418.76 854.562 418.76C852.674 418.76 851.13 418.488 849.93 417.944V416C851.194 416.672 852.778 417.008 854.682 417.008C855.786 417.008 856.65 416.68 857.274 416.024C857.914 415.384 858.234 414.504 858.234 413.384V412.88C858.234 412.688 858.242 412.416 858.258 412.064C858.274 411.696 858.29 411.44 858.306 411.296H858.21C857.346 412.592 856.018 413.24 854.226 413.24C852.562 413.24 851.258 412.656 850.314 411.488C849.386 410.32 848.922 408.688 848.922 406.592C848.922 404.544 849.386 402.92 850.314 401.72C851.258 400.504 852.554 399.896 854.202 399.896ZM854.49 401.672C853.418 401.672 852.586 402.104 851.994 402.968C851.402 403.816 851.106 405.032 851.106 406.616C851.106 408.2 851.394 409.416 851.97 410.264C852.546 411.096 853.402 411.512 854.538 411.512C855.834 411.512 856.778 411.168 857.37 410.48C857.962 409.776 858.258 408.648 858.258 407.096V406.592C858.258 404.848 857.954 403.592 857.346 402.824C856.738 402.056 855.786 401.672 854.49 401.672ZM869.279 399.92C870.847 399.92 872.007 400.264 872.759 400.952C873.511 401.64 873.887 402.736 873.887 404.24V413H872.351L871.943 411.176H871.847C871.287 411.88 870.695 412.4 870.071 412.736C869.447 413.072 868.599 413.24 867.527 413.24C866.359 413.24 865.391 412.936 864.623 412.328C863.855 411.704 863.471 410.736 863.471 409.424C863.471 408.144 863.975 407.16 864.983 406.472C865.991 405.768 867.543 405.384 869.639 405.32L871.823 405.248V404.48C871.823 403.408 871.591 402.664 871.127 402.248C870.663 401.832 870.007 401.624 869.159 401.624C868.487 401.624 867.847 401.728 867.239 401.936C866.631 402.128 866.063 402.352 865.535 402.608L864.887 401.024C865.447 400.72 866.111 400.464 866.879 400.256C867.647 400.032 868.447 399.92 869.279 399.92ZM871.799 406.712L869.903 406.784C868.303 406.848 867.191 407.104 866.567 407.552C865.959 408 865.655 408.632 865.655 409.448C865.655 410.168 865.871 410.696 866.303 411.032C866.751 411.368 867.319 411.536 868.007 411.536C869.079 411.536 869.975 411.24 870.695 410.648C871.431 410.04 871.799 409.112 871.799 407.864V406.712ZM891.972 399.896C893.428 399.896 894.516 400.272 895.236 401.024C895.956 401.776 896.316 402.976 896.316 404.624V413H894.228V404.72C894.228 402.688 893.356 401.672 891.612 401.672C890.364 401.672 889.468 402.032 888.924 402.752C888.396 403.472 888.132 404.52 888.132 405.896V413H886.044V404.72C886.044 402.688 885.164 401.672 883.404 401.672C882.108 401.672 881.212 402.072 880.716 402.872C880.22 403.672 879.972 404.824 879.972 406.328V413H877.86V400.136H879.564L879.876 401.888H879.996C880.396 401.216 880.932 400.72 881.604 400.4C882.292 400.064 883.02 399.896 883.788 399.896C885.804 399.896 887.116 400.616 887.724 402.056H887.844C888.276 401.32 888.86 400.776 889.596 400.424C890.332 400.072 891.124 399.896 891.972 399.896ZM901.37 395.312C901.69 395.312 901.97 395.424 902.21 395.648C902.466 395.856 902.594 396.192 902.594 396.656C902.594 397.12 902.466 397.464 902.21 397.688C901.97 397.896 901.69 398 901.37 398C901.018 398 900.722 397.896 900.482 397.688C900.242 397.464 900.122 397.12 900.122 396.656C900.122 396.192 900.242 395.856 900.482 395.648C900.722 395.424 901.018 395.312 901.37 395.312ZM902.402 400.136V413H900.29V400.136H902.402ZM906.166 411.704C906.166 411.112 906.31 410.696 906.598 410.456C906.886 410.216 907.23 410.096 907.63 410.096C908.03 410.096 908.382 410.216 908.686 410.456C908.99 410.696 909.142 411.112 909.142 411.704C909.142 412.28 908.99 412.696 908.686 412.952C908.382 413.208 908.03 413.336 907.63 413.336C907.23 413.336 906.886 413.208 906.598 412.952C906.31 412.696 906.166 412.28 906.166 411.704ZM917.723 395.864C919.963 395.864 921.595 396.304 922.619 397.184C923.643 398.064 924.155 399.304 924.155 400.904C924.155 401.848 923.939 402.736 923.507 403.568C923.091 404.384 922.379 405.048 921.371 405.56C920.379 406.072 919.027 406.328 917.315 406.328H915.347V413H913.187V395.864H917.723ZM917.531 397.712H915.347V404.48H917.075C918.707 404.48 919.923 404.216 920.723 403.688C921.523 403.16 921.923 402.264 921.923 401C921.923 399.896 921.563 399.072 920.843 398.528C920.139 397.984 919.035 397.712 917.531 397.712ZM932.303 399.92C933.871 399.92 935.031 400.264 935.783 400.952C936.535 401.64 936.911 402.736 936.911 404.24V413H935.375L934.967 411.176H934.871C934.311 411.88 933.719 412.4 933.095 412.736C932.471 413.072 931.623 413.24 930.551 413.24C929.383 413.24 928.415 412.936 927.647 412.328C926.879 411.704 926.495 410.736 926.495 409.424C926.495 408.144 926.999 407.16 928.007 406.472C929.015 405.768 930.567 405.384 932.663 405.32L934.847 405.248V404.48C934.847 403.408 934.615 402.664 934.151 402.248C933.687 401.832 933.031 401.624 932.183 401.624C931.511 401.624 930.871 401.728 930.263 401.936C929.655 402.128 929.087 402.352 928.559 402.608L927.911 401.024C928.471 400.72 929.135 400.464 929.903 400.256C930.671 400.032 931.471 399.92 932.303 399.92ZM934.823 406.712L932.927 406.784C931.327 406.848 930.215 407.104 929.591 407.552C928.983 408 928.679 408.632 928.679 409.448C928.679 410.168 928.895 410.696 929.327 411.032C929.775 411.368 930.343 411.536 931.031 411.536C932.103 411.536 932.999 411.24 933.719 410.648C934.455 410.04 934.823 409.112 934.823 407.864V406.712ZM945.444 399.896C946.292 399.896 947.052 400.056 947.724 400.376C948.412 400.696 948.996 401.184 949.476 401.84H949.596L949.884 400.136H951.564V413.216C951.564 415.056 951.092 416.44 950.148 417.368C949.22 418.296 947.772 418.76 945.804 418.76C943.916 418.76 942.372 418.488 941.172 417.944V416C942.436 416.672 944.02 417.008 945.924 417.008C947.028 417.008 947.892 416.68 948.516 416.024C949.156 415.384 949.476 414.504 949.476 413.384V412.88C949.476 412.688 949.484 412.416 949.5 412.064C949.516 411.696 949.532 411.44 949.548 411.296H949.452C948.588 412.592 947.26 413.24 945.468 413.24C943.804 413.24 942.5 412.656 941.556 411.488C940.628 410.32 940.164 408.688 940.164 406.592C940.164 404.544 940.628 402.92 941.556 401.72C942.5 400.504 943.796 399.896 945.444 399.896ZM945.732 401.672C944.66 401.672 943.828 402.104 943.236 402.968C942.644 403.816 942.348 405.032 942.348 406.616C942.348 408.2 942.636 409.416 943.212 410.264C943.788 411.096 944.644 411.512 945.78 411.512C947.076 411.512 948.02 411.168 948.612 410.48C949.204 409.776 949.5 408.648 949.5 407.096V406.592C949.5 404.848 949.196 403.592 948.588 402.824C947.98 402.056 947.028 401.672 945.732 401.672ZM960.617 399.896C961.705 399.896 962.649 400.136 963.449 400.616C964.249 401.096 964.857 401.776 965.273 402.656C965.705 403.52 965.921 404.536 965.921 405.704V406.976H957.113C957.145 408.432 957.513 409.544 958.217 410.312C958.937 411.064 959.937 411.44 961.217 411.44C962.033 411.44 962.753 411.368 963.377 411.224C964.017 411.064 964.673 410.84 965.345 410.552V412.4C964.689 412.688 964.041 412.896 963.401 413.024C962.761 413.168 962.001 413.24 961.121 413.24C959.889 413.24 958.809 412.992 957.881 412.496C956.953 412 956.225 411.264 955.697 410.288C955.185 409.312 954.929 408.104 954.929 406.664C954.929 405.256 955.161 404.048 955.625 403.04C956.105 402.032 956.769 401.256 957.617 400.712C958.481 400.168 959.481 399.896 960.617 399.896ZM960.593 401.624C959.585 401.624 958.785 401.952 958.193 402.608C957.617 403.248 957.273 404.144 957.161 405.296H963.713C963.697 404.208 963.441 403.328 962.945 402.656C962.449 401.968 961.665 401.624 960.593 401.624ZM974.212 395.864C976.34 395.864 977.908 396.272 978.916 397.088C979.94 397.888 980.452 399.104 980.452 400.736C980.452 401.648 980.284 402.408 979.948 403.016C979.612 403.624 979.18 404.112 978.652 404.48C978.14 404.848 977.596 405.128 977.02 405.32L981.724 413H979.204L975.052 405.92H971.644V413H969.484V395.864H974.212ZM974.092 397.736H971.644V404.096H974.212C975.604 404.096 976.62 403.824 977.26 403.28C977.9 402.72 978.22 401.904 978.22 400.832C978.22 399.712 977.884 398.92 977.212 398.456C976.54 397.976 975.5 397.736 974.092 397.736ZM995.31 406.544C995.31 408.672 994.766 410.32 993.678 411.488C992.606 412.656 991.15 413.24 989.31 413.24C988.174 413.24 987.158 412.984 986.262 412.472C985.382 411.944 984.686 411.184 984.174 410.192C983.662 409.184 983.406 407.968 983.406 406.544C983.406 404.416 983.934 402.776 984.99 401.624C986.062 400.472 987.526 399.896 989.382 399.896C990.534 399.896 991.558 400.16 992.454 400.688C993.35 401.2 994.046 401.952 994.542 402.944C995.054 403.92 995.31 405.12 995.31 406.544ZM985.59 406.544C985.59 408.064 985.886 409.272 986.478 410.168C987.086 411.048 988.046 411.488 989.358 411.488C990.654 411.488 991.606 411.048 992.214 410.168C992.822 409.272 993.126 408.064 993.126 406.544C993.126 405.024 992.822 403.832 992.214 402.968C991.606 402.104 990.646 401.672 989.334 401.672C988.022 401.672 987.07 402.104 986.478 402.968C985.886 403.832 985.59 405.024 985.59 406.544ZM1009.41 400.136V413H1007.68L1007.37 411.296H1007.27C1006.86 411.968 1006.28 412.464 1005.55 412.784C1004.81 413.088 1004.03 413.24 1003.19 413.24C1001.64 413.24 1000.47 412.872 999.689 412.136C998.905 411.384 998.513 410.192 998.513 408.56V400.136H1000.65V408.416C1000.65 410.464 1001.6 411.488 1003.51 411.488C1004.93 411.488 1005.91 411.088 1006.46 410.288C1007.02 409.488 1007.3 408.336 1007.3 406.832V400.136H1009.41ZM1017.79 411.512C1018.11 411.512 1018.44 411.488 1018.77 411.44C1019.11 411.392 1019.38 411.328 1019.59 411.248V412.856C1019.37 412.968 1019.05 413.056 1018.63 413.12C1018.21 413.2 1017.81 413.24 1017.43 413.24C1016.76 413.24 1016.13 413.128 1015.56 412.904C1015 412.664 1014.54 412.256 1014.19 411.68C1013.84 411.104 1013.66 410.296 1013.66 409.256V401.768H1011.84V400.76L1013.69 399.92L1014.53 397.184H1015.77V400.136H1019.49V401.768H1015.77V409.208C1015.77 409.992 1015.96 410.576 1016.33 410.96C1016.71 411.328 1017.2 411.512 1017.79 411.512ZM1027.13 399.896C1028.22 399.896 1029.17 400.136 1029.97 400.616C1030.77 401.096 1031.37 401.776 1031.79 402.656C1032.22 403.52 1032.44 404.536 1032.44 405.704V406.976H1023.63C1023.66 408.432 1024.03 409.544 1024.73 410.312C1025.45 411.064 1026.45 411.44 1027.73 411.44C1028.55 411.44 1029.27 411.368 1029.89 411.224C1030.53 411.064 1031.19 410.84 1031.86 410.552V412.4C1031.21 412.688 1030.56 412.896 1029.92 413.024C1029.28 413.168 1028.52 413.24 1027.64 413.24C1026.41 413.24 1025.33 412.992 1024.4 412.496C1023.47 412 1022.74 411.264 1022.21 410.288C1021.7 409.312 1021.45 408.104 1021.45 406.664C1021.45 405.256 1021.68 404.048 1022.14 403.04C1022.62 402.032 1023.29 401.256 1024.13 400.712C1025 400.168 1026 399.896 1027.13 399.896ZM1027.11 401.624C1026.1 401.624 1025.3 401.952 1024.71 402.608C1024.13 403.248 1023.79 404.144 1023.68 405.296H1030.23C1030.21 404.208 1029.96 403.328 1029.46 402.656C1028.97 401.968 1028.18 401.624 1027.11 401.624ZM1041.71 399.896C1041.95 399.896 1042.21 399.912 1042.48 399.944C1042.75 399.96 1043 399.992 1043.22 400.04L1042.96 401.984C1042.75 401.936 1042.52 401.896 1042.26 401.864C1042.01 401.832 1041.78 401.816 1041.57 401.816C1040.91 401.816 1040.3 402 1039.72 402.368C1039.14 402.72 1038.68 403.224 1038.33 403.88C1037.99 404.52 1037.82 405.272 1037.82 406.136V413H1035.71V400.136H1037.44L1037.68 402.488H1037.78C1038.18 401.784 1038.7 401.176 1039.36 400.664C1040.03 400.152 1040.82 399.896 1041.71 399.896ZM1043.88 411.704C1043.88 411.112 1044.03 410.696 1044.32 410.456C1044.6 410.216 1044.95 410.096 1045.35 410.096C1045.75 410.096 1046.1 410.216 1046.4 410.456C1046.71 410.696 1046.86 411.112 1046.86 411.704C1046.86 412.28 1046.71 412.696 1046.4 412.952C1046.1 413.208 1045.75 413.336 1045.35 413.336C1044.95 413.336 1044.6 413.208 1044.32 412.952C1044.03 412.696 1043.88 412.28 1043.88 411.704ZM1056.74 399.896C1058.32 399.896 1059.59 400.448 1060.55 401.552C1061.53 402.656 1062.02 404.32 1062.02 406.544C1062.02 408.736 1061.53 410.4 1060.55 411.536C1059.59 412.672 1058.31 413.24 1056.71 413.24C1055.72 413.24 1054.9 413.056 1054.24 412.688C1053.6 412.32 1053.1 411.88 1052.73 411.368H1052.59C1052.6 411.64 1052.63 411.984 1052.66 412.4C1052.71 412.816 1052.73 413.176 1052.73 413.48V418.76H1050.62V400.136H1052.35L1052.63 401.888H1052.73C1053.11 401.328 1053.62 400.856 1054.24 400.472C1054.87 400.088 1055.7 399.896 1056.74 399.896ZM1056.35 401.672C1055.04 401.672 1054.11 402.04 1053.57 402.776C1053.03 403.512 1052.75 404.632 1052.73 406.136V406.544C1052.73 408.128 1052.99 409.352 1053.5 410.216C1054.03 411.064 1054.99 411.488 1056.4 411.488C1057.17 411.488 1057.81 411.28 1058.32 410.864C1058.83 410.432 1059.21 409.84 1059.45 409.088C1059.71 408.336 1059.83 407.48 1059.83 406.52C1059.83 405.048 1059.55 403.872 1058.97 402.992C1058.41 402.112 1057.54 401.672 1056.35 401.672ZM1076.57 406.544C1076.57 408.672 1076.02 410.32 1074.94 411.488C1073.86 412.656 1072.41 413.24 1070.57 413.24C1069.43 413.24 1068.42 412.984 1067.52 412.472C1066.64 411.944 1065.94 411.184 1065.43 410.192C1064.92 409.184 1064.66 407.968 1064.66 406.544C1064.66 404.416 1065.19 402.776 1066.25 401.624C1067.32 400.472 1068.78 399.896 1070.64 399.896C1071.79 399.896 1072.82 400.16 1073.71 400.688C1074.61 401.2 1075.3 401.952 1075.8 402.944C1076.31 403.92 1076.57 405.12 1076.57 406.544ZM1066.85 406.544C1066.85 408.064 1067.14 409.272 1067.74 410.168C1068.34 411.048 1069.3 411.488 1070.62 411.488C1071.91 411.488 1072.86 411.048 1073.47 410.168C1074.08 409.272 1074.38 408.064 1074.38 406.544C1074.38 405.024 1074.08 403.832 1073.47 402.968C1072.86 402.104 1071.9 401.672 1070.59 401.672C1069.28 401.672 1068.33 402.104 1067.74 402.968C1067.14 403.832 1066.85 405.024 1066.85 406.544ZM1086.04 399.896C1087.62 399.896 1088.89 400.448 1089.85 401.552C1090.83 402.656 1091.32 404.32 1091.32 406.544C1091.32 408.736 1090.83 410.4 1089.85 411.536C1088.89 412.672 1087.61 413.24 1086.01 413.24C1085.02 413.24 1084.2 413.056 1083.54 412.688C1082.9 412.32 1082.4 411.88 1082.03 411.368H1081.88C1081.9 411.64 1081.92 411.984 1081.96 412.4C1082 412.816 1082.03 413.176 1082.03 413.48V418.76H1079.92V400.136H1081.64L1081.93 401.888H1082.03C1082.41 401.328 1082.92 400.856 1083.54 400.472C1084.16 400.088 1085 399.896 1086.04 399.896ZM1085.65 401.672C1084.34 401.672 1083.41 402.04 1082.87 402.776C1082.32 403.512 1082.04 404.632 1082.03 406.136V406.544C1082.03 408.128 1082.28 409.352 1082.8 410.216C1083.32 411.064 1084.29 411.488 1085.7 411.488C1086.47 411.488 1087.11 411.28 1087.62 410.864C1088.13 410.432 1088.51 409.84 1088.75 409.088C1089 408.336 1089.13 407.48 1089.13 406.52C1089.13 405.048 1088.84 403.872 1088.27 402.992C1087.71 402.112 1086.84 401.672 1085.65 401.672ZM1099.7 395.864C1101.82 395.864 1103.39 396.272 1104.4 397.088C1105.42 397.888 1105.94 399.104 1105.94 400.736C1105.94 401.648 1105.77 402.408 1105.43 403.016C1105.1 403.624 1104.66 404.112 1104.14 404.48C1103.62 404.848 1103.08 405.128 1102.5 405.32L1107.21 413H1104.69L1100.54 405.92H1097.13V413H1094.97V395.864H1099.7ZM1099.58 397.736H1097.13V404.096H1099.7C1101.09 404.096 1102.1 403.824 1102.74 403.28C1103.38 402.72 1103.7 401.904 1103.7 400.832C1103.7 399.712 1103.37 398.92 1102.7 398.456C1102.02 397.976 1100.98 397.736 1099.58 397.736ZM1120.79 406.544C1120.79 408.672 1120.25 410.32 1119.16 411.488C1118.09 412.656 1116.63 413.24 1114.79 413.24C1113.66 413.24 1112.64 412.984 1111.75 412.472C1110.87 411.944 1110.17 411.184 1109.66 410.192C1109.15 409.184 1108.89 407.968 1108.89 406.544C1108.89 404.416 1109.42 402.776 1110.47 401.624C1111.55 400.472 1113.01 399.896 1114.87 399.896C1116.02 399.896 1117.04 400.16 1117.94 400.688C1118.83 401.2 1119.53 401.952 1120.03 402.944C1120.54 403.92 1120.79 405.12 1120.79 406.544ZM1111.07 406.544C1111.07 408.064 1111.37 409.272 1111.96 410.168C1112.57 411.048 1113.53 411.488 1114.84 411.488C1116.14 411.488 1117.09 411.048 1117.7 410.168C1118.31 409.272 1118.61 408.064 1118.61 406.544C1118.61 405.024 1118.31 403.832 1117.7 402.968C1117.09 402.104 1116.13 401.672 1114.82 401.672C1113.51 401.672 1112.55 402.104 1111.96 402.968C1111.37 403.832 1111.07 405.024 1111.07 406.544ZM1134.89 400.136V413H1133.17L1132.85 411.296H1132.76C1132.34 411.968 1131.77 412.464 1131.03 412.784C1130.29 413.088 1129.51 413.24 1128.68 413.24C1127.13 413.24 1125.96 412.872 1125.17 412.136C1124.39 411.384 1124 410.192 1124 408.56V400.136H1126.13V408.416C1126.13 410.464 1127.09 411.488 1128.99 411.488C1130.41 411.488 1131.4 411.088 1131.94 410.288C1132.5 409.488 1132.78 408.336 1132.78 406.832V400.136H1134.89ZM1143.27 411.512C1143.59 411.512 1143.92 411.488 1144.26 411.44C1144.59 411.392 1144.87 411.328 1145.07 411.248V412.856C1144.85 412.968 1144.53 413.056 1144.11 413.12C1143.7 413.2 1143.3 413.24 1142.91 413.24C1142.24 413.24 1141.62 413.128 1141.04 412.904C1140.48 412.664 1140.03 412.256 1139.67 411.68C1139.32 411.104 1139.15 410.296 1139.15 409.256V401.768H1137.32V400.76L1139.17 399.92L1140.01 397.184H1141.26V400.136H1144.98V401.768H1141.26V409.208C1141.26 409.992 1141.44 410.576 1141.81 410.96C1142.19 411.328 1142.68 411.512 1143.27 411.512ZM1152.62 399.896C1153.71 399.896 1154.65 400.136 1155.45 400.616C1156.25 401.096 1156.86 401.776 1157.27 402.656C1157.71 403.52 1157.92 404.536 1157.92 405.704V406.976H1149.11C1149.15 408.432 1149.51 409.544 1150.22 410.312C1150.94 411.064 1151.94 411.44 1153.22 411.44C1154.03 411.44 1154.75 411.368 1155.38 411.224C1156.02 411.064 1156.67 410.84 1157.35 410.552V412.4C1156.69 412.688 1156.04 412.896 1155.4 413.024C1154.76 413.168 1154 413.24 1153.12 413.24C1151.89 413.24 1150.81 412.992 1149.88 412.496C1148.95 412 1148.23 411.264 1147.7 410.288C1147.19 409.312 1146.93 408.104 1146.93 406.664C1146.93 405.256 1147.16 404.048 1147.63 403.04C1148.11 402.032 1148.77 401.256 1149.62 400.712C1150.48 400.168 1151.48 399.896 1152.62 399.896ZM1152.59 401.624C1151.59 401.624 1150.79 401.952 1150.19 402.608C1149.62 403.248 1149.27 404.144 1149.16 405.296H1155.71C1155.7 404.208 1155.44 403.328 1154.95 402.656C1154.45 401.968 1153.67 401.624 1152.59 401.624ZM1160.12 406.424C1160.12 404.472 1160.4 402.592 1160.96 400.784C1161.53 398.96 1162.43 397.32 1163.64 395.864H1165.64C1164.52 397.368 1163.67 399.024 1163.09 400.832C1162.53 402.64 1162.25 404.496 1162.25 406.4C1162.25 408.256 1162.53 410.08 1163.09 411.872C1163.67 413.648 1164.51 415.288 1165.61 416.792H1163.64C1162.43 415.384 1161.53 413.792 1160.96 412.016C1160.4 410.224 1160.12 408.36 1160.12 406.424ZM1172.59 406.424C1172.59 408.36 1172.3 410.224 1171.73 412.016C1171.17 413.792 1170.28 415.384 1169.06 416.792H1167.1C1168.2 415.288 1169.03 413.648 1169.59 411.872C1170.17 410.08 1170.46 408.256 1170.46 406.4C1170.46 404.496 1170.17 402.64 1169.59 400.832C1169.03 399.024 1168.19 397.368 1167.07 395.864H1169.06C1170.28 397.32 1171.17 398.96 1171.73 400.784C1172.3 402.592 1172.59 404.472 1172.59 406.424Z" fill="black"/>
+</svg>
diff --git a/docs/pics/PageRouterPush.svg b/docs/pics/PageRouterPush.svg
new file mode 100644 (file)
index 0000000..a74a1e9
--- /dev/null
@@ -0,0 +1,14 @@
+<svg width="1224" height="726" viewBox="0 0 1224 726" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="2" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M159 116H329.023C331.968 116 334.674 117.618 336.069 120.212L380.693 203.212C381.964 205.577 381.964 208.423 380.693 210.788L336.069 293.788C334.674 296.382 331.968 298 329.023 298H159C154.582 298 151 294.418 151 290V124C151 119.582 154.582 116 159 116Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 181.864C222.956 181.864 224.588 182.304 225.612 183.184C226.636 184.064 227.148 185.304 227.148 186.904C227.148 187.848 226.932 188.736 226.5 189.568C226.084 190.384 225.372 191.048 224.364 191.56C223.372 192.072 222.02 192.328 220.308 192.328H218.34V199H216.18V181.864H220.716ZM220.524 183.712H218.34V190.48H220.068C221.7 190.48 222.916 190.216 223.716 189.688C224.516 189.16 224.916 188.264 224.916 187C224.916 185.896 224.556 185.072 223.836 184.528C223.132 183.984 222.028 183.712 220.524 183.712ZM235.295 185.92C236.863 185.92 238.023 186.264 238.775 186.952C239.527 187.64 239.903 188.736 239.903 190.24V199H238.367L237.959 197.176H237.863C237.303 197.88 236.711 198.4 236.087 198.736C235.463 199.072 234.615 199.24 233.543 199.24C232.375 199.24 231.407 198.936 230.639 198.328C229.871 197.704 229.487 196.736 229.487 195.424C229.487 194.144 229.991 193.16 230.999 192.472C232.007 191.768 233.559 191.384 235.655 191.32L237.839 191.248V190.48C237.839 189.408 237.607 188.664 237.143 188.248C236.679 187.832 236.023 187.624 235.175 187.624C234.503 187.624 233.863 187.728 233.255 187.936C232.647 188.128 232.079 188.352 231.551 188.608L230.903 187.024C231.463 186.72 232.127 186.464 232.895 186.256C233.663 186.032 234.463 185.92 235.295 185.92ZM237.815 192.712L235.919 192.784C234.319 192.848 233.207 193.104 232.583 193.552C231.975 194 231.671 194.632 231.671 195.448C231.671 196.168 231.887 196.696 232.319 197.032C232.767 197.368 233.335 197.536 234.023 197.536C235.095 197.536 235.991 197.24 236.711 196.648C237.447 196.04 237.815 195.112 237.815 193.864V192.712ZM248.436 185.896C249.284 185.896 250.044 186.056 250.716 186.376C251.404 186.696 251.988 187.184 252.468 187.84H252.588L252.876 186.136H254.556V199.216C254.556 201.056 254.084 202.44 253.14 203.368C252.212 204.296 250.764 204.76 248.796 204.76C246.908 204.76 245.364 204.488 244.164 203.944V202C245.428 202.672 247.012 203.008 248.916 203.008C250.02 203.008 250.884 202.68 251.508 202.024C252.148 201.384 252.468 200.504 252.468 199.384V198.88C252.468 198.688 252.476 198.416 252.492 198.064C252.508 197.696 252.524 197.44 252.54 197.296H252.444C251.58 198.592 250.252 199.24 248.46 199.24C246.796 199.24 245.492 198.656 244.548 197.488C243.62 196.32 243.156 194.688 243.156 192.592C243.156 190.544 243.62 188.92 244.548 187.72C245.492 186.504 246.788 185.896 248.436 185.896ZM248.724 187.672C247.652 187.672 246.82 188.104 246.228 188.968C245.636 189.816 245.34 191.032 245.34 192.616C245.34 194.2 245.628 195.416 246.204 196.264C246.78 197.096 247.636 197.512 248.772 197.512C250.068 197.512 251.012 197.168 251.604 196.48C252.196 195.776 252.492 194.648 252.492 193.096V192.592C252.492 190.848 252.188 189.592 251.58 188.824C250.972 188.056 250.02 187.672 248.724 187.672ZM263.61 185.896C264.698 185.896 265.642 186.136 266.442 186.616C267.242 187.096 267.85 187.776 268.266 188.656C268.698 189.52 268.914 190.536 268.914 191.704V192.976H260.106C260.138 194.432 260.506 195.544 261.21 196.312C261.93 197.064 262.93 197.44 264.21 197.44C265.026 197.44 265.746 197.368 266.37 197.224C267.01 197.064 267.666 196.84 268.338 196.552V198.4C267.682 198.688 267.034 198.896 266.394 199.024C265.754 199.168 264.994 199.24 264.114 199.24C262.882 199.24 261.802 198.992 260.874 198.496C259.946 198 259.218 197.264 258.69 196.288C258.178 195.312 257.922 194.104 257.922 192.664C257.922 191.256 258.154 190.048 258.618 189.04C259.098 188.032 259.762 187.256 260.61 186.712C261.474 186.168 262.474 185.896 263.61 185.896ZM263.586 187.624C262.578 187.624 261.778 187.952 261.186 188.608C260.61 189.248 260.266 190.144 260.154 191.296H266.706C266.69 190.208 266.434 189.328 265.938 188.656C265.442 187.968 264.658 187.624 263.586 187.624ZM204.683 214.864C204.459 215.792 204.227 216.8 203.987 217.888C203.763 218.96 203.595 219.912 203.483 220.744H201.203L201.035 220.48C201.243 219.648 201.539 218.72 201.923 217.696C202.323 216.672 202.723 215.728 203.123 214.864H204.683ZM200.243 214.864C200.019 215.792 199.795 216.8 199.571 217.888C199.347 218.96 199.171 219.912 199.043 220.744H196.787L196.643 220.48C196.867 219.648 197.163 218.72 197.531 217.696C197.915 216.672 198.307 215.728 198.707 214.864H200.243ZM213.668 214.864L207.284 232H205.22L211.604 214.864H213.668ZM218.062 213.76V219.112C218.062 219.752 218.022 220.352 217.942 220.912H218.086C218.502 220.256 219.062 219.76 219.766 219.424C220.486 219.088 221.262 218.92 222.094 218.92C223.646 218.92 224.814 219.296 225.598 220.048C226.398 220.784 226.798 221.976 226.798 223.624V232H224.71V223.768C224.71 221.704 223.75 220.672 221.83 220.672C220.39 220.672 219.398 221.08 218.854 221.896C218.326 222.696 218.062 223.848 218.062 225.352V232H215.95V213.76H218.062ZM241.97 225.544C241.97 227.672 241.426 229.32 240.338 230.488C239.266 231.656 237.81 232.24 235.97 232.24C234.834 232.24 233.818 231.984 232.922 231.472C232.042 230.944 231.346 230.184 230.834 229.192C230.322 228.184 230.066 226.968 230.066 225.544C230.066 223.416 230.594 221.776 231.65 220.624C232.722 219.472 234.186 218.896 236.042 218.896C237.194 218.896 238.218 219.16 239.114 219.688C240.01 220.2 240.706 220.952 241.202 221.944C241.714 222.92 241.97 224.12 241.97 225.544ZM232.25 225.544C232.25 227.064 232.546 228.272 233.138 229.168C233.746 230.048 234.706 230.488 236.018 230.488C237.314 230.488 238.266 230.048 238.874 229.168C239.482 228.272 239.786 227.064 239.786 225.544C239.786 224.024 239.482 222.832 238.874 221.968C238.266 221.104 237.306 220.672 235.994 220.672C234.682 220.672 233.73 221.104 233.138 221.968C232.546 222.832 232.25 224.024 232.25 225.544ZM259.429 218.896C260.885 218.896 261.973 219.272 262.693 220.024C263.413 220.776 263.773 221.976 263.773 223.624V232H261.685V223.72C261.685 221.688 260.813 220.672 259.069 220.672C257.821 220.672 256.925 221.032 256.381 221.752C255.853 222.472 255.589 223.52 255.589 224.896V232H253.501V223.72C253.501 221.688 252.621 220.672 250.861 220.672C249.565 220.672 248.669 221.072 248.173 221.872C247.677 222.672 247.429 223.824 247.429 225.328V232H245.317V219.136H247.021L247.333 220.888H247.453C247.853 220.216 248.389 219.72 249.061 219.4C249.749 219.064 250.477 218.896 251.245 218.896C253.261 218.896 254.573 219.616 255.181 221.056H255.301C255.733 220.32 256.317 219.776 257.053 219.424C257.789 219.072 258.581 218.896 259.429 218.896ZM272.715 218.896C273.803 218.896 274.747 219.136 275.547 219.616C276.347 220.096 276.955 220.776 277.371 221.656C277.803 222.52 278.019 223.536 278.019 224.704V225.976H269.211C269.243 227.432 269.611 228.544 270.315 229.312C271.035 230.064 272.035 230.44 273.315 230.44C274.131 230.44 274.851 230.368 275.475 230.224C276.115 230.064 276.771 229.84 277.443 229.552V231.4C276.787 231.688 276.139 231.896 275.499 232.024C274.859 232.168 274.099 232.24 273.219 232.24C271.987 232.24 270.907 231.992 269.979 231.496C269.051 231 268.323 230.264 267.795 229.288C267.283 228.312 267.027 227.104 267.027 225.664C267.027 224.256 267.259 223.048 267.723 222.04C268.203 221.032 268.867 220.256 269.715 219.712C270.579 219.168 271.579 218.896 272.715 218.896ZM272.691 220.624C271.683 220.624 270.883 220.952 270.291 221.608C269.715 222.248 269.371 223.144 269.259 224.296H275.811C275.795 223.208 275.539 222.328 275.043 221.656C274.547 220.968 273.763 220.624 272.691 220.624ZM287.18 214.864L287.348 215.128C287.14 215.976 286.836 216.912 286.436 217.936C286.052 218.96 285.668 219.896 285.284 220.744H283.7C283.844 220.152 283.996 219.504 284.156 218.8C284.316 218.096 284.46 217.408 284.588 216.736C284.732 216.048 284.844 215.424 284.924 214.864H287.18ZM282.74 214.864L282.908 215.128C282.684 215.976 282.38 216.912 281.996 217.936C281.612 218.96 281.228 219.896 280.844 220.744H279.308C279.468 220.152 279.62 219.504 279.764 218.8C279.908 218.096 280.044 217.408 280.172 216.736C280.3 216.048 280.404 215.424 280.484 214.864H282.74Z" fill="black"/>
+<path d="M306.501 37.864C308.741 37.864 310.373 38.304 311.397 39.184C312.421 40.064 312.933 41.304 312.933 42.904C312.933 43.848 312.717 44.736 312.285 45.568C311.869 46.384 311.157 47.048 310.149 47.56C309.157 48.072 307.805 48.328 306.093 48.328H304.125V55H301.965V37.864H306.501ZM306.309 39.712H304.125V46.48H305.853C307.485 46.48 308.701 46.216 309.501 45.688C310.301 45.16 310.701 44.264 310.701 43C310.701 41.896 310.341 41.072 309.621 40.528C308.917 39.984 307.813 39.712 306.309 39.712ZM321.08 41.92C322.648 41.92 323.808 42.264 324.56 42.952C325.312 43.64 325.688 44.736 325.688 46.24V55H324.152L323.744 53.176H323.648C323.088 53.88 322.496 54.4 321.872 54.736C321.248 55.072 320.4 55.24 319.328 55.24C318.16 55.24 317.192 54.936 316.424 54.328C315.656 53.704 315.272 52.736 315.272 51.424C315.272 50.144 315.776 49.16 316.784 48.472C317.792 47.768 319.344 47.384 321.44 47.32L323.624 47.248V46.48C323.624 45.408 323.392 44.664 322.928 44.248C322.464 43.832 321.808 43.624 320.96 43.624C320.288 43.624 319.648 43.728 319.04 43.936C318.432 44.128 317.864 44.352 317.336 44.608L316.688 43.024C317.248 42.72 317.912 42.464 318.68 42.256C319.448 42.032 320.248 41.92 321.08 41.92ZM323.6 48.712L321.704 48.784C320.104 48.848 318.992 49.104 318.368 49.552C317.76 50 317.456 50.632 317.456 51.448C317.456 52.168 317.672 52.696 318.104 53.032C318.552 53.368 319.12 53.536 319.808 53.536C320.88 53.536 321.776 53.24 322.496 52.648C323.232 52.04 323.6 51.112 323.6 49.864V48.712ZM334.221 41.896C335.069 41.896 335.829 42.056 336.501 42.376C337.189 42.696 337.773 43.184 338.253 43.84H338.373L338.661 42.136H340.341V55.216C340.341 57.056 339.869 58.44 338.925 59.368C337.997 60.296 336.549 60.76 334.581 60.76C332.693 60.76 331.149 60.488 329.949 59.944V58C331.213 58.672 332.797 59.008 334.701 59.008C335.805 59.008 336.669 58.68 337.293 58.024C337.933 57.384 338.253 56.504 338.253 55.384V54.88C338.253 54.688 338.261 54.416 338.277 54.064C338.293 53.696 338.309 53.44 338.325 53.296H338.229C337.365 54.592 336.037 55.24 334.245 55.24C332.581 55.24 331.277 54.656 330.333 53.488C329.405 52.32 328.941 50.688 328.941 48.592C328.941 46.544 329.405 44.92 330.333 43.72C331.277 42.504 332.573 41.896 334.221 41.896ZM334.509 43.672C333.437 43.672 332.605 44.104 332.013 44.968C331.421 45.816 331.125 47.032 331.125 48.616C331.125 50.2 331.413 51.416 331.989 52.264C332.565 53.096 333.421 53.512 334.557 53.512C335.853 53.512 336.797 53.168 337.389 52.48C337.981 51.776 338.277 50.648 338.277 49.096V48.592C338.277 46.848 337.973 45.592 337.365 44.824C336.757 44.056 335.805 43.672 334.509 43.672ZM349.395 41.896C350.483 41.896 351.427 42.136 352.227 42.616C353.027 43.096 353.635 43.776 354.051 44.656C354.483 45.52 354.699 46.536 354.699 47.704V48.976H345.891C345.923 50.432 346.291 51.544 346.995 52.312C347.715 53.064 348.715 53.44 349.995 53.44C350.811 53.44 351.531 53.368 352.155 53.224C352.795 53.064 353.451 52.84 354.123 52.552V54.4C353.467 54.688 352.819 54.896 352.179 55.024C351.539 55.168 350.779 55.24 349.899 55.24C348.667 55.24 347.587 54.992 346.659 54.496C345.731 54 345.003 53.264 344.475 52.288C343.963 51.312 343.707 50.104 343.707 48.664C343.707 47.256 343.939 46.048 344.403 45.04C344.883 44.032 345.547 43.256 346.395 42.712C347.259 42.168 348.259 41.896 349.395 41.896ZM349.371 43.624C348.363 43.624 347.563 43.952 346.971 44.608C346.395 45.248 346.051 46.144 345.939 47.296H352.491C352.475 46.208 352.219 45.328 351.723 44.656C351.227 43.968 350.443 43.624 349.371 43.624ZM362.99 37.864C365.118 37.864 366.686 38.272 367.694 39.088C368.718 39.888 369.23 41.104 369.23 42.736C369.23 43.648 369.062 44.408 368.726 45.016C368.39 45.624 367.958 46.112 367.43 46.48C366.918 46.848 366.374 47.128 365.798 47.32L370.502 55H367.982L363.83 47.92H360.422V55H358.262V37.864H362.99ZM362.87 39.736H360.422V46.096H362.99C364.382 46.096 365.398 45.824 366.038 45.28C366.678 44.72 366.998 43.904 366.998 42.832C366.998 41.712 366.662 40.92 365.99 40.456C365.318 39.976 364.278 39.736 362.87 39.736ZM384.087 48.544C384.087 50.672 383.543 52.32 382.455 53.488C381.383 54.656 379.927 55.24 378.087 55.24C376.951 55.24 375.935 54.984 375.039 54.472C374.159 53.944 373.463 53.184 372.951 52.192C372.439 51.184 372.183 49.968 372.183 48.544C372.183 46.416 372.711 44.776 373.767 43.624C374.839 42.472 376.303 41.896 378.159 41.896C379.311 41.896 380.335 42.16 381.231 42.688C382.127 43.2 382.823 43.952 383.319 44.944C383.831 45.92 384.087 47.12 384.087 48.544ZM374.367 48.544C374.367 50.064 374.663 51.272 375.255 52.168C375.863 53.048 376.823 53.488 378.135 53.488C379.431 53.488 380.383 53.048 380.991 52.168C381.599 51.272 381.903 50.064 381.903 48.544C381.903 47.024 381.599 45.832 380.991 44.968C380.383 44.104 379.423 43.672 378.111 43.672C376.799 43.672 375.847 44.104 375.255 44.968C374.663 45.832 374.367 47.024 374.367 48.544ZM398.187 42.136V55H396.459L396.147 53.296H396.051C395.635 53.968 395.059 54.464 394.323 54.784C393.587 55.088 392.803 55.24 391.971 55.24C390.419 55.24 389.251 54.872 388.467 54.136C387.683 53.384 387.291 52.192 387.291 50.56V42.136H389.427V50.416C389.427 52.464 390.379 53.488 392.283 53.488C393.707 53.488 394.691 53.088 395.235 52.288C395.795 51.488 396.075 50.336 396.075 48.832V42.136H398.187ZM406.566 53.512C406.886 53.512 407.214 53.488 407.55 53.44C407.886 53.392 408.158 53.328 408.366 53.248V54.856C408.142 54.968 407.822 55.056 407.406 55.12C406.99 55.2 406.59 55.24 406.206 55.24C405.534 55.24 404.91 55.128 404.334 54.904C403.774 54.664 403.318 54.256 402.966 53.68C402.614 53.104 402.438 52.296 402.438 51.256V43.768H400.614V42.76L402.462 41.92L403.302 39.184H404.55V42.136H408.27V43.768H404.55V51.208C404.55 51.992 404.734 52.576 405.102 52.96C405.486 53.328 405.974 53.512 406.566 53.512ZM415.91 41.896C416.998 41.896 417.942 42.136 418.742 42.616C419.542 43.096 420.15 43.776 420.566 44.656C420.998 45.52 421.214 46.536 421.214 47.704V48.976H412.406C412.438 50.432 412.806 51.544 413.51 52.312C414.23 53.064 415.23 53.44 416.51 53.44C417.326 53.44 418.046 53.368 418.67 53.224C419.31 53.064 419.966 52.84 420.638 52.552V54.4C419.982 54.688 419.334 54.896 418.694 55.024C418.054 55.168 417.294 55.24 416.414 55.24C415.182 55.24 414.102 54.992 413.174 54.496C412.246 54 411.518 53.264 410.99 52.288C410.478 51.312 410.222 50.104 410.222 48.664C410.222 47.256 410.454 46.048 410.918 45.04C411.398 44.032 412.062 43.256 412.91 42.712C413.774 42.168 414.774 41.896 415.91 41.896ZM415.886 43.624C414.878 43.624 414.078 43.952 413.486 44.608C412.91 45.248 412.566 46.144 412.454 47.296H419.006C418.99 46.208 418.734 45.328 418.238 44.656C417.742 43.968 416.958 43.624 415.886 43.624ZM430.489 41.896C430.729 41.896 430.985 41.912 431.257 41.944C431.529 41.96 431.777 41.992 432.001 42.04L431.737 43.984C431.529 43.936 431.297 43.896 431.041 43.864C430.785 43.832 430.553 43.816 430.345 43.816C429.689 43.816 429.073 44 428.497 44.368C427.921 44.72 427.457 45.224 427.105 45.88C426.769 46.52 426.601 47.272 426.601 48.136V55H424.489V42.136H426.217L426.457 44.488H426.553C426.953 43.784 427.481 43.176 428.137 42.664C428.809 42.152 429.593 41.896 430.489 41.896ZM328.683 70.864C328.459 71.792 328.227 72.8 327.987 73.888C327.763 74.96 327.595 75.912 327.483 76.744H325.203L325.035 76.48C325.243 75.648 325.539 74.72 325.923 73.696C326.323 72.672 326.723 71.728 327.123 70.864H328.683ZM324.243 70.864C324.019 71.792 323.795 72.8 323.571 73.888C323.347 74.96 323.171 75.912 323.043 76.744H320.787L320.643 76.48C320.867 75.648 321.163 74.72 321.531 73.696C321.915 72.672 322.307 71.728 322.707 70.864H324.243ZM337.668 70.864L331.284 88H329.22L335.604 70.864H337.668ZM342.062 69.76V75.112C342.062 75.752 342.022 76.352 341.942 76.912H342.086C342.502 76.256 343.062 75.76 343.766 75.424C344.486 75.088 345.262 74.92 346.094 74.92C347.646 74.92 348.814 75.296 349.598 76.048C350.398 76.784 350.798 77.976 350.798 79.624V88H348.71V79.768C348.71 77.704 347.75 76.672 345.83 76.672C344.39 76.672 343.398 77.08 342.854 77.896C342.326 78.696 342.062 79.848 342.062 81.352V88H339.95V69.76H342.062ZM365.97 81.544C365.97 83.672 365.426 85.32 364.338 86.488C363.266 87.656 361.81 88.24 359.97 88.24C358.834 88.24 357.818 87.984 356.922 87.472C356.042 86.944 355.346 86.184 354.834 85.192C354.322 84.184 354.066 82.968 354.066 81.544C354.066 79.416 354.594 77.776 355.65 76.624C356.722 75.472 358.186 74.896 360.042 74.896C361.194 74.896 362.218 75.16 363.114 75.688C364.01 76.2 364.706 76.952 365.202 77.944C365.714 78.92 365.97 80.12 365.97 81.544ZM356.25 81.544C356.25 83.064 356.546 84.272 357.138 85.168C357.746 86.048 358.706 86.488 360.018 86.488C361.314 86.488 362.266 86.048 362.874 85.168C363.482 84.272 363.786 83.064 363.786 81.544C363.786 80.024 363.482 78.832 362.874 77.968C362.266 77.104 361.306 76.672 359.994 76.672C358.682 76.672 357.73 77.104 357.138 77.968C356.546 78.832 356.25 80.024 356.25 81.544ZM383.429 74.896C384.885 74.896 385.973 75.272 386.693 76.024C387.413 76.776 387.773 77.976 387.773 79.624V88H385.685V79.72C385.685 77.688 384.813 76.672 383.069 76.672C381.821 76.672 380.925 77.032 380.381 77.752C379.853 78.472 379.589 79.52 379.589 80.896V88H377.501V79.72C377.501 77.688 376.621 76.672 374.861 76.672C373.565 76.672 372.669 77.072 372.173 77.872C371.677 78.672 371.429 79.824 371.429 81.328V88H369.317V75.136H371.021L371.333 76.888H371.453C371.853 76.216 372.389 75.72 373.061 75.4C373.749 75.064 374.477 74.896 375.245 74.896C377.261 74.896 378.573 75.616 379.181 77.056H379.301C379.733 76.32 380.317 75.776 381.053 75.424C381.789 75.072 382.581 74.896 383.429 74.896ZM396.715 74.896C397.803 74.896 398.747 75.136 399.547 75.616C400.347 76.096 400.955 76.776 401.371 77.656C401.803 78.52 402.019 79.536 402.019 80.704V81.976H393.211C393.243 83.432 393.611 84.544 394.315 85.312C395.035 86.064 396.035 86.44 397.315 86.44C398.131 86.44 398.851 86.368 399.475 86.224C400.115 86.064 400.771 85.84 401.443 85.552V87.4C400.787 87.688 400.139 87.896 399.499 88.024C398.859 88.168 398.099 88.24 397.219 88.24C395.987 88.24 394.907 87.992 393.979 87.496C393.051 87 392.323 86.264 391.795 85.288C391.283 84.312 391.027 83.104 391.027 81.664C391.027 80.256 391.259 79.048 391.723 78.04C392.203 77.032 392.867 76.256 393.715 75.712C394.579 75.168 395.579 74.896 396.715 74.896ZM396.691 76.624C395.683 76.624 394.883 76.952 394.291 77.608C393.715 78.248 393.371 79.144 393.259 80.296H399.811C399.795 79.208 399.539 78.328 399.043 77.656C398.547 76.968 397.763 76.624 396.691 76.624ZM411.18 70.864L411.348 71.128C411.14 71.976 410.836 72.912 410.436 73.936C410.052 74.96 409.668 75.896 409.284 76.744H407.7C407.844 76.152 407.996 75.504 408.156 74.8C408.316 74.096 408.46 73.408 408.588 72.736C408.732 72.048 408.844 71.424 408.924 70.864H411.18ZM406.74 70.864L406.908 71.128C406.684 71.976 406.38 72.912 405.996 73.936C405.612 74.96 405.228 75.896 404.844 76.744H403.308C403.468 76.152 403.62 75.504 403.764 74.8C403.908 74.096 404.044 73.408 404.172 72.736C404.3 72.048 404.404 71.424 404.484 70.864H406.74Z" fill="black"/>
+<rect x="2" y="383" width="728" height="341" rx="8" fill="#D1D5D9" stroke="#393C3F" stroke-width="4"/>
+<path d="M297 497H527.023C529.968 497 532.674 498.618 534.069 501.212L578.693 584.212C579.964 586.577 579.964 589.423 578.693 591.788L534.069 674.788C532.674 677.382 529.968 679 527.023 679H297C292.582 679 289 675.418 289 671V505C289 500.582 292.582 497 297 497Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M435.716 562.864C437.956 562.864 439.588 563.304 440.612 564.184C441.636 565.064 442.148 566.304 442.148 567.904C442.148 568.848 441.932 569.736 441.5 570.568C441.084 571.384 440.372 572.048 439.364 572.56C438.372 573.072 437.02 573.328 435.308 573.328H433.34V580H431.18V562.864H435.716ZM435.524 564.712H433.34V571.48H435.068C436.7 571.48 437.916 571.216 438.716 570.688C439.516 570.16 439.916 569.264 439.916 568C439.916 566.896 439.556 566.072 438.836 565.528C438.132 564.984 437.028 564.712 435.524 564.712ZM450.295 566.92C451.863 566.92 453.023 567.264 453.775 567.952C454.527 568.64 454.903 569.736 454.903 571.24V580H453.367L452.959 578.176H452.863C452.303 578.88 451.711 579.4 451.087 579.736C450.463 580.072 449.615 580.24 448.543 580.24C447.375 580.24 446.407 579.936 445.639 579.328C444.871 578.704 444.487 577.736 444.487 576.424C444.487 575.144 444.991 574.16 445.999 573.472C447.007 572.768 448.559 572.384 450.655 572.32L452.839 572.248V571.48C452.839 570.408 452.607 569.664 452.143 569.248C451.679 568.832 451.023 568.624 450.175 568.624C449.503 568.624 448.863 568.728 448.255 568.936C447.647 569.128 447.079 569.352 446.551 569.608L445.903 568.024C446.463 567.72 447.127 567.464 447.895 567.256C448.663 567.032 449.463 566.92 450.295 566.92ZM452.815 573.712L450.919 573.784C449.319 573.848 448.207 574.104 447.583 574.552C446.975 575 446.671 575.632 446.671 576.448C446.671 577.168 446.887 577.696 447.319 578.032C447.767 578.368 448.335 578.536 449.023 578.536C450.095 578.536 450.991 578.24 451.711 577.648C452.447 577.04 452.815 576.112 452.815 574.864V573.712ZM463.436 566.896C464.284 566.896 465.044 567.056 465.716 567.376C466.404 567.696 466.988 568.184 467.468 568.84H467.588L467.876 567.136H469.556V580.216C469.556 582.056 469.084 583.44 468.14 584.368C467.212 585.296 465.764 585.76 463.796 585.76C461.908 585.76 460.364 585.488 459.164 584.944V583C460.428 583.672 462.012 584.008 463.916 584.008C465.02 584.008 465.884 583.68 466.508 583.024C467.148 582.384 467.468 581.504 467.468 580.384V579.88C467.468 579.688 467.476 579.416 467.492 579.064C467.508 578.696 467.524 578.44 467.54 578.296H467.444C466.58 579.592 465.252 580.24 463.46 580.24C461.796 580.24 460.492 579.656 459.548 578.488C458.62 577.32 458.156 575.688 458.156 573.592C458.156 571.544 458.62 569.92 459.548 568.72C460.492 567.504 461.788 566.896 463.436 566.896ZM463.724 568.672C462.652 568.672 461.82 569.104 461.228 569.968C460.636 570.816 460.34 572.032 460.34 573.616C460.34 575.2 460.628 576.416 461.204 577.264C461.78 578.096 462.636 578.512 463.772 578.512C465.068 578.512 466.012 578.168 466.604 577.48C467.196 576.776 467.492 575.648 467.492 574.096V573.592C467.492 571.848 467.188 570.592 466.58 569.824C465.972 569.056 465.02 568.672 463.724 568.672ZM478.61 566.896C479.698 566.896 480.642 567.136 481.442 567.616C482.242 568.096 482.85 568.776 483.266 569.656C483.698 570.52 483.914 571.536 483.914 572.704V573.976H475.106C475.138 575.432 475.506 576.544 476.21 577.312C476.93 578.064 477.93 578.44 479.21 578.44C480.026 578.44 480.746 578.368 481.37 578.224C482.01 578.064 482.666 577.84 483.338 577.552V579.4C482.682 579.688 482.034 579.896 481.394 580.024C480.754 580.168 479.994 580.24 479.114 580.24C477.882 580.24 476.802 579.992 475.874 579.496C474.946 579 474.218 578.264 473.69 577.288C473.178 576.312 472.922 575.104 472.922 573.664C472.922 572.256 473.154 571.048 473.618 570.04C474.098 569.032 474.762 568.256 475.61 567.712C476.474 567.168 477.474 566.896 478.61 566.896ZM478.586 568.624C477.578 568.624 476.778 568.952 476.186 569.608C475.61 570.248 475.266 571.144 475.154 572.296H481.706C481.69 571.208 481.434 570.328 480.938 569.656C480.442 568.968 479.658 568.624 478.586 568.624ZM430.863 595.864C430.639 596.792 430.407 597.8 430.167 598.888C429.943 599.96 429.775 600.912 429.663 601.744H427.383L427.215 601.48C427.423 600.648 427.719 599.72 428.103 598.696C428.503 597.672 428.903 596.728 429.303 595.864H430.863ZM426.423 595.864C426.199 596.792 425.975 597.8 425.751 598.888C425.527 599.96 425.351 600.912 425.223 601.744H422.967L422.823 601.48C423.047 600.648 423.343 599.72 423.711 598.696C424.095 597.672 424.487 596.728 424.887 595.864H426.423ZM439.848 595.864L433.464 613H431.4L437.784 595.864H439.848ZM447.002 599.92C448.57 599.92 449.73 600.264 450.482 600.952C451.234 601.64 451.61 602.736 451.61 604.24V613H450.074L449.666 611.176H449.57C449.01 611.88 448.418 612.4 447.794 612.736C447.17 613.072 446.322 613.24 445.25 613.24C444.082 613.24 443.114 612.936 442.346 612.328C441.578 611.704 441.194 610.736 441.194 609.424C441.194 608.144 441.698 607.16 442.706 606.472C443.714 605.768 445.266 605.384 447.362 605.32L449.546 605.248V604.48C449.546 603.408 449.314 602.664 448.85 602.248C448.386 601.832 447.73 601.624 446.882 601.624C446.21 601.624 445.57 601.728 444.962 601.936C444.354 602.128 443.786 602.352 443.258 602.608L442.61 601.024C443.17 600.72 443.834 600.464 444.602 600.256C445.37 600.032 446.17 599.92 447.002 599.92ZM449.522 606.712L447.626 606.784C446.026 606.848 444.914 607.104 444.29 607.552C443.682 608 443.378 608.632 443.378 609.448C443.378 610.168 443.594 610.696 444.026 611.032C444.474 611.368 445.042 611.536 445.73 611.536C446.802 611.536 447.698 611.24 448.418 610.648C449.154 610.04 449.522 609.112 449.522 607.864V606.712ZM461.703 599.896C463.287 599.896 464.559 600.448 465.519 601.552C466.495 602.656 466.983 604.32 466.983 606.544C466.983 608.736 466.495 610.4 465.519 611.536C464.559 612.672 463.279 613.24 461.679 613.24C460.687 613.24 459.863 613.056 459.207 612.688C458.567 612.32 458.063 611.88 457.695 611.368H457.551C457.567 611.64 457.591 611.984 457.623 612.4C457.671 612.816 457.695 613.176 457.695 613.48V618.76H455.583V600.136H457.311L457.599 601.888H457.695C458.079 601.328 458.583 600.856 459.207 600.472C459.831 600.088 460.663 599.896 461.703 599.896ZM461.319 601.672C460.007 601.672 459.079 602.04 458.535 602.776C457.991 603.512 457.711 604.632 457.695 606.136V606.544C457.695 608.128 457.951 609.352 458.463 610.216C458.991 611.064 459.959 611.488 461.367 611.488C462.135 611.488 462.775 611.28 463.287 610.864C463.799 610.432 464.175 609.84 464.415 609.088C464.671 608.336 464.799 607.48 464.799 606.52C464.799 605.048 464.511 603.872 463.935 602.992C463.375 602.112 462.503 601.672 461.319 601.672ZM476.469 599.896C478.053 599.896 479.325 600.448 480.285 601.552C481.261 602.656 481.749 604.32 481.749 606.544C481.749 608.736 481.261 610.4 480.285 611.536C479.325 612.672 478.045 613.24 476.445 613.24C475.453 613.24 474.629 613.056 473.973 612.688C473.333 612.32 472.829 611.88 472.461 611.368H472.317C472.333 611.64 472.357 611.984 472.389 612.4C472.437 612.816 472.461 613.176 472.461 613.48V618.76H470.349V600.136H472.077L472.365 601.888H472.461C472.845 601.328 473.349 600.856 473.973 600.472C474.597 600.088 475.429 599.896 476.469 599.896ZM476.085 601.672C474.773 601.672 473.845 602.04 473.301 602.776C472.757 603.512 472.477 604.632 472.461 606.136V606.544C472.461 608.128 472.717 609.352 473.229 610.216C473.757 611.064 474.725 611.488 476.133 611.488C476.901 611.488 477.541 611.28 478.053 610.864C478.565 610.432 478.941 609.84 479.181 609.088C479.437 608.336 479.565 607.48 479.565 606.52C479.565 605.048 479.277 603.872 478.701 602.992C478.141 602.112 477.269 601.672 476.085 601.672ZM491 595.864L491.168 596.128C490.96 596.976 490.656 597.912 490.256 598.936C489.872 599.96 489.488 600.896 489.104 601.744H487.52C487.664 601.152 487.816 600.504 487.976 599.8C488.136 599.096 488.28 598.408 488.408 597.736C488.552 597.048 488.664 596.424 488.744 595.864H491ZM486.56 595.864L486.728 596.128C486.504 596.976 486.2 597.912 485.816 598.936C485.432 599.96 485.048 600.896 484.664 601.744H483.128C483.288 601.152 483.44 600.504 483.584 599.8C483.728 599.096 483.864 598.408 483.992 597.736C484.12 597.048 484.224 596.424 484.304 595.864H486.56Z" fill="black"/>
+<path d="M159 497H329.023C331.968 497 334.674 498.618 336.069 501.212L380.693 584.212C381.964 586.577 381.964 589.423 380.693 591.788L336.069 674.788C334.674 677.382 331.968 679 329.023 679H159C154.582 679 151 675.418 151 671V505C151 500.582 154.582 497 159 497Z" fill="#9B9EA2" stroke="#393C3F" stroke-width="4"/>
+<path d="M220.716 562.864C222.956 562.864 224.588 563.304 225.612 564.184C226.636 565.064 227.148 566.304 227.148 567.904C227.148 568.848 226.932 569.736 226.5 570.568C226.084 571.384 225.372 572.048 224.364 572.56C223.372 573.072 222.02 573.328 220.308 573.328H218.34V580H216.18V562.864H220.716ZM220.524 564.712H218.34V571.48H220.068C221.7 571.48 222.916 571.216 223.716 570.688C224.516 570.16 224.916 569.264 224.916 568C224.916 566.896 224.556 566.072 223.836 565.528C223.132 564.984 222.028 564.712 220.524 564.712ZM235.295 566.92C236.863 566.92 238.023 567.264 238.775 567.952C239.527 568.64 239.903 569.736 239.903 571.24V580H238.367L237.959 578.176H237.863C237.303 578.88 236.711 579.4 236.087 579.736C235.463 580.072 234.615 580.24 233.543 580.24C232.375 580.24 231.407 579.936 230.639 579.328C229.871 578.704 229.487 577.736 229.487 576.424C229.487 575.144 229.991 574.16 230.999 573.472C232.007 572.768 233.559 572.384 235.655 572.32L237.839 572.248V571.48C237.839 570.408 237.607 569.664 237.143 569.248C236.679 568.832 236.023 568.624 235.175 568.624C234.503 568.624 233.863 568.728 233.255 568.936C232.647 569.128 232.079 569.352 231.551 569.608L230.903 568.024C231.463 567.72 232.127 567.464 232.895 567.256C233.663 567.032 234.463 566.92 235.295 566.92ZM237.815 573.712L235.919 573.784C234.319 573.848 233.207 574.104 232.583 574.552C231.975 575 231.671 575.632 231.671 576.448C231.671 577.168 231.887 577.696 232.319 578.032C232.767 578.368 233.335 578.536 234.023 578.536C235.095 578.536 235.991 578.24 236.711 577.648C237.447 577.04 237.815 576.112 237.815 574.864V573.712ZM248.436 566.896C249.284 566.896 250.044 567.056 250.716 567.376C251.404 567.696 251.988 568.184 252.468 568.84H252.588L252.876 567.136H254.556V580.216C254.556 582.056 254.084 583.44 253.14 584.368C252.212 585.296 250.764 585.76 248.796 585.76C246.908 585.76 245.364 585.488 244.164 584.944V583C245.428 583.672 247.012 584.008 248.916 584.008C250.02 584.008 250.884 583.68 251.508 583.024C252.148 582.384 252.468 581.504 252.468 580.384V579.88C252.468 579.688 252.476 579.416 252.492 579.064C252.508 578.696 252.524 578.44 252.54 578.296H252.444C251.58 579.592 250.252 580.24 248.46 580.24C246.796 580.24 245.492 579.656 244.548 578.488C243.62 577.32 243.156 575.688 243.156 573.592C243.156 571.544 243.62 569.92 244.548 568.72C245.492 567.504 246.788 566.896 248.436 566.896ZM248.724 568.672C247.652 568.672 246.82 569.104 246.228 569.968C245.636 570.816 245.34 572.032 245.34 573.616C245.34 575.2 245.628 576.416 246.204 577.264C246.78 578.096 247.636 578.512 248.772 578.512C250.068 578.512 251.012 578.168 251.604 577.48C252.196 576.776 252.492 575.648 252.492 574.096V573.592C252.492 571.848 252.188 570.592 251.58 569.824C250.972 569.056 250.02 568.672 248.724 568.672ZM263.61 566.896C264.698 566.896 265.642 567.136 266.442 567.616C267.242 568.096 267.85 568.776 268.266 569.656C268.698 570.52 268.914 571.536 268.914 572.704V573.976H260.106C260.138 575.432 260.506 576.544 261.21 577.312C261.93 578.064 262.93 578.44 264.21 578.44C265.026 578.44 265.746 578.368 266.37 578.224C267.01 578.064 267.666 577.84 268.338 577.552V579.4C267.682 579.688 267.034 579.896 266.394 580.024C265.754 580.168 264.994 580.24 264.114 580.24C262.882 580.24 261.802 579.992 260.874 579.496C259.946 579 259.218 578.264 258.69 577.288C258.178 576.312 257.922 575.104 257.922 573.664C257.922 572.256 258.154 571.048 258.618 570.04C259.098 569.032 259.762 568.256 260.61 567.712C261.474 567.168 262.474 566.896 263.61 566.896ZM263.586 568.624C262.578 568.624 261.778 568.952 261.186 569.608C260.61 570.248 260.266 571.144 260.154 572.296H266.706C266.69 571.208 266.434 570.328 265.938 569.656C265.442 568.968 264.658 568.624 263.586 568.624ZM204.683 595.864C204.459 596.792 204.227 597.8 203.987 598.888C203.763 599.96 203.595 600.912 203.483 601.744H201.203L201.035 601.48C201.243 600.648 201.539 599.72 201.923 598.696C202.323 597.672 202.723 596.728 203.123 595.864H204.683ZM200.243 595.864C200.019 596.792 199.795 597.8 199.571 598.888C199.347 599.96 199.171 600.912 199.043 601.744H196.787L196.643 601.48C196.867 600.648 197.163 599.72 197.531 598.696C197.915 597.672 198.307 596.728 198.707 595.864H200.243ZM213.668 595.864L207.284 613H205.22L211.604 595.864H213.668ZM218.062 594.76V600.112C218.062 600.752 218.022 601.352 217.942 601.912H218.086C218.502 601.256 219.062 600.76 219.766 600.424C220.486 600.088 221.262 599.92 222.094 599.92C223.646 599.92 224.814 600.296 225.598 601.048C226.398 601.784 226.798 602.976 226.798 604.624V613H224.71V604.768C224.71 602.704 223.75 601.672 221.83 601.672C220.39 601.672 219.398 602.08 218.854 602.896C218.326 603.696 218.062 604.848 218.062 606.352V613H215.95V594.76H218.062ZM241.97 606.544C241.97 608.672 241.426 610.32 240.338 611.488C239.266 612.656 237.81 613.24 235.97 613.24C234.834 613.24 233.818 612.984 232.922 612.472C232.042 611.944 231.346 611.184 230.834 610.192C230.322 609.184 230.066 607.968 230.066 606.544C230.066 604.416 230.594 602.776 231.65 601.624C232.722 600.472 234.186 599.896 236.042 599.896C237.194 599.896 238.218 600.16 239.114 600.688C240.01 601.2 240.706 601.952 241.202 602.944C241.714 603.92 241.97 605.12 241.97 606.544ZM232.25 606.544C232.25 608.064 232.546 609.272 233.138 610.168C233.746 611.048 234.706 611.488 236.018 611.488C237.314 611.488 238.266 611.048 238.874 610.168C239.482 609.272 239.786 608.064 239.786 606.544C239.786 605.024 239.482 603.832 238.874 602.968C238.266 602.104 237.306 601.672 235.994 601.672C234.682 601.672 233.73 602.104 233.138 602.968C232.546 603.832 232.25 605.024 232.25 606.544ZM259.429 599.896C260.885 599.896 261.973 600.272 262.693 601.024C263.413 601.776 263.773 602.976 263.773 604.624V613H261.685V604.72C261.685 602.688 260.813 601.672 259.069 601.672C257.821 601.672 256.925 602.032 256.381 602.752C255.853 603.472 255.589 604.52 255.589 605.896V613H253.501V604.72C253.501 602.688 252.621 601.672 250.861 601.672C249.565 601.672 248.669 602.072 248.173 602.872C247.677 603.672 247.429 604.824 247.429 606.328V613H245.317V600.136H247.021L247.333 601.888H247.453C247.853 601.216 248.389 600.72 249.061 600.4C249.749 600.064 250.477 599.896 251.245 599.896C253.261 599.896 254.573 600.616 255.181 602.056H255.301C255.733 601.32 256.317 600.776 257.053 600.424C257.789 600.072 258.581 599.896 259.429 599.896ZM272.715 599.896C273.803 599.896 274.747 600.136 275.547 600.616C276.347 601.096 276.955 601.776 277.371 602.656C277.803 603.52 278.019 604.536 278.019 605.704V606.976H269.211C269.243 608.432 269.611 609.544 270.315 610.312C271.035 611.064 272.035 611.44 273.315 611.44C274.131 611.44 274.851 611.368 275.475 611.224C276.115 611.064 276.771 610.84 277.443 610.552V612.4C276.787 612.688 276.139 612.896 275.499 613.024C274.859 613.168 274.099 613.24 273.219 613.24C271.987 613.24 270.907 612.992 269.979 612.496C269.051 612 268.323 611.264 267.795 610.288C267.283 609.312 267.027 608.104 267.027 606.664C267.027 605.256 267.259 604.048 267.723 603.04C268.203 602.032 268.867 601.256 269.715 600.712C270.579 600.168 271.579 599.896 272.715 599.896ZM272.691 601.624C271.683 601.624 270.883 601.952 270.291 602.608C269.715 603.248 269.371 604.144 269.259 605.296H275.811C275.795 604.208 275.539 603.328 275.043 602.656C274.547 601.968 273.763 601.624 272.691 601.624ZM287.18 595.864L287.348 596.128C287.14 596.976 286.836 597.912 286.436 598.936C286.052 599.96 285.668 600.896 285.284 601.744H283.7C283.844 601.152 283.996 600.504 284.156 599.8C284.316 599.096 284.46 598.408 284.588 597.736C284.732 597.048 284.844 596.424 284.924 595.864H287.18ZM282.74 595.864L282.908 596.128C282.684 596.976 282.38 597.912 281.996 598.936C281.612 599.96 281.228 600.896 280.844 601.744H279.308C279.468 601.152 279.62 600.504 279.764 599.8C279.908 599.096 280.044 598.408 280.172 597.736C280.3 597.048 280.404 596.424 280.484 595.864H282.74Z" fill="black"/>
+<path d="M306.501 418.864C308.741 418.864 310.373 419.304 311.397 420.184C312.421 421.064 312.933 422.304 312.933 423.904C312.933 424.848 312.717 425.736 312.285 426.568C311.869 427.384 311.157 428.048 310.149 428.56C309.157 429.072 307.805 429.328 306.093 429.328H304.125V436H301.965V418.864H306.501ZM306.309 420.712H304.125V427.48H305.853C307.485 427.48 308.701 427.216 309.501 426.688C310.301 426.16 310.701 425.264 310.701 424C310.701 422.896 310.341 422.072 309.621 421.528C308.917 420.984 307.813 420.712 306.309 420.712ZM321.08 422.92C322.648 422.92 323.808 423.264 324.56 423.952C325.312 424.64 325.688 425.736 325.688 427.24V436H324.152L323.744 434.176H323.648C323.088 434.88 322.496 435.4 321.872 435.736C321.248 436.072 320.4 436.24 319.328 436.24C318.16 436.24 317.192 435.936 316.424 435.328C315.656 434.704 315.272 433.736 315.272 432.424C315.272 431.144 315.776 430.16 316.784 429.472C317.792 428.768 319.344 428.384 321.44 428.32L323.624 428.248V427.48C323.624 426.408 323.392 425.664 322.928 425.248C322.464 424.832 321.808 424.624 320.96 424.624C320.288 424.624 319.648 424.728 319.04 424.936C318.432 425.128 317.864 425.352 317.336 425.608L316.688 424.024C317.248 423.72 317.912 423.464 318.68 423.256C319.448 423.032 320.248 422.92 321.08 422.92ZM323.6 429.712L321.704 429.784C320.104 429.848 318.992 430.104 318.368 430.552C317.76 431 317.456 431.632 317.456 432.448C317.456 433.168 317.672 433.696 318.104 434.032C318.552 434.368 319.12 434.536 319.808 434.536C320.88 434.536 321.776 434.24 322.496 433.648C323.232 433.04 323.6 432.112 323.6 430.864V429.712ZM334.221 422.896C335.069 422.896 335.829 423.056 336.501 423.376C337.189 423.696 337.773 424.184 338.253 424.84H338.373L338.661 423.136H340.341V436.216C340.341 438.056 339.869 439.44 338.925 440.368C337.997 441.296 336.549 441.76 334.581 441.76C332.693 441.76 331.149 441.488 329.949 440.944V439C331.213 439.672 332.797 440.008 334.701 440.008C335.805 440.008 336.669 439.68 337.293 439.024C337.933 438.384 338.253 437.504 338.253 436.384V435.88C338.253 435.688 338.261 435.416 338.277 435.064C338.293 434.696 338.309 434.44 338.325 434.296H338.229C337.365 435.592 336.037 436.24 334.245 436.24C332.581 436.24 331.277 435.656 330.333 434.488C329.405 433.32 328.941 431.688 328.941 429.592C328.941 427.544 329.405 425.92 330.333 424.72C331.277 423.504 332.573 422.896 334.221 422.896ZM334.509 424.672C333.437 424.672 332.605 425.104 332.013 425.968C331.421 426.816 331.125 428.032 331.125 429.616C331.125 431.2 331.413 432.416 331.989 433.264C332.565 434.096 333.421 434.512 334.557 434.512C335.853 434.512 336.797 434.168 337.389 433.48C337.981 432.776 338.277 431.648 338.277 430.096V429.592C338.277 427.848 337.973 426.592 337.365 425.824C336.757 425.056 335.805 424.672 334.509 424.672ZM349.395 422.896C350.483 422.896 351.427 423.136 352.227 423.616C353.027 424.096 353.635 424.776 354.051 425.656C354.483 426.52 354.699 427.536 354.699 428.704V429.976H345.891C345.923 431.432 346.291 432.544 346.995 433.312C347.715 434.064 348.715 434.44 349.995 434.44C350.811 434.44 351.531 434.368 352.155 434.224C352.795 434.064 353.451 433.84 354.123 433.552V435.4C353.467 435.688 352.819 435.896 352.179 436.024C351.539 436.168 350.779 436.24 349.899 436.24C348.667 436.24 347.587 435.992 346.659 435.496C345.731 435 345.003 434.264 344.475 433.288C343.963 432.312 343.707 431.104 343.707 429.664C343.707 428.256 343.939 427.048 344.403 426.04C344.883 425.032 345.547 424.256 346.395 423.712C347.259 423.168 348.259 422.896 349.395 422.896ZM349.371 424.624C348.363 424.624 347.563 424.952 346.971 425.608C346.395 426.248 346.051 427.144 345.939 428.296H352.491C352.475 427.208 352.219 426.328 351.723 425.656C351.227 424.968 350.443 424.624 349.371 424.624ZM362.99 418.864C365.118 418.864 366.686 419.272 367.694 420.088C368.718 420.888 369.23 422.104 369.23 423.736C369.23 424.648 369.062 425.408 368.726 426.016C368.39 426.624 367.958 427.112 367.43 427.48C366.918 427.848 366.374 428.128 365.798 428.32L370.502 436H367.982L363.83 428.92H360.422V436H358.262V418.864H362.99ZM362.87 420.736H360.422V427.096H362.99C364.382 427.096 365.398 426.824 366.038 426.28C366.678 425.72 366.998 424.904 366.998 423.832C366.998 422.712 366.662 421.92 365.99 421.456C365.318 420.976 364.278 420.736 362.87 420.736ZM384.087 429.544C384.087 431.672 383.543 433.32 382.455 434.488C381.383 435.656 379.927 436.24 378.087 436.24C376.951 436.24 375.935 435.984 375.039 435.472C374.159 434.944 373.463 434.184 372.951 433.192C372.439 432.184 372.183 430.968 372.183 429.544C372.183 427.416 372.711 425.776 373.767 424.624C374.839 423.472 376.303 422.896 378.159 422.896C379.311 422.896 380.335 423.16 381.231 423.688C382.127 424.2 382.823 424.952 383.319 425.944C383.831 426.92 384.087 428.12 384.087 429.544ZM374.367 429.544C374.367 431.064 374.663 432.272 375.255 433.168C375.863 434.048 376.823 434.488 378.135 434.488C379.431 434.488 380.383 434.048 380.991 433.168C381.599 432.272 381.903 431.064 381.903 429.544C381.903 428.024 381.599 426.832 380.991 425.968C380.383 425.104 379.423 424.672 378.111 424.672C376.799 424.672 375.847 425.104 375.255 425.968C374.663 426.832 374.367 428.024 374.367 429.544ZM398.187 423.136V436H396.459L396.147 434.296H396.051C395.635 434.968 395.059 435.464 394.323 435.784C393.587 436.088 392.803 436.24 391.971 436.24C390.419 436.24 389.251 435.872 388.467 435.136C387.683 434.384 387.291 433.192 387.291 431.56V423.136H389.427V431.416C389.427 433.464 390.379 434.488 392.283 434.488C393.707 434.488 394.691 434.088 395.235 433.288C395.795 432.488 396.075 431.336 396.075 429.832V423.136H398.187ZM406.566 434.512C406.886 434.512 407.214 434.488 407.55 434.44C407.886 434.392 408.158 434.328 408.366 434.248V435.856C408.142 435.968 407.822 436.056 407.406 436.12C406.99 436.2 406.59 436.24 406.206 436.24C405.534 436.24 404.91 436.128 404.334 435.904C403.774 435.664 403.318 435.256 402.966 434.68C402.614 434.104 402.438 433.296 402.438 432.256V424.768H400.614V423.76L402.462 422.92L403.302 420.184H404.55V423.136H408.27V424.768H404.55V432.208C404.55 432.992 404.734 433.576 405.102 433.96C405.486 434.328 405.974 434.512 406.566 434.512ZM415.91 422.896C416.998 422.896 417.942 423.136 418.742 423.616C419.542 424.096 420.15 424.776 420.566 425.656C420.998 426.52 421.214 427.536 421.214 428.704V429.976H412.406C412.438 431.432 412.806 432.544 413.51 433.312C414.23 434.064 415.23 434.44 416.51 434.44C417.326 434.44 418.046 434.368 418.67 434.224C419.31 434.064 419.966 433.84 420.638 433.552V435.4C419.982 435.688 419.334 435.896 418.694 436.024C418.054 436.168 417.294 436.24 416.414 436.24C415.182 436.24 414.102 435.992 413.174 435.496C412.246 435 411.518 434.264 410.99 433.288C410.478 432.312 410.222 431.104 410.222 429.664C410.222 428.256 410.454 427.048 410.918 426.04C411.398 425.032 412.062 424.256 412.91 423.712C413.774 423.168 414.774 422.896 415.91 422.896ZM415.886 424.624C414.878 424.624 414.078 424.952 413.486 425.608C412.91 426.248 412.566 427.144 412.454 428.296H419.006C418.99 427.208 418.734 426.328 418.238 425.656C417.742 424.968 416.958 424.624 415.886 424.624ZM430.489 422.896C430.729 422.896 430.985 422.912 431.257 422.944C431.529 422.96 431.777 422.992 432.001 423.04L431.737 424.984C431.529 424.936 431.297 424.896 431.041 424.864C430.785 424.832 430.553 424.816 430.345 424.816C429.689 424.816 429.073 425 428.497 425.368C427.921 425.72 427.457 426.224 427.105 426.88C426.769 427.52 426.601 428.272 426.601 429.136V436H424.489V423.136H426.217L426.457 425.488H426.553C426.953 424.784 427.481 424.176 428.137 423.664C428.809 423.152 429.593 422.896 430.489 422.896ZM281.117 451.864C280.893 452.792 280.661 453.8 280.421 454.888C280.197 455.96 280.029 456.912 279.917 457.744H277.637L277.469 457.48C277.677 456.648 277.973 455.72 278.357 454.696C278.757 453.672 279.157 452.728 279.557 451.864H281.117ZM276.677 451.864C276.453 452.792 276.229 453.8 276.005 454.888C275.781 455.96 275.605 456.912 275.477 457.744H273.221L273.077 457.48C273.301 456.648 273.597 455.72 273.965 454.696C274.349 453.672 274.741 452.728 275.141 451.864H276.677ZM290.102 451.864L283.718 469H281.654L288.038 451.864H290.102ZM294.496 450.76V456.112C294.496 456.752 294.456 457.352 294.376 457.912H294.52C294.936 457.256 295.496 456.76 296.2 456.424C296.92 456.088 297.696 455.92 298.528 455.92C300.08 455.92 301.248 456.296 302.032 457.048C302.832 457.784 303.232 458.976 303.232 460.624V469H301.144V460.768C301.144 458.704 300.184 457.672 298.264 457.672C296.824 457.672 295.832 458.08 295.288 458.896C294.76 459.696 294.496 460.848 294.496 462.352V469H292.384V450.76H294.496ZM318.404 462.544C318.404 464.672 317.86 466.32 316.772 467.488C315.7 468.656 314.244 469.24 312.404 469.24C311.268 469.24 310.252 468.984 309.356 468.472C308.476 467.944 307.78 467.184 307.268 466.192C306.756 465.184 306.5 463.968 306.5 462.544C306.5 460.416 307.028 458.776 308.084 457.624C309.156 456.472 310.62 455.896 312.476 455.896C313.628 455.896 314.652 456.16 315.548 456.688C316.444 457.2 317.14 457.952 317.636 458.944C318.148 459.92 318.404 461.12 318.404 462.544ZM308.684 462.544C308.684 464.064 308.98 465.272 309.572 466.168C310.18 467.048 311.14 467.488 312.452 467.488C313.748 467.488 314.7 467.048 315.308 466.168C315.916 465.272 316.22 464.064 316.22 462.544C316.22 461.024 315.916 459.832 315.308 458.968C314.7 458.104 313.74 457.672 312.428 457.672C311.116 457.672 310.164 458.104 309.572 458.968C308.98 459.832 308.684 461.024 308.684 462.544ZM335.863 455.896C337.319 455.896 338.407 456.272 339.127 457.024C339.847 457.776 340.207 458.976 340.207 460.624V469H338.119V460.72C338.119 458.688 337.247 457.672 335.503 457.672C334.255 457.672 333.359 458.032 332.815 458.752C332.287 459.472 332.023 460.52 332.023 461.896V469H329.935V460.72C329.935 458.688 329.055 457.672 327.295 457.672C325.999 457.672 325.103 458.072 324.607 458.872C324.111 459.672 323.863 460.824 323.863 462.328V469H321.751V456.136H323.455L323.767 457.888H323.887C324.287 457.216 324.823 456.72 325.495 456.4C326.183 456.064 326.911 455.896 327.679 455.896C329.695 455.896 331.007 456.616 331.615 458.056H331.735C332.167 457.32 332.751 456.776 333.487 456.424C334.223 456.072 335.015 455.896 335.863 455.896ZM349.149 455.896C350.237 455.896 351.181 456.136 351.981 456.616C352.781 457.096 353.389 457.776 353.805 458.656C354.237 459.52 354.453 460.536 354.453 461.704V462.976H345.645C345.677 464.432 346.045 465.544 346.749 466.312C347.469 467.064 348.469 467.44 349.749 467.44C350.565 467.44 351.285 467.368 351.909 467.224C352.549 467.064 353.205 466.84 353.877 466.552V468.4C353.221 468.688 352.573 468.896 351.933 469.024C351.293 469.168 350.533 469.24 349.653 469.24C348.421 469.24 347.341 468.992 346.413 468.496C345.485 468 344.757 467.264 344.229 466.288C343.717 465.312 343.461 464.104 343.461 462.664C343.461 461.256 343.693 460.048 344.157 459.04C344.637 458.032 345.301 457.256 346.149 456.712C347.013 456.168 348.013 455.896 349.149 455.896ZM349.125 457.624C348.117 457.624 347.317 457.952 346.725 458.608C346.149 459.248 345.805 460.144 345.693 461.296H352.245C352.229 460.208 351.973 459.328 351.477 458.656C350.981 457.968 350.197 457.624 349.125 457.624ZM363.613 451.864L363.781 452.128C363.573 452.976 363.269 453.912 362.869 454.936C362.485 455.96 362.101 456.896 361.717 457.744H360.133C360.277 457.152 360.429 456.504 360.589 455.8C360.749 455.096 360.893 454.408 361.021 453.736C361.165 453.048 361.277 452.424 361.357 451.864H363.613ZM359.173 451.864L359.341 452.128C359.117 452.976 358.813 453.912 358.429 454.936C358.045 455.96 357.661 456.896 357.277 457.744H355.741C355.901 457.152 356.053 456.504 356.197 455.8C356.341 455.096 356.477 454.408 356.605 453.736C356.733 453.048 356.837 452.424 356.917 451.864H359.173ZM371.513 464.344L380.561 460.576L371.513 456.28V454.408L382.817 460.048V461.248L371.513 466.216V464.344ZM398.609 451.864C398.385 452.792 398.153 453.8 397.913 454.888C397.689 455.96 397.521 456.912 397.409 457.744H395.129L394.961 457.48C395.169 456.648 395.465 455.72 395.849 454.696C396.249 453.672 396.649 452.728 397.049 451.864H398.609ZM394.169 451.864C393.945 452.792 393.721 453.8 393.497 454.888C393.273 455.96 393.097 456.912 392.969 457.744H390.713L390.569 457.48C390.793 456.648 391.089 455.72 391.457 454.696C391.841 453.672 392.233 452.728 392.633 451.864H394.169ZM407.594 451.864L401.21 469H399.146L405.53 451.864H407.594ZM414.748 455.92C416.316 455.92 417.476 456.264 418.228 456.952C418.98 457.64 419.356 458.736 419.356 460.24V469H417.82L417.412 467.176H417.316C416.756 467.88 416.164 468.4 415.54 468.736C414.916 469.072 414.068 469.24 412.996 469.24C411.828 469.24 410.86 468.936 410.092 468.328C409.324 467.704 408.94 466.736 408.94 465.424C408.94 464.144 409.444 463.16 410.452 462.472C411.46 461.768 413.012 461.384 415.108 461.32L417.292 461.248V460.48C417.292 459.408 417.06 458.664 416.596 458.248C416.132 457.832 415.476 457.624 414.628 457.624C413.956 457.624 413.316 457.728 412.708 457.936C412.1 458.128 411.532 458.352 411.004 458.608L410.356 457.024C410.916 456.72 411.58 456.464 412.348 456.256C413.116 456.032 413.916 455.92 414.748 455.92ZM417.268 462.712L415.372 462.784C413.772 462.848 412.66 463.104 412.036 463.552C411.428 464 411.124 464.632 411.124 465.448C411.124 466.168 411.34 466.696 411.772 467.032C412.22 467.368 412.788 467.536 413.476 467.536C414.548 467.536 415.444 467.24 416.164 466.648C416.9 466.04 417.268 465.112 417.268 463.864V462.712ZM429.449 455.896C431.033 455.896 432.305 456.448 433.265 457.552C434.241 458.656 434.729 460.32 434.729 462.544C434.729 464.736 434.241 466.4 433.265 467.536C432.305 468.672 431.025 469.24 429.425 469.24C428.433 469.24 427.609 469.056 426.953 468.688C426.313 468.32 425.809 467.88 425.441 467.368H425.297C425.313 467.64 425.337 467.984 425.369 468.4C425.417 468.816 425.441 469.176 425.441 469.48V474.76H423.329V456.136H425.057L425.345 457.888H425.441C425.825 457.328 426.329 456.856 426.953 456.472C427.577 456.088 428.409 455.896 429.449 455.896ZM429.065 457.672C427.753 457.672 426.825 458.04 426.281 458.776C425.737 459.512 425.457 460.632 425.441 462.136V462.544C425.441 464.128 425.697 465.352 426.209 466.216C426.737 467.064 427.705 467.488 429.113 467.488C429.881 467.488 430.521 467.28 431.033 466.864C431.545 466.432 431.921 465.84 432.161 465.088C432.417 464.336 432.545 463.48 432.545 462.52C432.545 461.048 432.257 459.872 431.681 458.992C431.121 458.112 430.249 457.672 429.065 457.672ZM444.215 455.896C445.799 455.896 447.071 456.448 448.031 457.552C449.007 458.656 449.495 460.32 449.495 462.544C449.495 464.736 449.007 466.4 448.031 467.536C447.071 468.672 445.791 469.24 444.191 469.24C443.199 469.24 442.375 469.056 441.719 468.688C441.079 468.32 440.575 467.88 440.207 467.368H440.063C440.079 467.64 440.103 467.984 440.135 468.4C440.183 468.816 440.207 469.176 440.207 469.48V474.76H438.095V456.136H439.823L440.111 457.888H440.207C440.591 457.328 441.095 456.856 441.719 456.472C442.343 456.088 443.175 455.896 444.215 455.896ZM443.831 457.672C442.519 457.672 441.591 458.04 441.047 458.776C440.503 459.512 440.223 460.632 440.207 462.136V462.544C440.207 464.128 440.463 465.352 440.975 466.216C441.503 467.064 442.471 467.488 443.879 467.488C444.647 467.488 445.287 467.28 445.799 466.864C446.311 466.432 446.687 465.84 446.927 465.088C447.183 464.336 447.311 463.48 447.311 462.52C447.311 461.048 447.023 459.872 446.447 458.992C445.887 458.112 445.015 457.672 443.831 457.672ZM458.746 451.864L458.914 452.128C458.706 452.976 458.402 453.912 458.002 454.936C457.618 455.96 457.234 456.896 456.85 457.744H455.266C455.41 457.152 455.562 456.504 455.722 455.8C455.882 455.096 456.026 454.408 456.154 453.736C456.298 453.048 456.41 452.424 456.49 451.864H458.746ZM454.306 451.864L454.474 452.128C454.25 452.976 453.946 453.912 453.562 454.936C453.178 455.96 452.794 456.896 452.41 457.744H450.874C451.034 457.152 451.186 456.504 451.33 455.8C451.474 455.096 451.61 454.408 451.738 453.736C451.866 453.048 451.97 452.424 452.05 451.864H454.306Z" fill="black"/>
+<path d="M356 205C354.895 205 354 205.895 354 207C354 208.105 354.895 209 356 209V205ZM759.5 207H761.5V205H759.5V207ZM759.5 588V590H761.5V588H759.5ZM645.586 586.586C644.805 587.367 644.805 588.633 645.586 589.414L658.314 602.142C659.095 602.923 660.361 602.923 661.142 602.142C661.923 601.361 661.923 600.095 661.142 599.314L649.828 588L661.142 576.686C661.923 575.905 661.923 574.639 661.142 573.858C660.361 573.077 659.095 573.077 658.314 573.858L645.586 586.586ZM356 209H759.5V205H356V209ZM757.5 207V588H761.5V207H757.5ZM759.5 586H647V590H759.5V586Z" fill="#232629"/>
+<path d="M784.915 413H782.371L776.299 404.816L774.547 406.352V413H772.387V395.864H774.547V404.312C775.027 403.768 775.515 403.224 776.011 402.68C776.507 402.136 777.003 401.592 777.499 401.048L782.131 395.864H784.651L777.859 403.328L784.915 413ZM788.038 395.312C788.358 395.312 788.638 395.424 788.878 395.648C789.134 395.856 789.262 396.192 789.262 396.656C789.262 397.12 789.134 397.464 788.878 397.688C788.638 397.896 788.358 398 788.038 398C787.686 398 787.39 397.896 787.15 397.688C786.91 397.464 786.79 397.12 786.79 396.656C786.79 396.192 786.91 395.856 787.15 395.648C787.39 395.424 787.686 395.312 788.038 395.312ZM789.07 400.136V413H786.958V400.136H789.07ZM799.145 399.896C799.385 399.896 799.641 399.912 799.913 399.944C800.185 399.96 800.433 399.992 800.657 400.04L800.393 401.984C800.185 401.936 799.953 401.896 799.697 401.864C799.441 401.832 799.209 401.816 799.001 401.816C798.345 401.816 797.729 402 797.153 402.368C796.577 402.72 796.113 403.224 795.761 403.88C795.425 404.52 795.257 405.272 795.257 406.136V413H793.145V400.136H794.873L795.113 402.488H795.209C795.609 401.784 796.137 401.176 796.793 400.664C797.465 400.152 798.249 399.896 799.145 399.896ZM804.14 395.312C804.46 395.312 804.74 395.424 804.98 395.648C805.236 395.856 805.364 396.192 805.364 396.656C805.364 397.12 805.236 397.464 804.98 397.688C804.74 397.896 804.46 398 804.14 398C803.788 398 803.492 397.896 803.252 397.688C803.012 397.464 802.892 397.12 802.892 396.656C802.892 396.192 803.012 395.856 803.252 395.648C803.492 395.424 803.788 395.312 804.14 395.312ZM805.172 400.136V413H803.06V400.136H805.172ZM813.807 399.896C814.655 399.896 815.415 400.056 816.087 400.376C816.775 400.696 817.359 401.184 817.839 401.84H817.959L818.247 400.136H819.927V413.216C819.927 415.056 819.455 416.44 818.511 417.368C817.583 418.296 816.135 418.76 814.167 418.76C812.279 418.76 810.735 418.488 809.535 417.944V416C810.799 416.672 812.383 417.008 814.287 417.008C815.391 417.008 816.255 416.68 816.879 416.024C817.519 415.384 817.839 414.504 817.839 413.384V412.88C817.839 412.688 817.847 412.416 817.863 412.064C817.879 411.696 817.895 411.44 817.911 411.296H817.815C816.951 412.592 815.623 413.24 813.831 413.24C812.167 413.24 810.863 412.656 809.919 411.488C808.991 410.32 808.527 408.688 808.527 406.592C808.527 404.544 808.991 402.92 809.919 401.72C810.863 400.504 812.159 399.896 813.807 399.896ZM814.095 401.672C813.023 401.672 812.191 402.104 811.599 402.968C811.007 403.816 810.711 405.032 810.711 406.616C810.711 408.2 810.999 409.416 811.575 410.264C812.151 411.096 813.007 411.512 814.143 411.512C815.439 411.512 816.383 411.168 816.975 410.48C817.567 409.776 817.863 408.648 817.863 407.096V406.592C817.863 404.848 817.559 403.592 816.951 402.824C816.343 402.056 815.391 401.672 814.095 401.672ZM828.885 399.92C830.453 399.92 831.613 400.264 832.365 400.952C833.117 401.64 833.493 402.736 833.493 404.24V413H831.957L831.549 411.176H831.453C830.893 411.88 830.301 412.4 829.677 412.736C829.053 413.072 828.205 413.24 827.133 413.24C825.965 413.24 824.997 412.936 824.229 412.328C823.461 411.704 823.077 410.736 823.077 409.424C823.077 408.144 823.581 407.16 824.589 406.472C825.597 405.768 827.149 405.384 829.245 405.32L831.429 405.248V404.48C831.429 403.408 831.197 402.664 830.733 402.248C830.269 401.832 829.613 401.624 828.765 401.624C828.093 401.624 827.453 401.728 826.845 401.936C826.237 402.128 825.669 402.352 825.141 402.608L824.493 401.024C825.053 400.72 825.717 400.464 826.485 400.256C827.253 400.032 828.053 399.92 828.885 399.92ZM831.405 406.712L829.509 406.784C827.909 406.848 826.797 407.104 826.173 407.552C825.565 408 825.261 408.632 825.261 409.448C825.261 410.168 825.477 410.696 825.909 411.032C826.357 411.368 826.925 411.536 827.613 411.536C828.685 411.536 829.581 411.24 830.301 410.648C831.037 410.04 831.405 409.112 831.405 407.864V406.712ZM851.578 399.896C853.034 399.896 854.122 400.272 854.842 401.024C855.562 401.776 855.922 402.976 855.922 404.624V413H853.834V404.72C853.834 402.688 852.962 401.672 851.218 401.672C849.97 401.672 849.074 402.032 848.53 402.752C848.002 403.472 847.738 404.52 847.738 405.896V413H845.65V404.72C845.65 402.688 844.77 401.672 843.01 401.672C841.714 401.672 840.818 402.072 840.322 402.872C839.826 403.672 839.578 404.824 839.578 406.328V413H837.466V400.136H839.17L839.482 401.888H839.602C840.002 401.216 840.538 400.72 841.21 400.4C841.898 400.064 842.626 399.896 843.394 399.896C845.41 399.896 846.722 400.616 847.33 402.056H847.45C847.882 401.32 848.466 400.776 849.202 400.424C849.938 400.072 850.73 399.896 851.578 399.896ZM860.975 395.312C861.295 395.312 861.575 395.424 861.815 395.648C862.071 395.856 862.199 396.192 862.199 396.656C862.199 397.12 862.071 397.464 861.815 397.688C861.575 397.896 861.295 398 860.975 398C860.623 398 860.327 397.896 860.087 397.688C859.847 397.464 859.727 397.12 859.727 396.656C859.727 396.192 859.847 395.856 860.087 395.648C860.327 395.424 860.623 395.312 860.975 395.312ZM862.007 400.136V413H859.895V400.136H862.007ZM865.771 411.704C865.771 411.112 865.915 410.696 866.203 410.456C866.491 410.216 866.835 410.096 867.235 410.096C867.635 410.096 867.987 410.216 868.291 410.456C868.595 410.696 868.747 411.112 868.747 411.704C868.747 412.28 868.595 412.696 868.291 412.952C867.987 413.208 867.635 413.336 867.235 413.336C866.835 413.336 866.491 413.208 866.203 412.952C865.915 412.696 865.771 412.28 865.771 411.704ZM877.329 395.864C879.569 395.864 881.201 396.304 882.225 397.184C883.249 398.064 883.761 399.304 883.761 400.904C883.761 401.848 883.545 402.736 883.113 403.568C882.697 404.384 881.985 405.048 880.977 405.56C879.985 406.072 878.633 406.328 876.921 406.328H874.953V413H872.793V395.864H877.329ZM877.137 397.712H874.953V404.48H876.681C878.313 404.48 879.529 404.216 880.329 403.688C881.129 403.16 881.529 402.264 881.529 401C881.529 399.896 881.169 399.072 880.449 398.528C879.745 397.984 878.641 397.712 877.137 397.712ZM891.908 399.92C893.476 399.92 894.636 400.264 895.388 400.952C896.14 401.64 896.516 402.736 896.516 404.24V413H894.98L894.572 411.176H894.476C893.916 411.88 893.324 412.4 892.7 412.736C892.076 413.072 891.228 413.24 890.156 413.24C888.988 413.24 888.02 412.936 887.252 412.328C886.484 411.704 886.1 410.736 886.1 409.424C886.1 408.144 886.604 407.16 887.612 406.472C888.62 405.768 890.172 405.384 892.268 405.32L894.452 405.248V404.48C894.452 403.408 894.22 402.664 893.756 402.248C893.292 401.832 892.636 401.624 891.788 401.624C891.116 401.624 890.476 401.728 889.868 401.936C889.26 402.128 888.692 402.352 888.164 402.608L887.516 401.024C888.076 400.72 888.74 400.464 889.508 400.256C890.276 400.032 891.076 399.92 891.908 399.92ZM894.428 406.712L892.532 406.784C890.932 406.848 889.82 407.104 889.196 407.552C888.588 408 888.284 408.632 888.284 409.448C888.284 410.168 888.5 410.696 888.932 411.032C889.38 411.368 889.948 411.536 890.636 411.536C891.708 411.536 892.604 411.24 893.324 410.648C894.06 410.04 894.428 409.112 894.428 407.864V406.712ZM905.049 399.896C905.897 399.896 906.657 400.056 907.329 400.376C908.017 400.696 908.601 401.184 909.081 401.84H909.201L909.489 400.136H911.169V413.216C911.169 415.056 910.697 416.44 909.753 417.368C908.825 418.296 907.377 418.76 905.409 418.76C903.521 418.76 901.977 418.488 900.777 417.944V416C902.041 416.672 903.625 417.008 905.529 417.008C906.633 417.008 907.497 416.68 908.121 416.024C908.761 415.384 909.081 414.504 909.081 413.384V412.88C909.081 412.688 909.089 412.416 909.105 412.064C909.121 411.696 909.137 411.44 909.153 411.296H909.057C908.193 412.592 906.865 413.24 905.073 413.24C903.409 413.24 902.105 412.656 901.161 411.488C900.233 410.32 899.769 408.688 899.769 406.592C899.769 404.544 900.233 402.92 901.161 401.72C902.105 400.504 903.401 399.896 905.049 399.896ZM905.337 401.672C904.265 401.672 903.433 402.104 902.841 402.968C902.249 403.816 901.953 405.032 901.953 406.616C901.953 408.2 902.241 409.416 902.817 410.264C903.393 411.096 904.249 411.512 905.385 411.512C906.681 411.512 907.625 411.168 908.217 410.48C908.809 409.776 909.105 408.648 909.105 407.096V406.592C909.105 404.848 908.801 403.592 908.193 402.824C907.585 402.056 906.633 401.672 905.337 401.672ZM920.223 399.896C921.311 399.896 922.255 400.136 923.055 400.616C923.855 401.096 924.463 401.776 924.879 402.656C925.311 403.52 925.527 404.536 925.527 405.704V406.976H916.719C916.751 408.432 917.119 409.544 917.823 410.312C918.543 411.064 919.543 411.44 920.823 411.44C921.639 411.44 922.359 411.368 922.983 411.224C923.623 411.064 924.279 410.84 924.951 410.552V412.4C924.295 412.688 923.647 412.896 923.007 413.024C922.367 413.168 921.607 413.24 920.727 413.24C919.495 413.24 918.415 412.992 917.487 412.496C916.559 412 915.831 411.264 915.303 410.288C914.791 409.312 914.535 408.104 914.535 406.664C914.535 405.256 914.767 404.048 915.231 403.04C915.711 402.032 916.375 401.256 917.223 400.712C918.087 400.168 919.087 399.896 920.223 399.896ZM920.199 401.624C919.191 401.624 918.391 401.952 917.799 402.608C917.223 403.248 916.879 404.144 916.767 405.296H923.319C923.303 404.208 923.047 403.328 922.551 402.656C922.055 401.968 921.271 401.624 920.199 401.624ZM933.818 395.864C935.946 395.864 937.514 396.272 938.522 397.088C939.546 397.888 940.058 399.104 940.058 400.736C940.058 401.648 939.89 402.408 939.554 403.016C939.218 403.624 938.786 404.112 938.258 404.48C937.746 404.848 937.202 405.128 936.626 405.32L941.33 413H938.81L934.658 405.92H931.25V413H929.09V395.864H933.818ZM933.698 397.736H931.25V404.096H933.818C935.21 404.096 936.226 403.824 936.866 403.28C937.506 402.72 937.826 401.904 937.826 400.832C937.826 399.712 937.49 398.92 936.818 398.456C936.146 397.976 935.106 397.736 933.698 397.736ZM954.915 406.544C954.915 408.672 954.371 410.32 953.283 411.488C952.211 412.656 950.755 413.24 948.915 413.24C947.779 413.24 946.763 412.984 945.867 412.472C944.987 411.944 944.291 411.184 943.779 410.192C943.267 409.184 943.011 407.968 943.011 406.544C943.011 404.416 943.539 402.776 944.595 401.624C945.667 400.472 947.131 399.896 948.987 399.896C950.139 399.896 951.163 400.16 952.059 400.688C952.955 401.2 953.651 401.952 954.147 402.944C954.659 403.92 954.915 405.12 954.915 406.544ZM945.195 406.544C945.195 408.064 945.491 409.272 946.083 410.168C946.691 411.048 947.651 411.488 948.963 411.488C950.259 411.488 951.211 411.048 951.819 410.168C952.427 409.272 952.731 408.064 952.731 406.544C952.731 405.024 952.427 403.832 951.819 402.968C951.211 402.104 950.251 401.672 948.939 401.672C947.627 401.672 946.675 402.104 946.083 402.968C945.491 403.832 945.195 405.024 945.195 406.544ZM969.015 400.136V413H967.287L966.975 411.296H966.879C966.463 411.968 965.887 412.464 965.151 412.784C964.415 413.088 963.631 413.24 962.799 413.24C961.247 413.24 960.079 412.872 959.295 412.136C958.511 411.384 958.119 410.192 958.119 408.56V400.136H960.255V408.416C960.255 410.464 961.207 411.488 963.111 411.488C964.535 411.488 965.519 411.088 966.063 410.288C966.623 409.488 966.903 408.336 966.903 406.832V400.136H969.015ZM977.395 411.512C977.715 411.512 978.043 411.488 978.379 411.44C978.715 411.392 978.987 411.328 979.195 411.248V412.856C978.971 412.968 978.651 413.056 978.235 413.12C977.819 413.2 977.419 413.24 977.035 413.24C976.363 413.24 975.739 413.128 975.163 412.904C974.603 412.664 974.147 412.256 973.795 411.68C973.443 411.104 973.267 410.296 973.267 409.256V401.768H971.443V400.76L973.291 399.92L974.131 397.184H975.379V400.136H979.099V401.768H975.379V409.208C975.379 409.992 975.563 410.576 975.931 410.96C976.315 411.328 976.803 411.512 977.395 411.512ZM986.738 399.896C987.826 399.896 988.77 400.136 989.57 400.616C990.37 401.096 990.978 401.776 991.394 402.656C991.826 403.52 992.042 404.536 992.042 405.704V406.976H983.234C983.266 408.432 983.634 409.544 984.338 410.312C985.058 411.064 986.058 411.44 987.338 411.44C988.154 411.44 988.874 411.368 989.498 411.224C990.138 411.064 990.794 410.84 991.466 410.552V412.4C990.81 412.688 990.162 412.896 989.522 413.024C988.882 413.168 988.122 413.24 987.242 413.24C986.01 413.24 984.93 412.992 984.002 412.496C983.074 412 982.346 411.264 981.818 410.288C981.306 409.312 981.05 408.104 981.05 406.664C981.05 405.256 981.282 404.048 981.746 403.04C982.226 402.032 982.89 401.256 983.738 400.712C984.602 400.168 985.602 399.896 986.738 399.896ZM986.714 401.624C985.706 401.624 984.906 401.952 984.314 402.608C983.738 403.248 983.394 404.144 983.282 405.296H989.834C989.818 404.208 989.562 403.328 989.066 402.656C988.57 401.968 987.786 401.624 986.714 401.624ZM1001.32 399.896C1001.56 399.896 1001.81 399.912 1002.09 399.944C1002.36 399.96 1002.61 399.992 1002.83 400.04L1002.57 401.984C1002.36 401.936 1002.13 401.896 1001.87 401.864C1001.61 401.832 1001.38 401.816 1001.17 401.816C1000.52 401.816 999.901 402 999.325 402.368C998.749 402.72 998.285 403.224 997.933 403.88C997.597 404.52 997.429 405.272 997.429 406.136V413H995.317V400.136H997.045L997.285 402.488H997.381C997.781 401.784 998.309 401.176 998.965 400.664C999.637 400.152 1000.42 399.896 1001.32 399.896ZM1003.49 411.704C1003.49 411.112 1003.63 410.696 1003.92 410.456C1004.21 410.216 1004.55 410.096 1004.95 410.096C1005.35 410.096 1005.71 410.216 1006.01 410.456C1006.31 410.696 1006.47 411.112 1006.47 411.704C1006.47 412.28 1006.31 412.696 1006.01 412.952C1005.71 413.208 1005.35 413.336 1004.95 413.336C1004.55 413.336 1004.21 413.208 1003.92 412.952C1003.63 412.696 1003.49 412.28 1003.49 411.704ZM1016.34 399.896C1017.93 399.896 1019.2 400.448 1020.16 401.552C1021.14 402.656 1021.62 404.32 1021.62 406.544C1021.62 408.736 1021.14 410.4 1020.16 411.536C1019.2 412.672 1017.92 413.24 1016.32 413.24C1015.33 413.24 1014.5 413.056 1013.85 412.688C1013.21 412.32 1012.7 411.88 1012.34 411.368H1012.19C1012.21 411.64 1012.23 411.984 1012.26 412.4C1012.31 412.816 1012.34 413.176 1012.34 413.48V418.76H1010.22V400.136H1011.95L1012.24 401.888H1012.34C1012.72 401.328 1013.22 400.856 1013.85 400.472C1014.47 400.088 1015.3 399.896 1016.34 399.896ZM1015.96 401.672C1014.65 401.672 1013.72 402.04 1013.18 402.776C1012.63 403.512 1012.35 404.632 1012.34 406.136V406.544C1012.34 408.128 1012.59 409.352 1013.1 410.216C1013.63 411.064 1014.6 411.488 1016.01 411.488C1016.78 411.488 1017.42 411.28 1017.93 410.864C1018.44 410.432 1018.82 409.84 1019.06 409.088C1019.31 408.336 1019.44 407.48 1019.44 406.52C1019.44 405.048 1019.15 403.872 1018.58 402.992C1018.02 402.112 1017.14 401.672 1015.96 401.672ZM1035.74 400.136V413H1034.01L1033.7 411.296H1033.61C1033.19 411.968 1032.61 412.464 1031.88 412.784C1031.14 413.088 1030.36 413.24 1029.53 413.24C1027.97 413.24 1026.81 412.872 1026.02 412.136C1025.24 411.384 1024.85 410.192 1024.85 408.56V400.136H1026.98V408.416C1026.98 410.464 1027.93 411.488 1029.84 411.488C1031.26 411.488 1032.25 411.088 1032.79 410.288C1033.35 409.488 1033.63 408.336 1033.63 406.832V400.136H1035.74ZM1048.2 409.448C1048.2 410.696 1047.74 411.64 1046.81 412.28C1045.88 412.92 1044.63 413.24 1043.07 413.24C1042.17 413.24 1041.39 413.168 1040.74 413.024C1040.1 412.88 1039.53 412.68 1039.03 412.424V410.504C1039.55 410.76 1040.16 411 1040.88 411.224C1041.62 411.432 1042.36 411.536 1043.11 411.536C1044.19 411.536 1044.96 411.368 1045.44 411.032C1045.92 410.68 1046.16 410.216 1046.16 409.64C1046.16 409.32 1046.07 409.032 1045.9 408.776C1045.72 408.52 1045.4 408.264 1044.94 408.008C1044.49 407.752 1043.84 407.464 1042.99 407.144C1042.16 406.824 1041.45 406.504 1040.86 406.184C1040.27 405.864 1039.81 405.48 1039.49 405.032C1039.17 404.584 1039.01 404.008 1039.01 403.304C1039.01 402.216 1039.45 401.376 1040.33 400.784C1041.23 400.192 1042.39 399.896 1043.83 399.896C1044.62 399.896 1045.35 399.976 1046.02 400.136C1046.71 400.296 1047.35 400.504 1047.94 400.76L1047.22 402.44C1046.67 402.216 1046.11 402.024 1045.51 401.864C1044.92 401.704 1044.31 401.624 1043.69 401.624C1042.83 401.624 1042.16 401.768 1041.7 402.056C1041.25 402.328 1041.03 402.704 1041.03 403.184C1041.03 403.552 1041.13 403.856 1041.34 404.096C1041.55 404.336 1041.89 404.576 1042.37 404.816C1042.87 405.056 1043.52 405.328 1044.34 405.632C1045.15 405.936 1045.85 406.248 1046.43 406.568C1047 406.888 1047.44 407.28 1047.75 407.744C1048.05 408.192 1048.2 408.76 1048.2 409.448ZM1053.42 394.76V400.112C1053.42 400.752 1053.38 401.352 1053.3 401.912H1053.45C1053.86 401.256 1054.42 400.76 1055.13 400.424C1055.85 400.088 1056.62 399.92 1057.45 399.92C1059.01 399.92 1060.17 400.296 1060.96 401.048C1061.76 401.784 1062.16 402.976 1062.16 404.624V413H1060.07V404.768C1060.07 402.704 1059.11 401.672 1057.19 401.672C1055.75 401.672 1054.76 402.08 1054.21 402.896C1053.69 403.696 1053.42 404.848 1053.42 406.352V413H1051.31V394.76H1053.42ZM1071.16 395.864C1073.29 395.864 1074.86 396.272 1075.87 397.088C1076.89 397.888 1077.4 399.104 1077.4 400.736C1077.4 401.648 1077.23 402.408 1076.9 403.016C1076.56 403.624 1076.13 404.112 1075.6 404.48C1075.09 404.848 1074.55 405.128 1073.97 405.32L1078.67 413H1076.15L1072 405.92H1068.59V413H1066.43V395.864H1071.16ZM1071.04 397.736H1068.59V404.096H1071.16C1072.55 404.096 1073.57 403.824 1074.21 403.28C1074.85 402.72 1075.17 401.904 1075.17 400.832C1075.17 399.712 1074.83 398.92 1074.16 398.456C1073.49 397.976 1072.45 397.736 1071.04 397.736ZM1092.26 406.544C1092.26 408.672 1091.72 410.32 1090.63 411.488C1089.56 412.656 1088.1 413.24 1086.26 413.24C1085.12 413.24 1084.11 412.984 1083.21 412.472C1082.33 411.944 1081.64 411.184 1081.12 410.192C1080.61 409.184 1080.36 407.968 1080.36 406.544C1080.36 404.416 1080.88 402.776 1081.94 401.624C1083.01 400.472 1084.48 399.896 1086.33 399.896C1087.48 399.896 1088.51 400.16 1089.4 400.688C1090.3 401.2 1091 401.952 1091.49 402.944C1092 403.92 1092.26 405.12 1092.26 406.544ZM1082.54 406.544C1082.54 408.064 1082.84 409.272 1083.43 410.168C1084.04 411.048 1085 411.488 1086.31 411.488C1087.6 411.488 1088.56 411.048 1089.16 410.168C1089.77 409.272 1090.08 408.064 1090.08 406.544C1090.08 405.024 1089.77 403.832 1089.16 402.968C1088.56 402.104 1087.6 401.672 1086.28 401.672C1084.97 401.672 1084.02 402.104 1083.43 402.968C1082.84 403.832 1082.54 405.024 1082.54 406.544ZM1106.36 400.136V413H1104.63L1104.32 411.296H1104.22C1103.81 411.968 1103.23 412.464 1102.49 412.784C1101.76 413.088 1100.97 413.24 1100.14 413.24C1098.59 413.24 1097.42 412.872 1096.64 412.136C1095.85 411.384 1095.46 410.192 1095.46 408.56V400.136H1097.6V408.416C1097.6 410.464 1098.55 411.488 1100.45 411.488C1101.88 411.488 1102.86 411.088 1103.41 410.288C1103.97 409.488 1104.25 408.336 1104.25 406.832V400.136H1106.36ZM1114.74 411.512C1115.06 411.512 1115.39 411.488 1115.72 411.44C1116.06 411.392 1116.33 411.328 1116.54 411.248V412.856C1116.31 412.968 1115.99 413.056 1115.58 413.12C1115.16 413.2 1114.76 413.24 1114.38 413.24C1113.71 413.24 1113.08 413.128 1112.51 412.904C1111.95 412.664 1111.49 412.256 1111.14 411.68C1110.79 411.104 1110.61 410.296 1110.61 409.256V401.768H1108.79V400.76L1110.63 399.92L1111.47 397.184H1112.72V400.136H1116.44V401.768H1112.72V409.208C1112.72 409.992 1112.91 410.576 1113.27 410.96C1113.66 411.328 1114.15 411.512 1114.74 411.512ZM1124.08 399.896C1125.17 399.896 1126.11 400.136 1126.91 400.616C1127.71 401.096 1128.32 401.776 1128.74 402.656C1129.17 403.52 1129.39 404.536 1129.39 405.704V406.976H1120.58C1120.61 408.432 1120.98 409.544 1121.68 410.312C1122.4 411.064 1123.4 411.44 1124.68 411.44C1125.5 411.44 1126.22 411.368 1126.84 411.224C1127.48 411.064 1128.14 410.84 1128.81 410.552V412.4C1128.15 412.688 1127.51 412.896 1126.87 413.024C1126.23 413.168 1125.47 413.24 1124.59 413.24C1123.35 413.24 1122.27 412.992 1121.35 412.496C1120.42 412 1119.69 411.264 1119.16 410.288C1118.65 409.312 1118.39 408.104 1118.39 406.664C1118.39 405.256 1118.63 404.048 1119.09 403.04C1119.57 402.032 1120.23 401.256 1121.08 400.712C1121.95 400.168 1122.95 399.896 1124.08 399.896ZM1124.06 401.624C1123.05 401.624 1122.25 401.952 1121.66 402.608C1121.08 403.248 1120.74 404.144 1120.63 405.296H1127.18C1127.16 404.208 1126.91 403.328 1126.41 402.656C1125.91 401.968 1125.13 401.624 1124.06 401.624ZM1131.58 406.424C1131.58 404.472 1131.86 402.592 1132.42 400.784C1133 398.96 1133.89 397.32 1135.11 395.864H1137.1C1135.98 397.368 1135.13 399.024 1134.56 400.832C1134 402.64 1133.72 404.496 1133.72 406.4C1133.72 408.256 1134 410.08 1134.56 411.872C1135.13 413.648 1135.97 415.288 1137.08 416.792H1135.11C1133.89 415.384 1133 413.792 1132.42 412.016C1131.86 410.224 1131.58 408.36 1131.58 406.424ZM1146.14 395.864C1145.92 396.792 1145.69 397.8 1145.45 398.888C1145.22 399.96 1145.06 400.912 1144.94 401.744H1142.66L1142.5 401.48C1142.7 400.648 1143 399.72 1143.38 398.696C1143.78 397.672 1144.18 396.728 1144.58 395.864H1146.14ZM1141.7 395.864C1141.48 396.792 1141.26 397.8 1141.03 398.888C1140.81 399.96 1140.63 400.912 1140.5 401.744H1138.25L1138.1 401.48C1138.33 400.648 1138.62 399.72 1138.99 398.696C1139.38 397.672 1139.77 396.728 1140.17 395.864H1141.7ZM1155.13 395.864L1148.75 413H1146.68L1153.07 395.864H1155.13ZM1162.28 399.92C1163.85 399.92 1165.01 400.264 1165.76 400.952C1166.52 401.64 1166.89 402.736 1166.89 404.24V413H1165.36L1164.95 411.176H1164.85C1164.29 411.88 1163.7 412.4 1163.08 412.736C1162.45 413.072 1161.6 413.24 1160.53 413.24C1159.36 413.24 1158.4 412.936 1157.63 412.328C1156.86 411.704 1156.48 410.736 1156.48 409.424C1156.48 408.144 1156.98 407.16 1157.99 406.472C1159 405.768 1160.55 405.384 1162.64 405.32L1164.83 405.248V404.48C1164.83 403.408 1164.6 402.664 1164.13 402.248C1163.67 401.832 1163.01 401.624 1162.16 401.624C1161.49 401.624 1160.85 401.728 1160.24 401.936C1159.64 402.128 1159.07 402.352 1158.54 402.608L1157.89 401.024C1158.45 400.72 1159.12 400.464 1159.88 400.256C1160.65 400.032 1161.45 399.92 1162.28 399.92ZM1164.8 406.712L1162.91 406.784C1161.31 406.848 1160.2 407.104 1159.57 407.552C1158.96 408 1158.66 408.632 1158.66 409.448C1158.66 410.168 1158.88 410.696 1159.31 411.032C1159.76 411.368 1160.32 411.536 1161.01 411.536C1162.08 411.536 1162.98 411.24 1163.7 410.648C1164.44 410.04 1164.8 409.112 1164.8 407.864V406.712ZM1176.98 399.896C1178.57 399.896 1179.84 400.448 1180.8 401.552C1181.78 402.656 1182.26 404.32 1182.26 406.544C1182.26 408.736 1181.78 410.4 1180.8 411.536C1179.84 412.672 1178.56 413.24 1176.96 413.24C1175.97 413.24 1175.14 413.056 1174.49 412.688C1173.85 412.32 1173.34 411.88 1172.98 411.368H1172.83C1172.85 411.64 1172.87 411.984 1172.9 412.4C1172.95 412.816 1172.98 413.176 1172.98 413.48V418.76H1170.86V400.136H1172.59L1172.88 401.888H1172.98C1173.36 401.328 1173.86 400.856 1174.49 400.472C1175.11 400.088 1175.94 399.896 1176.98 399.896ZM1176.6 401.672C1175.29 401.672 1174.36 402.04 1173.82 402.776C1173.27 403.512 1172.99 404.632 1172.98 406.136V406.544C1172.98 408.128 1173.23 409.352 1173.74 410.216C1174.27 411.064 1175.24 411.488 1176.65 411.488C1177.42 411.488 1178.06 411.28 1178.57 410.864C1179.08 410.432 1179.46 409.84 1179.7 409.088C1179.95 408.336 1180.08 407.48 1180.08 406.52C1180.08 405.048 1179.79 403.872 1179.22 402.992C1178.66 402.112 1177.78 401.672 1176.6 401.672ZM1191.75 399.896C1193.33 399.896 1194.61 400.448 1195.57 401.552C1196.54 402.656 1197.03 404.32 1197.03 406.544C1197.03 408.736 1196.54 410.4 1195.57 411.536C1194.61 412.672 1193.33 413.24 1191.73 413.24C1190.73 413.24 1189.91 413.056 1189.25 412.688C1188.61 412.32 1188.11 411.88 1187.74 411.368H1187.6C1187.61 411.64 1187.64 411.984 1187.67 412.4C1187.72 412.816 1187.74 413.176 1187.74 413.48V418.76H1185.63V400.136H1187.36L1187.65 401.888H1187.74C1188.13 401.328 1188.63 400.856 1189.25 400.472C1189.88 400.088 1190.71 399.896 1191.75 399.896ZM1191.37 401.672C1190.05 401.672 1189.13 402.04 1188.58 402.776C1188.04 403.512 1187.76 404.632 1187.74 406.136V406.544C1187.74 408.128 1188 409.352 1188.51 410.216C1189.04 411.064 1190.01 411.488 1191.41 411.488C1192.18 411.488 1192.82 411.28 1193.33 410.864C1193.85 410.432 1194.22 409.84 1194.46 409.088C1194.72 408.336 1194.85 407.48 1194.85 406.52C1194.85 405.048 1194.56 403.872 1193.98 402.992C1193.42 402.112 1192.55 401.672 1191.37 401.672ZM1206.28 395.864L1206.45 396.128C1206.24 396.976 1205.94 397.912 1205.54 398.936C1205.15 399.96 1204.77 400.896 1204.39 401.744H1202.8C1202.95 401.152 1203.1 400.504 1203.26 399.8C1203.42 399.096 1203.56 398.408 1203.69 397.736C1203.83 397.048 1203.95 396.424 1204.03 395.864H1206.28ZM1201.84 395.864L1202.01 396.128C1201.79 396.976 1201.48 397.912 1201.1 398.936C1200.71 399.96 1200.33 400.896 1199.95 401.744H1198.41C1198.57 401.152 1198.72 400.504 1198.87 399.8C1199.01 399.096 1199.15 398.408 1199.27 397.736C1199.4 397.048 1199.51 396.424 1199.59 395.864H1201.84ZM1212.99 406.424C1212.99 408.36 1212.7 410.224 1212.12 412.016C1211.56 413.792 1210.67 415.384 1209.46 416.792H1207.49C1208.59 415.288 1209.43 413.648 1209.99 411.872C1210.56 410.08 1210.85 408.256 1210.85 406.4C1210.85 404.496 1210.56 402.64 1209.99 400.832C1209.43 399.024 1208.59 397.368 1207.47 395.864H1209.46C1210.67 397.32 1211.56 398.96 1212.12 400.784C1212.7 402.592 1212.99 404.472 1212.99 406.424Z" fill="black"/>
+</svg>
diff --git a/docs/pics/icon/active.png b/docs/pics/icon/active.png
new file mode 100644 (file)
index 0000000..476efc8
Binary files /dev/null and b/docs/pics/icon/active.png differ
diff --git a/docs/pics/icon/selected.png b/docs/pics/icon/selected.png
new file mode 100644 (file)
index 0000000..d5beebf
Binary files /dev/null and b/docs/pics/icon/selected.png differ
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e43feb4
--- /dev/null
@@ -0,0 +1,2 @@
+
+add_subdirectory(applicationitemapp)
diff --git a/examples/applicationitemapp/CMakeLists.txt b/examples/applicationitemapp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1d9ddcd
--- /dev/null
@@ -0,0 +1,20 @@
+find_package(Qt${QT_MAJOR_VERSION} ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets)
+
+set(applicationitemapp_SRCS
+    main.cpp
+)
+
+qt_add_resources(RESOURCES resources.qrc)
+
+add_executable(applicationitemapp ${applicationitemapp_SRCS} ${RESOURCES})
+target_link_libraries(applicationitemapp
+    Qt${QT_MAJOR_VERSION}::Core
+    Qt${QT_MAJOR_VERSION}::Qml
+    Qt${QT_MAJOR_VERSION}::Quick
+    Qt${QT_MAJOR_VERSION}::Svg
+    Qt${QT_MAJOR_VERSION}::Widgets
+)
+
+install(TARGETS applicationitemapp ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+
+include(${CMAKE_SOURCE_DIR}/KF5Kirigami2Macros.cmake)
diff --git a/examples/applicationitemapp/main.cpp b/examples/applicationitemapp/main.cpp
new file mode 100644 (file)
index 0000000..e6548c3
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#include <QQuickView>
+#include <QUrl>
+#include <QtQml>
+#ifdef Q_OS_ANDROID
+#include <QGuiApplication>
+#else
+#include <QApplication>
+#endif
+
+Q_DECL_EXPORT int main(int argc, char *argv[])
+{
+    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+#ifdef Q_OS_ANDROID
+    QGuiApplication app(argc, argv);
+#else
+    QApplication app(argc, argv);
+#endif
+
+    QQuickView view;
+    view.setResizeMode(QQuickView::SizeRootObjectToView);
+    view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
+    view.show();
+
+    return app.exec();
+}
diff --git a/examples/applicationitemapp/main.qml b/examples/applicationitemapp/main.qml
new file mode 100644 (file)
index 0000000..c7c4623
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationItem {
+    id: root
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "action 3"
+            },
+            Kirigami.Action {
+                text: "action 4"
+            }
+        ]
+        handleVisible: true
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: mainPageComponent
+
+    Component {
+        id: mainPageComponent
+        Kirigami.Page {
+            title: "Hello"
+            actions.contextualActions: [
+                Kirigami.Action {
+                    text: "action 1"
+                },
+                Kirigami.Action {
+                    text: "action 2"
+                }
+            ]
+            Rectangle {
+                color: "red"
+                anchors.fill: parent
+            }
+        }
+    }
+}
diff --git a/examples/applicationitemapp/resources.qrc b/examples/applicationitemapp/resources.qrc
new file mode 100644 (file)
index 0000000..89f4bbe
--- /dev/null
@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file alias="main.qml">main.qml</file>
+    </qresource>
+</RCC>
diff --git a/examples/flexcolumn/main.qml b/examples/flexcolumn/main.qml
new file mode 100644 (file)
index 0000000..5bf3ca3
--- /dev/null
@@ -0,0 +1,30 @@
+import QtQuick 2.10
+import QtQuick.Layouts 1.12
+import org.kde.kirigami 2.14 as Kirigami
+
+Kirigami.FlexColumn {
+    Rectangle {
+        color: "red"
+
+        Layout.preferredHeight: 200
+        Layout.fillWidth: true
+    }
+    Rectangle {
+        color: "orange"
+
+        Layout.preferredHeight: 100
+        Layout.fillWidth: true
+    }
+    Rectangle {
+        color: "yellow"
+
+        Layout.preferredHeight: 50
+        Layout.fillWidth: true
+    }
+    Rectangle {
+        color: "green"
+
+        Layout.preferredHeight: 25
+        Layout.fillWidth: true
+    }
+}
\ No newline at end of file
diff --git a/examples/formlayout.qml b/examples/formlayout.qml
new file mode 100644 (file)
index 0000000..e150d66
--- /dev/null
@@ -0,0 +1,23 @@
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.FormLayout {
+       QQC2.TextField {
+               Kirigami.FormData.label: "Label:"
+       }
+       Kirigami.Separator {
+               Kirigami.FormData.label: "Section Title"
+               Kirigami.FormData.isSection: true
+       }
+       QQC2.TextField {
+               Kirigami.FormData.label: "Label:"
+       }
+       QQC2.TextField {
+       }
+       QQC2.Button {
+               text: "button"
+               Layout.fillWidth: true
+               Kirigami.FormData.label: "Button label: "
+       }
+}
diff --git a/examples/hero.qml b/examples/hero.qml
new file mode 100644 (file)
index 0000000..1edf9f9
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.4
+import org.kde.kirigami 2.15 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    pageStack.initialPage: Kirigami.Page {
+        Kirigami.Avatar {
+            id: avvy
+            name: "Janet Doe"
+            anchors.centerIn: parent
+
+            MouseArea {
+                anchors.fill: parent
+                onClicked: {
+                    let page = root.pageStack.layers.push(layer)
+                    page.hero.source = avvy
+                    page.hero.open()
+                }
+            }
+        }
+    }
+
+    Component {
+        id: layer
+
+        Kirigami.Page {
+            id: page
+            title: "Oh No"
+            property Kirigami.Hero hero: Kirigami.Hero {
+                destination: stackAv
+            }
+
+            Kirigami.Avatar {
+                id: stackAv
+                name: "John Doe"
+                width: height
+                height: Kirigami.Units.gridUnit * 20
+                anchors.centerIn: parent
+
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        page.hero.close()
+                        root.pageStack.layers.pop()
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/examples/icon/CustomSource.qml b/examples/icon/CustomSource.qml
new file mode 100644 (file)
index 0000000..417d72f
--- /dev/null
@@ -0,0 +1,5 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "image://provider/kirigami.svg"
+}
diff --git a/examples/icon/Fallback.qml b/examples/icon/Fallback.qml
new file mode 100644 (file)
index 0000000..d47c1ff
--- /dev/null
@@ -0,0 +1,6 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "this-icon-does-not-exist"
+    fallback: "view-refresh"
+}
diff --git a/examples/icon/FilesystemSource.qml b/examples/icon/FilesystemSource.qml
new file mode 100644 (file)
index 0000000..91317ca
--- /dev/null
@@ -0,0 +1,5 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "/home/example/cool.svg"
+}
diff --git a/examples/icon/IconThemeSource.qml b/examples/icon/IconThemeSource.qml
new file mode 100644 (file)
index 0000000..8d98836
--- /dev/null
@@ -0,0 +1,5 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "view-refresh"
+}
diff --git a/examples/icon/InternetSource.qml b/examples/icon/InternetSource.qml
new file mode 100644 (file)
index 0000000..9b98dad
--- /dev/null
@@ -0,0 +1,5 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "https://example.com/kirigami.png"
+}
diff --git a/examples/icon/ResourceSource.qml b/examples/icon/ResourceSource.qml
new file mode 100644 (file)
index 0000000..2a51771
--- /dev/null
@@ -0,0 +1,5 @@
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Icon {
+    source: "qrc:/kirigami.svg"
+}
diff --git a/examples/imagecolorstest.qml b/examples/imagecolorstest.qml
new file mode 100644 (file)
index 0000000..85f8ae8
--- /dev/null
@@ -0,0 +1,152 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as Controls
+import org.kde.kirigami 2.20 as Kirigami
+
+RowLayout {
+    id: root
+
+    width: 500
+    height: 500
+
+    property var icons: ["desktop", "firefox", "vlc", "blender", "applications-games", "blinken", "adjustlevels", "adjustrgb", "cuttlefish", "folder-games", "applications-network", "multimedia-player", "applications-utilities", "accessories-dictionary", "calligraflow", "calligrakrita", "view-left-close","calligraauthor"]
+    property int i
+
+    Kirigami.ImageColors {
+        id: palette
+        source: icon.source
+    }
+    Kirigami.ImageColors {
+        id: imgPalette
+        source: image
+    }
+
+    ColumnLayout {
+        Rectangle {
+            Layout.preferredWidth: 200
+            Layout.preferredHeight: 200
+            z: -1
+            color: palette.dominantContrast
+            Kirigami.Icon {
+                id: icon
+                anchors.centerIn: parent
+                width: 128
+                height: 128
+                source: "desktop"
+            }
+        }
+        Rectangle {
+            Layout.preferredWidth: 30
+            Layout.preferredHeight: 30
+            color: palette.average
+        }
+        Controls.Button {
+            text: "Next"
+            onClicked: {
+                i = (i + 1) % icons.length
+                icon.source = icons[i]
+               // palette.update()
+            }
+        }
+
+        Repeater {
+            model: palette.palette
+            delegate: RowLayout {
+                Layout.fillWidth: true
+                Rectangle {
+                    implicitWidth: 10 + 300 * modelData.ratio
+                    implicitHeight: 30
+                    color: modelData.color
+                }
+                Item {
+                    Layout.fillWidth: true
+                }
+                Rectangle {
+                    color: modelData.contrastColor
+                    implicitWidth: 30
+                    implicitHeight: 30
+                }
+            }
+        }
+    }
+    Item {
+        Layout.preferredWidth: 500
+        Layout.preferredHeight: 500 / (image.sourceSize.width / image.sourceSize.height)
+        Image {
+            id: image
+            source: "https://source.unsplash.com/random"
+            anchors.fill: parent
+            onStatusChanged: imgPalette.update()
+        }
+        ColumnLayout {
+            Controls.Button {
+                text: "Update"
+                onClicked: {
+                    image.source = "https://source.unsplash.com/random#" + (new Date()).getMilliseconds()
+                }
+            }
+            Repeater {
+                model: imgPalette.palette
+                delegate: RowLayout {
+                    Layout.fillWidth: true
+                    Rectangle {
+                        implicitWidth: 10 + 300 * modelData.ratio
+                        implicitHeight: 30
+                        color: modelData.color
+                    }
+                    Item {
+                        Layout.fillWidth: true
+                    }
+                    Rectangle {
+                        color: modelData.contrastColor
+                        implicitWidth: 30
+                        implicitHeight: 30
+                    }
+                }
+            }
+        }
+        Item {
+            width: 300
+            height: 150
+            Kirigami.Theme.backgroundColor: imgPalette.background
+            Kirigami.Theme.textColor: imgPalette.foreground
+            Kirigami.Theme.highlightColor: imgPalette.highlight
+
+            anchors {
+                bottom: parent.bottom
+                right: parent.right
+            }
+
+            Rectangle {
+                anchors.fill: parent
+                opacity: 0.8
+                color: Kirigami.Theme.backgroundColor
+            }
+            ColumnLayout {
+                anchors.centerIn: parent
+                RowLayout {
+                    Rectangle {
+                        Layout.alignment: Qt.AlignCenter
+                        implicitWidth: 10
+                        implicitHeight: 10
+                        color: Kirigami.Theme.highlightColor
+                    }
+                    Controls.Label {
+                        text: "Lorem Ipsum dolor sit amet"
+                        color: Kirigami.Theme.textColor
+                    }
+                }
+                RowLayout {
+                    Controls.TextField {
+                        Kirigami.Theme.inherit: true
+                        text: "text"
+                    }
+                    Controls.Button {
+                        Kirigami.Theme.inherit: true
+                        text: "Ok"
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/examples/listitemdraghandle.qml b/examples/listitemdraghandle.qml
new file mode 100644 (file)
index 0000000..ff78cf0
--- /dev/null
@@ -0,0 +1,49 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+       pageStack.initialPage: Kirigami.ScrollablePage {
+               ListView {
+                       id: mainList
+                       model: ListModel {
+                               ListElement {
+                                       someText: "Item 1"
+                               }
+                               ListElement {
+                                       someText: "Item 2"
+                               }
+                               ListElement {
+                                       someText: "Item 3"
+                               }
+                       }
+
+                       delegate: Item {
+                               width: mainList.width
+                               height: listItemComponent.implicitHeight
+                               Kirigami.AbstractListItem {
+                                       id: listItemComponent
+                                       contentItem: RowLayout {
+                                               Kirigami.ListItemDragHandle {
+                                                       listItem: listItemComponent
+                                                       listView: mainList
+                                                       onMoveRequested: mainList.model.move(oldIndex, newIndex, 1)
+                                               }
+                                               QQC2.Label {
+                                                       text: model.someText + " at index " + index
+                                                       Layout.fillWidth: true
+                                               }
+                                       }
+                               }
+                       }
+
+                       moveDisplaced: Transition {
+                               YAnimator {
+                                       duration: Kirigami.Units.longDuration
+                                       easing.type: Easing.InOutQuad
+                               }
+                       }
+               }
+       }
+}
diff --git a/examples/multiplatformnotesapp/NotesGeneral.qml b/examples/multiplatformnotesapp/NotesGeneral.qml
new file mode 100644 (file)
index 0000000..021170c
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    property string currentFile
+
+    pageStack.initialPage: iconView
+
+    Kirigami.ScrollablePage {
+        id: iconView
+        title: "Notes"
+        actions.contextualActions: [
+            Kirigami.Action {
+                id: sortAction
+                icon.name: "view-sort-ascending-symbolic"
+                tooltip: "Sort Ascending"
+            }
+        ]
+        background: Rectangle {
+            color: Kirigami.Theme.backgroundColor
+        }
+
+        GridView {
+            id: view
+            model: 100
+            cellWidth: Kirigami.Units.gridUnit * 9
+            cellHeight: cellWidth
+            currentIndex: -1
+            highlightMoveDuration: 0
+            highlight: Rectangle {
+                color: Kirigami.Theme.highlightColor
+            }
+            delegate: MouseArea {
+                width: view.cellWidth
+                height: view.cellHeight
+                Kirigami.Icon {
+                    source: "text-plain"
+                    anchors {
+                        fill: parent
+                        margins: Kirigami.Units.gridUnit
+                    }
+                    QQC2.Label {
+                        anchors {
+                            top: parent.bottom
+                            horizontalCenter: parent.horizontalCenter
+                        }
+                        text: "File " + modelData
+                    }
+                }
+                onClicked: {
+                    view.currentIndex = index;
+                    root.currentFile = "File " + modelData;
+                    if (root.pageStack.depth < 2) {
+                        root.pageStack.push(editorComponent);
+                    }
+                    root.pageStack.currentIndex = 1
+                }
+            }
+        }
+    }
+
+    Component {
+        id: editorComponent
+        Kirigami.ScrollablePage {
+            id: editor
+            title: root.currentFile
+            actions {
+                main: Kirigami.Action {
+                    id: shareAction
+                    icon.name: "document-share"
+                    text: "Share..."
+                    tooltip: "Share this document with your device"
+                    checkable: true
+                    onCheckedChanged: sheet.sheetOpen = checked;
+                }
+                contextualActions: [
+                    Kirigami.Action {
+                        icon.name: "format-text-bold-symbolic"
+                        tooltip: "Bold"
+                    },
+                    Kirigami.Action {
+                        icon.name: "format-text-underline-symbolic"
+                        tooltip: "Underline"
+                    },
+                    Kirigami.Action {
+                        icon.name: "format-text-italic-symbolic"
+                        tooltip: "Italic"
+                    }
+                ]
+            }
+            background: Rectangle {
+                color: Kirigami.Theme.backgroundColor
+                Rectangle {
+                    anchors.fill: parent
+                    color: "yellow"
+                    opacity: 0.2
+                }
+            }
+
+            Kirigami.OverlaySheet {
+                id: sheet
+                onSheetOpenChanged: shareAction.checked = sheetOpen
+                ListView {
+                    implicitWidth: Kirigami.Units.gridUnit * 30
+                    model: ListModel {
+                        ListElement {
+                            title: "Share with phone \"Nokia 3310\""
+                            description: "You selected this phone 12 times before. It's currently connected via bluetooth"
+                            buttonText: "Push Sync"
+                        }
+                        ListElement {
+                            title: "Share with phone \"My other Nexus5\""
+                            description: "You selected this phone 0 times before. It's currently connected to your laptop via Wifi"
+                            buttonText: "Push Sync"
+                        }
+                        ListElement {
+                            title: "Share with NextCloud"
+                            description: "You currently do not have a server set up for sharing and storing notes from Katie. If you want to set one up click here"
+                            buttonText: "Setup…"
+                        }
+                        ListElement {
+                            title: "Send document via email"
+                            description: "This will send the document as an attached file to your own email for later sync"
+                            buttonText: "Send As Email"
+                        }
+                    }
+                    header: Kirigami.AbstractListItem {
+                        height: Kirigami.Units.gridUnit * 6
+                        hoverEnabled: false
+                        RowLayout {
+                            Kirigami.Icon {
+                                source: "documentinfo"
+                                width: Kirigami.Units.iconSizes.large
+                                height: width
+                            }
+                            QQC2.Label {
+                                Layout.fillWidth: true
+                                Layout.minimumWidth: 0
+                                wrapMode: Text.WordWrap
+                                text: "This document has already automatically synced with your phone \"Dancepartymeister 12\". If you want to sync with another device or do further actions you can do that here"
+                            }
+                        }
+                    }
+                    delegate: Kirigami.AbstractListItem {
+                        height: Kirigami.Units.gridUnit * 6
+                        hoverEnabled: false
+                        //TODO: bug in overlaysheet
+                        rightPadding: Kirigami.Units.gridUnit * 1.5
+                        RowLayout {
+                            ColumnLayout {
+                                Layout.fillWidth: true
+                                Layout.minimumWidth: 0
+                                QQC2.Label {
+                                    wrapMode: Text.WordWrap
+                                    text: model.title
+                                }
+                                QQC2.Label {
+                                    Layout.fillWidth: true
+                                    Layout.minimumWidth: 0
+                                    wrapMode: Text.WordWrap
+                                    text: model.description
+                                }
+                            }
+                            QQC2.Button {
+                                text: model.buttonText
+                                onClicked: sheet.close()
+                            }
+                        }
+                    }
+                }
+            }
+            QQC2.TextArea {
+                background: Item {}
+                wrapMode: TextEdit.WordWrap
+                selectByMouse: true
+                text: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sollicitudin, lorem at semper pretium, tortor nisl pellentesque risus, eget eleifend odio ipsum ac mi. Donec justo ex, elementum vitae gravida vel, pretium ac lacus. Duis non metus ac enim viverra auctor in non nunc. Sed sit amet luctus nisi. Proin justo nulla, vehicula eget porta sit amet, aliquet vitae dolor. Mauris sed odio auctor, tempus ipsum ac, placerat enim. Ut in dolor vel ante dictum auctor.
+
+    Praesent blandit rhoncus augue. Phasellus consequat luctus pulvinar. Pellentesque rutrum laoreet dolor, sit amet pellentesque tellus mattis sed. Sed accumsan cursus tortor. Morbi et risus dolor. Nullam facilisis ipsum justo, nec sollicitudin mi pulvinar ac. Nulla facilisi. Donec maximus turpis eget mollis laoreet. Phasellus vel mauris et est mattis auctor eget sit amet turpis. Aliquam dignissim euismod purus, eu efficitur neque fermentum eu. Suspendisse potenti. Praesent mattis ex vitae neque rutrum tincidunt. Etiam placerat leo viverra pulvinar tincidunt.
+
+    Proin vel rutrum massa. Proin volutpat aliquet dapibus. Maecenas aliquet elit eu venenatis venenatis. Ut elementum, lacus vel auctor auctor, velit massa elementum ligula, quis elementum ex nisi aliquam mauris. Nulla facilisi. Pellentesque aliquet egestas venenatis. Donec iaculis ultrices laoreet. Vestibulum cursus rhoncus sollicitudin.
+
+    Proin quam libero, bibendum eget sodales id, gravida quis enim. Duis fermentum libero vitae sapien hendrerit, in tincidunt tortor semper. Nullam quam nisi, feugiat sed rutrum vitae, dignissim quis risus. Ut ultricies pellentesque est, ut gravida massa convallis sed. Ut placerat dui non felis interdum, id malesuada nulla ornare. Phasellus volutpat purus placerat velit porta tristique. Donec molestie leo in turpis bibendum pharetra. Fusce fermentum diam vitae neque laoreet, sed aliquam leo sollicitudin.
+
+    Ut facilisis massa arcu, eu suscipit ante varius sed. Morbi augue leo, mattis eu tempor vitae, condimentum sed urna. Curabitur ac blandit orci. Vestibulum quis consequat nunc. Proin imperdiet commodo imperdiet. Aenean mattis augue et imperdiet ultricies. Ut id feugiat nulla, et sollicitudin dui. Etiam scelerisque ligula ac euismod hendrerit. Integer in quam nibh. Pellentesque risus massa, porttitor quis fermentum eu, dictum varius magna. Morbi euismod bibendum lacus efficitur pretium. Phasellus elementum porttitor enim nec dictum. Morbi et augue laoreet, convallis quam quis, egestas quam.`
+            }
+        }
+    }
+}
diff --git a/examples/multiplatformnotesapp/notesDesktop.qml b/examples/multiplatformnotesapp/notesDesktop.qml
new file mode 100644 (file)
index 0000000..0b988da
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+NotesGeneral {
+    id: root
+}
diff --git a/examples/multiplatformnotesapp/notesMobile.qml b/examples/multiplatformnotesapp/notesMobile.qml
new file mode 100644 (file)
index 0000000..23921e1
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import org.kde.kirigami 2.20 as Kirigami
+
+NotesGeneral {
+    id: root
+
+    contextDrawer: Kirigami.ContextDrawer {}
+}
diff --git a/examples/overlaydrawer.qml b/examples/overlaydrawer.qml
new file mode 100644 (file)
index 0000000..5b4ba1c
--- /dev/null
@@ -0,0 +1,35 @@
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+       Kirigami.OverlayDrawer {
+               id: drawer
+
+               edge: Qt.BottomEdge
+
+               contentItem: RowLayout {
+                       QQC2.Button {
+                               text: "Close"
+                               onClicked: drawer.close()
+                       }
+               }
+       }
+
+       pageStack.initialPage: Kirigami.Page {
+               RowLayout {
+                       QQC2.Button {
+                               text: "Open drawer"
+                               onClicked: {
+                                       drawer.modal = isModal.checked
+                                       drawer.open()
+                               }
+                       }
+                       QQC2.CheckBox {
+                               id: isModal
+                               text: "Drawer is modal?"
+                       }
+               }
+       }
+}
+
diff --git a/examples/pagerow.qml b/examples/pagerow.qml
new file mode 100644 (file)
index 0000000..e9ba4b3
--- /dev/null
@@ -0,0 +1,41 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    pageStack.initialPage: [page1, page2]
+
+    Kirigami.Page {
+        id: page1
+        RowLayout {
+            QQC2.Button {
+                text: "add card"
+                onClicked: cardsView.model.append({});
+            }
+
+            QQC2.Button {
+                text: "remove card"
+                onClicked: {
+                    if (cardsView.model.count > 0) {
+                        cardsView.model.remove(cardsView.model.count-1)
+                    }
+                }
+            }
+        }
+    }
+    Kirigami.ScrollablePage {
+        id: page2
+        Kirigami.CardsListView {
+            id: cardsView
+            anchors.fill: parent
+            model: ListModel {}
+            delegate: Kirigami.Card {
+                banner.title: index
+                contentItem: QQC2.Label {
+                    text: "lorem ipsum"
+                }
+            }
+        }
+    }
+}
diff --git a/examples/scenepotitionattached.qml b/examples/scenepotitionattached.qml
new file mode 100644 (file)
index 0000000..c6fb7e7
--- /dev/null
@@ -0,0 +1,80 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+       id: root
+       pageStack.initialPage: Kirigami.Page {
+               GridLayout {
+                       anchors.top: parent.top
+                       anchors.left: parent.left
+                       anchors.right: parent.right
+                       ColumnLayout {
+                               Layout.column: 0
+                               Kirigami.Heading {
+                                       text: "scene position"
+                                       Layout.minimumHeight: Kirigami.Units.gridUnit * 2
+                               }
+                               ColumnLayout {
+                                       QQC2.Label {
+                                               // This will be 99 because it is the distance between the top of the window and this Item
+                                               text: "Sublayout 1: " + Kirigami.ScenePosition.y
+                                       }
+                                       QQC2.Label {
+                                               text: "Sublayout 2: " + Kirigami.ScenePosition.y
+                                       }
+                                       QQC2.Label {
+                                               text: "Sublayout 3: " + Kirigami.ScenePosition.y
+                                       }
+                               }
+                               Item {
+                                       Layout.minimumHeight: 100
+                               }
+                               QQC2.Label {
+                                       text: "Layout 1: " + Kirigami.ScenePosition.y
+                               }
+                               QQC2.Label {
+                                       text: "Layout 2: " + Kirigami.ScenePosition.y
+                               }
+                               QQC2.Label {
+                                       text: "Layout 3: " + Kirigami.ScenePosition.y
+                               }
+                       }
+                       ColumnLayout {
+                               Layout.column: 1
+                               Kirigami.Heading {
+                                       text: "non scene position"
+                                       Layout.minimumHeight: Kirigami.Units.gridUnit * 2
+                               }
+                               ColumnLayout {
+                                       ColumnLayout {
+                                               QQC2.Label {
+                                                       // This will be 0 because the X and Y properties are relative to the first Item
+                                                       text: "Sublayout 1: " + y
+                                               }
+                                               QQC2.Label {
+                                                       text: "Sublayout 2: " + y
+                                               }
+                                               QQC2.Label {
+                                                       text: "Sublayout 3: " + y
+                                               }
+                                       }
+                                       Item {
+                                               Layout.minimumHeight: 100
+                                       }
+                                       QQC2.Label {
+                                               text: "Layout 1: " + y
+                                       }
+                                       QQC2.Label {
+                                               text: "Layout 2: " + y
+                                       }
+                                       QQC2.Label {
+                                               text: "Layout 3: " + y
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
diff --git a/examples/settingscomponents/GeneralSettingsPage.qml b/examples/settingscomponents/GeneralSettingsPage.qml
new file mode 100644 (file)
index 0000000..1041344
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Felipe Kinoshita <kinofhek@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ScrollablePage {
+    title: qsTr("General")
+
+    QQC2.CheckBox {
+        Kirigami.FormData.label: i18n("Something")
+        text: i18n("Do something")
+    }
+}
diff --git a/examples/settingscomponents/SettingsPage.qml b/examples/settingscomponents/SettingsPage.qml
new file mode 100644 (file)
index 0000000..e51d8e8
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Felipe Kinoshita <kinofhek@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.CategorizedSettings {
+    actions: [
+        Kirigami.SettingAction {
+            text: qsTr("General")
+            icon.name: "wayland"
+            page: Qt.resolvedUrl("./GeneralSettingsPage.qml")
+        }
+    ]
+}
diff --git a/examples/settingscomponents/main.qml b/examples/settingscomponents/main.qml
new file mode 100644 (file)
index 0000000..36547f2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Felipe Kinoshita <kinofhek@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    title: qsTr("Hello, World")
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        isMenu: !Kirigami.isMobile
+        actions: [
+            Kirigami.Action {
+                text: qsTr("Settings")
+                icon.name: "settings-configure"
+                onTriggered: root.pageStack.pushDialogLayer(Qt.resolvedUrl("./SettingsPage.qml"), {
+                    width: root.width
+                }, {
+                    title: qsTr("Settings"),
+                    width: root.width - (Kirigami.Units.gridUnit * 4),
+                    height: root.height - (Kirigami.Units.gridUnit * 4)
+                })
+            }
+        ]
+    }
+
+    pageStack.initialPage: Kirigami.Page {
+        title: qsTr("Main Page")
+    }
+}
diff --git a/examples/settingscomponents/resources.qrc b/examples/settingscomponents/resources.qrc
new file mode 100644 (file)
index 0000000..a63d9d2
--- /dev/null
@@ -0,0 +1,7 @@
+<RCC>
+    <qresource prefix="/">
+        <file alias="main.qml">main.qml</file>
+        <file alias="SettingsPage.qml">SettingsPage.qml</file>
+        <file alias="GeneralSettingsPage.qml">GeneralSettingsPage.qml</file>
+    </qresource>
+</RCC>
diff --git a/examples/shadowrectangle.qml b/examples/shadowrectangle.qml
new file mode 100644 (file)
index 0000000..2dc5731
--- /dev/null
@@ -0,0 +1,18 @@
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Rectangle {
+       color: "white"
+       Kirigami.ShadowedRectangle {
+               anchors.centerIn: parent
+
+               color: "grey"
+               width: parent.width / 1.5
+               height: parent.height / 1.5
+               opacity: .3
+               border.width: Kirigami.Units.smallSpacing * 2
+               border.color: "red"
+               corners.bottomLeftRadius: Kirigami.Units.largeSpacing
+               corners.topRightRadius: Kirigami.Units.largeSpacing * 5
+       }
+}
diff --git a/examples/simpleexamples/AbstractApplicationWindow.qml b/examples/simpleexamples/AbstractApplicationWindow.qml
new file mode 100644 (file)
index 0000000..649b565
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.AbstractApplicationWindow {
+    id: root
+
+    width: 500
+    height: 800
+    visible: true
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Widget gallery"
+        titleIcon: "applications-graphics"
+
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "Sync"
+                icon.name: "folder-sync"
+                Kirigami.Action {
+                    text: "action 4"
+                }
+                Kirigami.Action {
+                    text: "action 5"
+                }
+            },
+            Kirigami.Action {
+                text: "Checkable"
+                icon.name: "view-list-details"
+                checkable: true
+                checked: false
+                onTriggered: {
+                    print("Action checked:" + checked)
+                }
+            },
+            Kirigami.Action {
+                text: "Settings"
+                icon.name: "configure"
+                checkable: true
+                //Need to do this, otherwise it breaks the bindings
+                property bool current: pageStack.currentItem ? pageStack.currentItem.objectName == "settingsPage" : false
+                onCurrentChanged: {
+                    checked = current;
+                }
+                onTriggered: {
+                    pageStack.push(settingsComponent);
+                }
+            }
+        ]
+
+        QQC2.CheckBox {
+            checked: true
+            text: "Option 1"
+        }
+        QQC2.CheckBox {
+            text: "Option 2"
+        }
+        QQC2.CheckBox {
+            text: "Option 3"
+        }
+        QQC2.Slider {
+            Layout.fillWidth: true
+            value: 0.5
+        }
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack: QQC2.StackView {
+        anchors.fill: parent
+        property int currentIndex: 0
+        focus: true
+        onCurrentIndexChanged: {
+            if (depth > currentIndex+1) {
+                pop(get(currentIndex));
+            }
+        }
+        onDepthChanged: {
+            currentIndex = depth-1;
+        }
+        initialItem: mainPageComponent
+
+        Keys.onReleased: event => {
+            if (event.key === Qt.Key_Back ||
+                    (event.key === Qt.Key_Left && (event.modifiers & Qt.AltModifier))) {
+                event.accepted = true;
+                if (root.contextDrawer && root.contextDrawer.drawerOpen) {
+                    root.contextDrawer.close();
+                } else if (root.globalDrawer && root.globalDrawer.drawerOpen) {
+                    root.globalDrawer.close();
+                } else {
+                    var backEvent = {accepted: false}
+                    if (root.pageStack.currentIndex >= 1) {
+                        root.pageStack.currentItem.backRequested(backEvent);
+                        if (!backEvent.accepted) {
+                            if (root.pageStack.depth > 1) {
+                                root.pageStack.currentIndex = Math.max(0, root.pageStack.currentIndex - 1);
+                                backEvent.accepted = true;
+                            } else {
+                                Qt.quit();
+                            }
+                        }
+                    }
+
+                    if (!backEvent.accepted) {
+                        Qt.quit();
+                    }
+                }
+            }
+        }
+    }
+
+    Component {
+        id: settingsComponent
+        Kirigami.Page {
+            title: "Settings"
+            objectName: "settingsPage"
+            Rectangle {
+                anchors.fill: parent
+            }
+        }
+    }
+
+    //Main app content
+    Component {
+        id: mainPageComponent
+        MultipleColumnsGallery {}
+    }
+}
diff --git a/examples/simpleexamples/FixedSidebar.qml b/examples/simpleexamples/FixedSidebar.qml
new file mode 100644 (file)
index 0000000..dda2844
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+import QtQuick.Controls 2.15 as QQC2
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    width: Kirigami.Units.gridUnit * 60
+    height: Kirigami.Units.gridUnit * 40
+
+    pageStack.initialPage: mainPageComponent
+    globalDrawer: Kirigami.OverlayDrawer {
+        drawerOpen: true
+        modal: false
+        contentItem: Item {
+            implicitWidth: Kirigami.Units.gridUnit * 10
+
+            QQC2.Label {
+                text: "This is a sidebar"
+                width: parent.width - Kirigami.Units.smallSpacing * 2
+                wrapMode: Text.WordWrap
+                anchors.horizontalCenter: parent.horizontalCenter
+            }
+        }
+    }
+
+    //Main app content
+    Component {
+        id: mainPageComponent
+        MultipleColumnsGallery {}
+    }
+}
diff --git a/examples/simpleexamples/MultipleColumnsGallery.qml b/examples/simpleexamples/MultipleColumnsGallery.qml
new file mode 100644 (file)
index 0000000..4e029ed
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ScrollablePage {
+    id: page
+
+    Layout.fillWidth: true
+    implicitWidth: Kirigami.Units.gridUnit * (Math.floor(Math.random() * 35) + 8)
+
+    title: "Multiple Columns"
+
+    actions.contextualActions: [
+        Kirigami.Action {
+            text:"Action for buttons"
+            icon.name: "bookmarks"
+            onTriggered: print("Action 1 clicked")
+        },
+        Kirigami.Action {
+            text:"Action 2"
+            icon.name: "folder"
+            enabled: false
+        }
+    ]
+
+    ColumnLayout {
+        width: page.width
+        spacing: Kirigami.Units.smallSpacing
+
+        QQC2.Label {
+            Layout.fillWidth: true
+            wrapMode: Text.WordWrap
+            text: "This page is used to test multiple columns: you can push and pop an arbitrary number of pages, each new page will have a random implicit width between 8 and 35 grid units.\nIf you enlarge the window enough, you can test how the application behaves with multiple columns."
+        }
+        Item {
+            Layout.minimumWidth: Kirigami.Units.gridUnit *2
+            Layout.minimumHeight: Layout.minimumWidth
+        }
+        QQC2.Label {
+            Layout.alignment: Qt.AlignHCenter
+            text: "Page implicitWidth: " + page.implicitWidth
+        }
+        QQC2.Button {
+            text: "Push Another Page"
+            Layout.alignment: Qt.AlignHCenter
+            onClicked: pageStack.push(Qt.resolvedUrl("MultipleColumnsGallery.qml"));
+        }
+        QQC2.Button {
+            text: "Pop A Page"
+            Layout.alignment: Qt.AlignHCenter
+            onClicked: pageStack.pop();
+        }
+        RowLayout {
+            Layout.alignment: Qt.AlignHCenter
+            QQC2.TextField {
+                id: edit
+                text: page.title
+            }
+            QQC2.Button {
+                text: "Rename Page"
+                onClicked: page.title = edit.text;
+            }
+        }
+        Kirigami.SearchField {
+            id: searchField
+            Layout.alignment: Qt.AlignHCenter
+            onAccepted: console.log("Search text is " + text);
+        }
+        Kirigami.PasswordField {
+            id: passwordField
+            Layout.alignment: Qt.AlignHCenter
+            onAccepted: console.log("Password")
+        }
+    }
+}
diff --git a/examples/simpleexamples/Sidebar.qml b/examples/simpleexamples/Sidebar.qml
new file mode 100644 (file)
index 0000000..098b6dc
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+    width: Kirigami.Units.gridUnit * 60
+    height: Kirigami.Units.gridUnit * 40
+
+
+    pageStack.initialPage: mainPageComponent
+    globalDrawer: Kirigami.OverlayDrawer {
+        id: drawer
+        drawerOpen: true
+        modal: false
+        //leftPadding: Kirigami.Units.largeSpacing
+        rightPadding: Kirigami.Units.largeSpacing
+        contentItem: ColumnLayout {
+            Layout.preferredWidth: Kirigami.Units.gridUnit * 20
+
+            QQC2.Label {
+                Layout.alignment: Qt.AlignHCenter
+                text: "This is a sidebar"
+                Layout.fillWidth: true
+                width: parent.width - Kirigami.Units.smallSpacing * 2
+                wrapMode: Text.WordWrap
+            }
+            QQC2.Button {
+                Layout.alignment: Qt.AlignHCenter
+                text: "Modal"
+                checkable: true
+                Layout.fillWidth: true
+                checked: false
+                onCheckedChanged: drawer.modal = checked
+            }
+            Item {
+                Layout.fillHeight: true
+            }
+        }
+    }
+    contextDrawer: Kirigami.OverlayDrawer {
+        id: contextDrawer
+        drawerOpen: true
+        edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
+        modal: false
+        leftPadding: Kirigami.Units.largeSpacing
+        rightPadding: Kirigami.Units.largeSpacing
+        contentItem: ColumnLayout {
+            Layout.preferredWidth: Kirigami.Units.gridUnit * 10
+
+            QQC2.Label {
+                Layout.alignment: Qt.AlignHCenter
+                text: "This is a sidebar"
+                Layout.fillWidth: true
+                width: parent.width - Kirigami.Units.smallSpacing * 2
+                wrapMode: Text.WordWrap
+            }
+            QQC2.Button {
+                Layout.alignment: Qt.AlignHCenter
+                text: "Modal"
+                checkable: true
+                Layout.fillWidth: true
+                checked: false
+                onCheckedChanged: contextDrawer.modal = checked
+            }
+            Item {
+                Layout.fillHeight: true
+            }
+        }
+    }
+
+    menuBar: QQC2.MenuBar {
+        QQC2.Menu {
+            title: qsTr("&File")
+            QQC2.Action { text: qsTr("&New...") }
+            QQC2.Action { text: qsTr("&Open...") }
+            QQC2.Action { text: qsTr("&Save") }
+            QQC2.Action { text: qsTr("Save &As...") }
+            QQC2.MenuSeparator { }
+            QQC2.Action { text: qsTr("&Quit") }
+        }
+        QQC2.Menu {
+            title: qsTr("&Edit")
+            QQC2.Action { text: qsTr("Cu&t") }
+            QQC2.Action { text: qsTr("&Copy") }
+            QQC2.Action { text: qsTr("&Paste") }
+        }
+        QQC2.Menu {
+            title: qsTr("&Help")
+            QQC2.Action { text: qsTr("&About") }
+        }
+    }
+    header: QQC2.ToolBar {
+        contentItem: RowLayout {
+            QQC2.ToolButton {
+                text: "Global ToolBar"
+            }
+            Item {
+                Layout.fillWidth: true
+            }
+            Kirigami.ActionTextField {
+                id: searchField
+
+                placeholderText: "Search…"
+
+                focusSequence: StandardKey.Find
+                leftActions: [
+                    Kirigami.Action {
+                        icon.name: "edit-clear"
+                        visible: searchField.text !== ""
+                        onTriggered: {
+                            searchField.text = ""
+                            searchField.accepted()
+                        }
+                    },
+                    Kirigami.Action {
+                        icon.name: "edit-clear"
+                        visible: searchField.text !== ""
+                        onTriggered: {
+                            searchField.text = ""
+                            searchField.accepted()
+                        }
+                    }
+                ]
+                rightActions: [
+                    Kirigami.Action {
+                        icon.name: "edit-clear"
+                        visible: searchField.text !== ""
+                        onTriggered: {
+                            searchField.text = ""
+                            searchField.accepted()
+                        }
+                    },
+                    Kirigami.Action {
+                        icon.name: "anchor"
+                        visible: searchField.text !== ""
+                        onTriggered: {
+                            searchField.text = ""
+                            searchField.accepted()
+                        }
+                    }
+                ]
+
+                onAccepted: console.log("Search text is " + searchField.text)
+            }
+        }
+    }
+    //Main app content
+    Component {
+        id: mainPageComponent
+        MultipleColumnsGallery {}
+    }
+    footer: QQC2.ToolBar {
+        position: QQC2.ToolBar.Footer
+        QQC2.Label {
+            anchors.fill: parent
+            verticalAlignment: Qt.AlignVCenter
+            text: "Global Footer"
+        }
+    }
+}
diff --git a/examples/simpleexamples/SimplePage.qml b/examples/simpleexamples/SimplePage.qml
new file mode 100644 (file)
index 0000000..56b82d8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ScrollablePage {
+    id: page
+
+    Layout.fillWidth: true
+    implicitWidth: Kirigami.Units.gridUnit * (Math.floor(Math.random() * 35) + 8)
+
+    title: i18n("Simple Scrollable Page")
+
+    actions.contextualActions: [
+        Kirigami.Action {
+            text:"Action for buttons"
+            icon.name: "bookmarks"
+            onTriggered: print("Action 1 clicked")
+        },
+        Kirigami.Action {
+            text:"Action 2"
+            icon.name: "folder"
+            enabled: false
+        }
+    ]
+
+    ColumnLayout {
+        width: page.width
+        spacing: Kirigami.Units.smallSpacing
+
+        QQC2.Label {
+            Layout.fillWidth: true
+            wrapMode: Text.WordWrap
+            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a sem venenatis, dictum odio vitae, tincidunt sapien. Proin a suscipit ligula, id interdum leo. Donec sed dolor sed lacus dignissim tempor a a lorem. In ullamcorper varius vestibulum. Sed nec arcu semper, varius velit ut, pharetra est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer odio nibh, tincidunt quis condimentum quis, consequat id lacus. Nulla quis mauris erat. Suspendisse rhoncus suscipit massa, at suscipit lorem rhoncus et."
+        }
+    }
+}
diff --git a/examples/simpleexamples/TabBarHeader.qml b/examples/simpleexamples/TabBarHeader.qml
new file mode 100644 (file)
index 0000000..3691cd4
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    width: 500
+    height: 800
+    visible: true
+
+    pageStack.initialPage: mainPageComponent
+    pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.TabBar
+
+    Component.onCompleted: {
+        pageStack.push(mainPageComponent);
+        pageStack.push(mainPageComponent);
+        pageStack.currentIndex = 0;
+    }
+
+    //Main app content
+    Component {
+        id: mainPageComponent
+        MultipleColumnsGallery {}
+    }
+}
diff --git a/examples/simpleexamples/customdrawer.qml b/examples/simpleexamples/customdrawer.qml
new file mode 100644 (file)
index 0000000..bae4b1f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    globalDrawer: Kirigami.OverlayDrawer {
+        contentItem: Rectangle {
+            implicitWidth: Kirigami.Units.gridUnit * 10
+            color: "red"
+            anchors.fill: parent
+        }
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: mainPageComponent
+
+    Component {
+        id: mainPageComponent
+        Kirigami.ScrollablePage {
+            title: "Hello"
+            Rectangle {
+                anchors.fill: parent
+            }
+        }
+    }
+}
diff --git a/examples/simpleexamples/dragPageWidth.qml b/examples/simpleexamples/dragPageWidth.qml
new file mode 100644 (file)
index 0000000..e8021dd
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Eike Hein <hein@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    property int defaultColumnWidth: Kirigami.Units.gridUnit * 13
+    property int columnWidth: defaultColumnWidth
+
+    pageStack.defaultColumnWidth: columnWidth
+    pageStack.initialPage: [firstPageComponent, secondPageComponent]
+
+    MouseArea {
+        id: dragHandle
+
+        visible: pageStack.wideMode
+
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+
+        x: columnWidth - (width / 2)
+        width: 2
+
+        property int dragRange: (Kirigami.Units.gridUnit * 5)
+        property int _lastX: -1
+
+        cursorShape: Qt.SplitHCursor
+
+        onPressed: mouse => {
+            _lastX = mouse.x;
+        }
+
+        onPositionChanged: mouse => {
+            if (mouse.x > _lastX) {
+                columnWidth = Math.min((defaultColumnWidth + dragRange),
+                    columnWidth + (mouse.x - _lastX));
+            } else if (mouse.x < _lastX) {
+                columnWidth = Math.max((defaultColumnWidth - dragRange),
+                    columnWidth - (_lastX - mouse.x));
+            }
+        }
+
+        Rectangle {
+            anchors.fill: parent
+
+            color: "blue"
+        }
+    }
+
+    Component {
+        id: firstPageComponent
+
+        Kirigami.Page {
+            id: firstPage
+
+            background: Rectangle { color: "red" }
+        }
+    }
+
+    Component {
+        id: secondPageComponent
+
+        Kirigami.Page {
+            id: secondPage
+
+            background: Rectangle { color: "green" }
+        }
+    }
+}
diff --git a/examples/simpleexamples/footer.qml b/examples/simpleexamples/footer.qml
new file mode 100644 (file)
index 0000000..c62c99f
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    footer: QQC2.ToolBar {
+        //height: Kirigami.Units.gridUnit * 3
+        RowLayout {
+            QQC2.ToolButton {
+                text: "text"
+            }
+        }
+    }
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "action 3"
+            },
+            Kirigami.Action {
+                text: "action 4"
+            }
+        ]
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: mainPageComponent
+
+    Component {
+        id: mainPageComponent
+        Kirigami.ScrollablePage {
+            title: "Hello"
+            Rectangle {
+                anchors.fill: parent
+            }
+        }
+    }
+
+
+}
diff --git a/examples/simpleexamples/minimal.qml b/examples/simpleexamples/minimal.qml
new file mode 100644 (file)
index 0000000..c8be6cc
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "action 3"
+            },
+            Kirigami.Action {
+                text: "action 4"
+            }
+        ]
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: mainPageComponent
+
+    Component {
+        id: mainPageComponent
+        Kirigami.ScrollablePage {
+            title: "Hello"
+            Rectangle {
+                anchors.fill: parent
+            }
+        }
+    }
+}
diff --git a/examples/simpleexamples/pagePoolDrawer.qml b/examples/simpleexamples/pagePoolDrawer.qml
new file mode 100644 (file)
index 0000000..ff24af7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    Kirigami.PagePool {
+        id: mainPagePool
+    }
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+        modal: !root.wideScreen
+        width: Kirigami.Units.gridUnit * 10
+
+        actions: [
+            Kirigami.PagePoolAction {
+                text: i18n("Page1")
+                icon.name: "speedometer"
+                pagePool: mainPagePool
+                page: "SimplePage.qml"
+            },
+            Kirigami.PagePoolAction {
+                text: i18n("Page2")
+                icon.name: "window-duplicate"
+                pagePool: mainPagePool
+                page: "MultipleColumnsGallery.qml"
+            }
+        ]
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: mainPagePool.loadPage("SimplePage.qml")
+
+}
diff --git a/examples/simpleexamples/pagePoolFirstColumn.qml b/examples/simpleexamples/pagePoolFirstColumn.qml
new file mode 100644 (file)
index 0000000..4cdaebc
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    Kirigami.PagePool {
+        id: mainPagePool
+    }
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+    }
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: wideScreen ? [firstPage, mainPagePool.loadPage("SimplePage.qml")] : [firstPage]
+
+    Component {
+        id: firstPage
+        Kirigami.ScrollablePage {
+            id: root
+            title: i18n("Sidebar")
+            property list<Kirigami.PagePoolAction> pageActions: [
+                Kirigami.PagePoolAction {
+                    text: i18n("Page1")
+                    icon.name: "speedometer"
+                    pagePool: mainPagePool
+                    basePage: root
+                    page: "SimplePage.qml"
+                },
+                Kirigami.PagePoolAction {
+                    text: i18n("Page2")
+                    icon.name: "window-duplicate"
+                    pagePool: mainPagePool
+                    basePage: root
+                    page: "MultipleColumnsGallery.qml"
+                }
+            ]
+            ListView {
+                model: pageActions
+                keyNavigationEnabled: true
+                activeFocusOnTab: true
+                delegate: Kirigami.BasicListItem {
+                    id: delegate
+                    action: modelData
+                }
+            }
+        }
+    }
+}
diff --git a/examples/simpleexamples/pushpopclear.qml b/examples/simpleexamples/pushpopclear.qml
new file mode 100644 (file)
index 0000000..b25ea57
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+
+        actions: [
+            Kirigami.Action {
+                text: "push"
+                onTriggered: pageStack.push(secondPageComponent)
+            },
+            Kirigami.Action {
+                text: "pop"
+                onTriggered: pageStack.pop()
+            },
+            Kirigami.Action {
+                text: "clear"
+                onTriggered: pageStack.clear()
+            },
+            Kirigami.Action {
+                text: "replace"
+                onTriggered: pageStack.replace(secondPageComponent)
+            }
+        ]
+    }
+
+    pageStack.initialPage: mainPageComponent
+
+    Component {
+        id: mainPageComponent
+        Kirigami.Page {
+            title: "First Page"
+            Rectangle {
+                anchors.fill: parent
+                QQC2.Label {
+                    text: "First Page"
+                }
+            }
+        }
+    }
+
+    Component {
+        id: secondPageComponent
+        Kirigami.Page {
+            title: "Second Page"
+            Rectangle {
+                color: "red"
+                anchors.fill: parent
+                QQC2.Label {
+                    text: "Second Page"
+                }
+            }
+        }
+    }
+}
diff --git a/examples/simpleexamples/simpleChatApp.qml b/examples/simpleexamples/simpleChatApp.qml
new file mode 100644 (file)
index 0000000..1c23da1
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    //header: Kirigami.ToolBarApplicationHeader {}
+    //FIXME: perhaps the default logic for going widescreen should be refined upstream
+    wideScreen: width > columnWidth * 3
+    property int columnWidth: Kirigami.Units.gridUnit * 13
+    property int footerHeight: Math.round(Kirigami.Units.gridUnit * 2.5)
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        contentItem.implicitWidth: columnWidth
+        title: "Chat App"
+        titleIcon: "konversation"
+        modal: true
+        drawerOpen: false
+        isMenu: true
+
+        actions: [
+            Kirigami.Action {
+                text: "Rooms"
+                icon.name: "view-list-icons"
+            },
+            Kirigami.Action {
+                text: "Contacts"
+                icon.name: "tag-people"
+            },
+            Kirigami.Action {
+                text: "Search"
+                icon.name: "search"
+            }
+        ]
+    }
+    contextDrawer: Kirigami.OverlayDrawer {
+        id: contextDrawer
+        //they can depend on the page like that or be defined directly here
+        edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
+        modal: !root.wideScreen
+        onModalChanged: drawerOpen = !modal
+        handleVisible: typeof applicationWindow === "undefined" ? false : applicationWindow().controlsVisible
+
+        //here padding 0 as listitems look better without as opposed to any other control
+        topPadding: 0
+        bottomPadding: 0
+        leftPadding: 0
+        rightPadding: 0
+
+        contentItem: ColumnLayout {
+            readonly property int implicitWidth: root.columnWidth
+            spacing: 0
+            QQC2.Control {
+                Layout.fillWidth: true
+                background: Rectangle {
+                    anchors.fill: parent
+                    color: Kirigami.Theme.highlightColor
+                    opacity: 0.8
+                }
+
+                padding: Kirigami.Units.gridUnit
+
+                contentItem: ColumnLayout {
+                    id: titleLayout
+                    spacing: Kirigami.Units.gridUnit
+
+                    RowLayout {
+                        spacing: Kirigami.Units.gridUnit
+                        Rectangle {
+                            color: Kirigami.Theme.highlightedTextColor
+                            radius: width
+                            implicitWidth: Kirigami.Units.iconSizes.medium
+                            implicitHeight: implicitWidth
+                        }
+                        ColumnLayout {
+                            QQC2.Label {
+                                Layout.fillWidth: true
+                                color: Kirigami.Theme.highlightedTextColor
+                                text: "KDE"
+                            }
+                            QQC2.Label {
+                                Layout.fillWidth: true
+                                color: Kirigami.Theme.highlightedTextColor
+                                font.pointSize: Kirigami.Units.fontMetrics.font.pointSize * 0.8
+                                text: "#kde: kde.org"
+                            }
+                        }
+                    }
+                    QQC2.Label {
+                        Layout.fillWidth: true
+                        color: Kirigami.Theme.highlightedTextColor
+                        text: "Main room for KDE community, other rooms are listed at kde.org/rooms"
+                        wrapMode: Text.WordWrap
+                    }
+                }
+            }
+
+            Kirigami.Separator {
+                Layout.fillWidth: true
+            }
+
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+                ListView {
+                    model: 50
+                    delegate: Kirigami.BasicListItem {
+                        label: "Person " + modelData
+                        separatorVisible: false
+                        reserveSpaceForIcon: false
+                    }
+                }
+            }
+
+            Kirigami.Separator {
+                Layout.fillWidth: true
+                Layout.maximumHeight: 1//implicitHeight
+            }
+            Kirigami.BasicListItem {
+                label: "Group call"
+                icon: "call-start"
+                separatorVisible: false
+            }
+            Kirigami.BasicListItem {
+                label: "Send Attachment"
+                icon: "mail-attachment"
+                separatorVisible: false
+            }
+        }
+    }
+
+    pageStack.defaultColumnWidth: columnWidth
+    pageStack.initialPage: [channelsComponent, chatComponent]
+
+    Component {
+        id: channelsComponent
+        Kirigami.ScrollablePage {
+            title: "Channels"
+            actions.main: Kirigami.Action {
+                icon.name: "search"
+                text: "Search"
+            }
+            background: Rectangle {
+                anchors.fill: parent
+                color: Kirigami.Theme.backgroundColor
+            }
+            footer: QQC2.ToolBar {
+                height: root.footerHeight
+                padding: Kirigami.Units.smallSpacing
+                RowLayout {
+                    anchors.fill: parent
+                    spacing: Kirigami.Units.smallSpacing
+                    //NOTE: icon support in tool button in Qt 5.11
+                    QQC2.ToolButton {
+                        Layout.fillHeight: true
+                        //make it square
+                        implicitWidth: height
+                        Kirigami.Icon {
+                            anchors.centerIn: parent
+                            width: Kirigami.Units.iconSizes.smallMedium
+                            height: width
+                            source: "configure"
+                        }
+                        onClicked: root.pageStack.layers.push(secondLayerComponent);
+                    }
+                    QQC2.ComboBox {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
+                        model: ["First", "Second", "Third"]
+                    }
+                }
+            }
+            ListView {
+                id: channelsList
+                currentIndex: 2
+                model: 30
+                delegate: Kirigami.BasicListItem {
+                    label: "#Channel " + modelData
+                    checkable: true
+                    checked: channelsList.currentIndex == index
+                    separatorVisible: false
+                    reserveSpaceForIcon: false
+                }
+            }
+        }
+    }
+
+    Component {
+        id: chatComponent
+        Kirigami.ScrollablePage {
+            title: "#KDE"
+            actions {
+                left: Kirigami.Action {
+                    icon.name: "documentinfo"
+                    text: "Channel info"
+                }
+                main: Kirigami.Action {
+                    icon.name: "search"
+                    text: "Search"
+                }
+            }
+            actions.contextualActions: [
+                Kirigami.Action {
+                    text: "Room Settings"
+                    icon.name: "configure"
+                    Kirigami.Action {
+                        text: "Setting 1"
+                    }
+                    Kirigami.Action {
+                        text: "Setting 2"
+                    }
+                },
+                Kirigami.Action {
+                    text: "Shared Media"
+                    icon.name: "document-share"
+                    Kirigami.Action {
+                        text: "Media 1"
+                    }
+                    Kirigami.Action {
+                        text: "Media 2"
+                    }
+                    Kirigami.Action {
+                        text: "Media 3"
+                    }
+                }
+            ]
+            background: Rectangle {
+                anchors.fill: parent
+                color: Kirigami.Theme.backgroundColor
+            }
+            footer: QQC2.Control {
+                height: footerHeight
+                padding: Kirigami.Units.smallSpacing
+                background: Rectangle {
+                    color: Kirigami.Theme.backgroundColor
+                    Kirigami.Separator {
+                        Rectangle {
+                            anchors.fill: parent
+                            color: Kirigami.Theme.focusColor
+                            visible: chatTextInput.activeFocus
+                        }
+                        anchors {
+                            left: parent.left
+                            right: parent.right
+                            top: parent.top
+                        }
+                    }
+                }
+                contentItem: RowLayout {
+                    QQC2.TextField {
+                        Layout.fillWidth: true
+                        id: chatTextInput
+                        background: Item {}
+                    }
+                    //NOTE: icon support in tool button in Qt 5.11
+                    QQC2.ToolButton {
+                        Layout.fillHeight: true
+                        //make it square
+                        implicitWidth: height
+                        Kirigami.Icon {
+                            anchors.centerIn: parent
+                            width: Kirigami.Units.iconSizes.smallMedium
+                            height: width
+                            source: "go-next"
+                        }
+                    }
+                }
+            }
+
+            ListView {
+                id: channelsList
+                verticalLayoutDirection: ListView.BottomToTop
+                currentIndex: 2
+                model: 30
+                delegate: Item {
+                    height: Kirigami.Units.gridUnit * 4
+                    ColumnLayout {
+                        x: Kirigami.Units.gridUnit
+                        anchors.verticalCenter: parent.verticalCenter
+                        QQC2.Label {
+                            text: modelData % 2 ? "John Doe" : "John Applebaum"
+                        }
+                        QQC2.Label {
+                            text: "Message " + modelData
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Component {
+        id: secondLayerComponent
+        Kirigami.Page {
+            title: "Settings"
+            background: Rectangle {
+                color: Kirigami.Theme.backgroundColor
+            }
+            footer: QQC2.ToolBar {
+                height: root.footerHeight
+                QQC2.ToolButton {
+                    Layout.fillHeight: true
+                    //make it square
+                    implicitWidth: height
+                    Kirigami.Icon {
+                        anchors.centerIn: parent
+                        width: Kirigami.Units.iconSizes.smallMedium
+                        height: width
+                        source: "configure"
+                    }
+                    onClicked: root.pageStack.layers.pop();
+                }
+            }
+        }
+    }
+}
diff --git a/examples/sizegroup.qml b/examples/sizegroup.qml
new file mode 100644 (file)
index 0000000..34c7906
--- /dev/null
@@ -0,0 +1,36 @@
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as Controls
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    pageStack.initialPage: Kirigami.Page {
+        Kirigami.SizeGroup {
+            items: [rect1, rect2, rect3]
+            mode: Kirigami.SizeGroup.Width
+        }
+        RowLayout {
+            id: layout
+            anchors.fill: parent
+
+            Rectangle {
+                id: rect1
+                color: "red"
+                height: 100
+                width: 400
+            }
+            Rectangle {
+                id: rect2
+                color: "green"
+                height: 100
+                implicitWidth: 150
+            }
+            Rectangle {
+                id: rect3
+                color: "blue"
+                height: 100
+                implicitWidth: 200
+            }
+        }
+    }
+}
diff --git a/examples/staticcmake/3rdparty/CMakeLists.txt b/examples/staticcmake/3rdparty/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e10afb2
--- /dev/null
@@ -0,0 +1,3 @@
+set(BUILD_SHARED_LIBS 0)
+add_subdirectory(kirigami)
diff --git a/examples/staticcmake/3rdparty/README b/examples/staticcmake/3rdparty/README
new file mode 100644 (file)
index 0000000..aad19dd
--- /dev/null
@@ -0,0 +1,6 @@
+Add here, with either a script that does a git checkout
+or as git submodules the two projects:
+
+git://anongit.kde.org/kirigami.git
+git://anongit.kde.org/breeze-icons.git
+
diff --git a/examples/staticcmake/CMakeLists.txt b/examples/staticcmake/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bf43173
--- /dev/null
@@ -0,0 +1,19 @@
+project(minimal)
+
+find_package(ECM REQUIRED CONFIG)
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
+
+set(BREEZEICONS_DIR ${CMAKE_SOURCE_DIR}/3rdparty/breeze-icons/)
+
+find_package(Qt5 REQUIRED Core Quick Multimedia Test Widgets QuickControls2)
+
+include(KDEInstallDirs)
+include(KDECompilerSettings)
+include(KDECMakeSettings)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+add_subdirectory(3rdparty)
+add_subdirectory(src)
+
diff --git a/examples/staticcmake/src/CMakeLists.txt b/examples/staticcmake/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b205a35
--- /dev/null
@@ -0,0 +1,27 @@
+
+include_directories(${CMAKE_SOURCE_DIR}/3rdparty/kirigami/src)
+include(${CMAKE_SOURCE_DIR}/3rdparty/kirigami/KF5Kirigami2Macros.cmake)
+
+set(minimal_SRCS
+    main.cpp
+    )
+
+qt_add_resources(RESOURCES kirigami-icons.qrc resources.qrc)
+
+if (ANDROID)
+    set(minimal_EXTRA_LIBS Qt5::AndroidExtras
+    #FIXME: we shouldn't have to link to it but otherwise the lib won't be packaged on Android
+    Qt5::QuickControls2)
+else ()
+#qstyle-based qqc2 style needs a QApplication
+    set(minimal_EXTRA_LIBS Qt5::Widgets)
+endif()
+
+
+add_executable(minimal ${minimal_SRCS} ${RESOURCES})
+#kirigamiplugin is the static library built by us
+target_link_libraries(minimal kirigamiplugin Qt5::Core  Qt5::Qml Qt5::Quick Qt5::QuickControls2 ${minimal_EXTRA_LIBS})
+
+#install(TARGETS minimal ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+
+kirigami_package_breeze_icons(ICONS open-menu-symbolic document-decrypt folder-sync go-next go-previous go-up handle-left handle-right view-list-icons applications-graphics media-record-symbolic)
diff --git a/examples/staticcmake/src/Page1.qml b/examples/staticcmake/src/Page1.qml
new file mode 100644 (file)
index 0000000..251ddc0
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+
+Page1Form {
+    button1.onClicked: {
+        console.log("Button Pressed. Entered text: " + textField1.text);
+    }
+}
diff --git a/examples/staticcmake/src/Page1Form.ui.qml b/examples/staticcmake/src/Page1Form.ui.qml
new file mode 100644 (file)
index 0000000..7f5b683
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.Page {
+    title: qsTr("Page 1")
+
+    property alias textField1: textField1
+    property alias button1: button1
+
+    actions {
+        main: Kirigami.Action {
+            text: "Sync"
+            icon.name: "folder-sync"
+            onTriggered: showPassiveNotification("Action clicked")
+        }
+    }
+
+    RowLayout {
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.topMargin: 20
+        anchors.top: parent.top
+
+        TextField {
+            id: textField1
+            placeholderText: qsTr("Text Field")
+        }
+
+        Button {
+            id: button1
+            text: qsTr("Press Me")
+        }
+    }
+}
diff --git a/examples/staticcmake/src/kirigami-icons.qrc b/examples/staticcmake/src/kirigami-icons.qrc
new file mode 100644 (file)
index 0000000..19c7da7
--- /dev/null
@@ -0,0 +1,15 @@
+<RCC>
+    <qresource prefix="/org/kde/kirigami/">
+        <file alias="icons/open-menu-symbolic.svg">../3rdparty/breeze-icons/icons/actions/24/open-menu-symbolic.svg</file>
+        <file alias="icons/document-decrypt.svg">../3rdparty/breeze-icons/icons/actions/32/document-decrypt.svg</file>
+        <file alias="icons/folder-sync.svg">../3rdparty/breeze-icons/icons/actions/32/folder-sync.svg</file>
+        <file alias="icons/go-next.svg">../3rdparty/breeze-icons/icons/actions/22/go-next.svg</file>
+        <file alias="icons/go-previous.svg">../3rdparty/breeze-icons/icons/actions/22/go-previous.svg</file>
+        <file alias="icons/go-up.svg">../3rdparty/breeze-icons/icons/actions/22/go-up.svg</file>
+        <file alias="icons/handle-left.svg">../3rdparty/breeze-icons/icons/actions/22/handle-left.svg</file>
+        <file alias="icons/handle-right.svg">../3rdparty/breeze-icons/icons/actions/22/handle-right.svg</file>
+        <file alias="icons/view-list-icons.svg">../3rdparty/breeze-icons/icons/actions/32/view-list-icons.svg</file>
+        <file alias="icons/applications-graphics.svg">../3rdparty/breeze-icons/icons/categories/32/applications-graphics.svg</file>
+        <file alias="icons/media-record-symbolic.svg">../3rdparty/breeze-icons/icons/actions/symbolic/media-record-symbolic.svg</file>
+    </qresource>
+</RCC>
diff --git a/examples/staticcmake/src/main.cpp b/examples/staticcmake/src/main.cpp
new file mode 100644 (file)
index 0000000..5a1e1ce
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifdef Q_OS_ANDROID
+#include <QGuiApplication>
+#else
+#include <QApplication>
+#endif
+
+#include <QColor>
+#include <QQmlApplicationEngine>
+#include <QUrl>
+#include <QtQml>
+
+#ifdef Q_OS_ANDROID
+#include <QtAndroid>
+
+// WindowManager.LayoutParams
+#define FLAG_TRANSLUCENT_STATUS 0x04000000
+#define FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 0x80000000
+// View
+#define SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 0x00002000
+
+#endif
+
+Q_IMPORT_PLUGIN(KirigamiPlugin)
+
+Q_DECL_EXPORT int main(int argc, char *argv[])
+{
+    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+// The desktop QQC2 style needs it to be a QApplication
+#ifdef Q_OS_ANDROID
+    QGuiApplication app(argc, argv);
+#else
+    QApplication app(argc, argv);
+#endif
+
+    // qputenv("QML_IMPORT_TRACE", "1");
+
+    QQmlApplicationEngine engine;
+
+    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
+
+    if (engine.rootObjects().isEmpty()) {
+        return -1;
+    }
+
+    // HACK to color the system bar on Android, use qtandroidextras and call the appropriate Java methods
+#ifdef Q_OS_ANDROID
+    QtAndroid::runOnAndroidThread([=]() {
+        QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
+        window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
+        window.callMethod<void>("setStatusBarColor", "(I)V", QColor("#2196f3").rgba());
+        window.callMethod<void>("setNavigationBarColor", "(I)V", QColor("#2196f3").rgba());
+    });
+#endif
+
+    return app.exec();
+}
diff --git a/examples/staticcmake/src/main.qml b/examples/staticcmake/src/main.qml
new file mode 100644 (file)
index 0000000..127d5c0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    visible: true
+    title: qsTr("Hello World")
+
+    pageStack.initialPage: Page1 {}
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: "Hello App"
+        titleIcon: "applications-graphics"
+        actions: [
+            Kirigami.Action {
+                text: "View"
+                icon.name: "view-list-icons"
+                Kirigami.Action {
+                    text: "action 1"
+                }
+                Kirigami.Action {
+                    text: "action 2"
+                }
+                Kirigami.Action {
+                    text: "action 3"
+                }
+            },
+            Kirigami.Action {
+                text: "action 3"
+            },
+            Kirigami.Action {
+                text: "action 4"
+            }
+        ]
+    }
+}
diff --git a/examples/staticcmake/src/qtquickcontrols2.conf b/examples/staticcmake/src/qtquickcontrols2.conf
new file mode 100644 (file)
index 0000000..c22fe2d
--- /dev/null
@@ -0,0 +1,15 @@
+; This file can be edited to change the style of the application
+; See Styling Qt Quick Controls 2 in the documentation for details:
+; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html
+
+[Controls]
+Style=Material
+
+[Universal]
+Theme=Light
+;Accent=Steel
+
+[Material]
+Theme=Light
+Accent=BlueGrey
+Primary=BlueGray
diff --git a/examples/staticcmake/src/resources.qrc b/examples/staticcmake/src/resources.qrc
new file mode 100644 (file)
index 0000000..44587bd
--- /dev/null
@@ -0,0 +1,8 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+        <file>Page1.qml</file>
+        <file>Page1Form.ui.qml</file>
+        <file>qtquickcontrols2.conf</file>
+    </qresource>
+</RCC>
diff --git a/examples/wheelhandler/FlickableUsage.qml b/examples/wheelhandler/FlickableUsage.qml
new file mode 100644 (file)
index 0000000..fb6ae76
--- /dev/null
@@ -0,0 +1,70 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.ApplicationWindow {
+    id: root
+
+    width: flickable.implicitWidth
+    height: flickable.implicitHeight
+
+    Flickable {
+        id: flickable
+
+        anchors.fill: parent
+        implicitWidth: wheelHandler.horizontalStepSize * 10 + leftMargin + rightMargin
+        implicitHeight: wheelHandler.verticalStepSize * 10 + topMargin + bottomMargin
+
+        leftMargin: QQC2.ScrollBar.vertical.visible && QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        rightMargin: QQC2.ScrollBar.vertical.visible && !QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        bottomMargin: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.height : 0
+
+        contentWidth: contentItem.childrenRect.width
+        contentHeight: contentItem.childrenRect.height
+
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+            filterMouseEvents: true
+            keyNavigationEnabled: true
+        }
+
+        QQC2.ScrollBar.vertical: QQC2.ScrollBar {
+            parent: flickable.parent
+            height: flickable.height - flickable.topMargin - flickable.bottomMargin
+            x: mirrored ? 0 : flickable.width - width
+            y: flickable.topMargin
+            active: flickable.QQC2.ScrollBar.horizontal.active
+            stepSize: wheelHandler.verticalStepSize / flickable.contentHeight
+        }
+
+        QQC2.ScrollBar.horizontal: QQC2.ScrollBar {
+            parent: flickable.parent
+            width: flickable.width - flickable.leftMargin - flickable.rightMargin
+            x: flickable.leftMargin
+            y: flickable.height - height
+            active: flickable.QQC2.ScrollBar.vertical.active
+            stepSize: wheelHandler.horizontalStepSize / flickable.contentWidth
+        }
+
+        Grid { // Example content
+            columns: Math.sqrt(visibleChildren.length)
+            Repeater {
+                model: 1000
+                delegate: Rectangle {
+                    implicitWidth: wheelHandler.horizontalStepSize
+                    implicitHeight: wheelHandler.verticalStepSize
+                    gradient: Gradient {
+                        orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                        GradientStop { position: 0; color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1) }
+                        GradientStop { position: 1; color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1) }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/examples/wheelhandler/ScrollViewUsage.qml b/examples/wheelhandler/ScrollViewUsage.qml
new file mode 100644 (file)
index 0000000..6530dad
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Templates 2.15 as T
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+T.ScrollView {
+    id: control
+
+    implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+                            contentWidth + leftPadding + rightPadding)
+    implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+                             contentHeight + topPadding + bottomPadding)
+
+    leftPadding: mirrored && T.ScrollBar.vertical.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.vertical.width : 0
+    rightPadding: !mirrored && T.ScrollBar.vertical.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.vertical.width : 0
+    bottomPadding: T.ScrollBar.horizontal.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.horizontal.height : 0
+
+    data: [
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: control.contentItem
+        }
+    ]
+
+    T.ScrollBar.vertical: QQC2.ScrollBar {
+        parent: control
+        x: control.mirrored ? 0 : control.width - width
+        y: control.topPadding
+        height: control.availableHeight
+        active: control.T.ScrollBar.horizontal.active
+        stepSize: wheelHandler.verticalStepSize / control.contentHeight
+    }
+
+    T.ScrollBar.horizontal: QQC2.ScrollBar {
+        parent: control
+        x: control.leftPadding
+        y: control.height - height
+        width: control.availableWidth
+        active: control.T.ScrollBar.vertical.active
+        stepSize: wheelHandler.horizontalStepSize / control.contentWidth
+    }
+}
diff --git a/logo.png b/logo.png
new file mode 100644 (file)
index 0000000..a63448d
Binary files /dev/null and b/logo.png differ
diff --git a/metainfo.yaml b/metainfo.yaml
new file mode 100644 (file)
index 0000000..9d1b66c
--- /dev/null
@@ -0,0 +1,24 @@
+maintainer: mart
+description: QtQuick plugins to build user interfaces based on the KDE human interface guidelines
+tier: 1
+type: functional
+platforms:
+    - name: Linux
+    - name: FreeBSD
+    - name: Android
+    - name: iOS
+      note: maintainer needed
+    - name: Windows
+    - name: macOS
+      note: maintainer needed
+public_lib: true
+deprecated: false
+release: true
+logo: logo.png
+libraries:
+ - cmake: KF5::Kirigami2
+cmakename: KF5Kirigami2
+irc: kde-kirigami
+mailinglist: plasma-devel
+group: Frameworks
+subgroup: Tier 1
diff --git a/poqm/ar/libkirigami2plugin_qt.po b/poqm/ar/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..ecbb3bb
--- /dev/null
@@ -0,0 +1,284 @@
+# Safa Alfulaij <safa1996alfulaij@gmail.com>, 2017, 2018.
+# Zayed Al-Saidi <zayed.alsaidi@gmail.com>, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-22 12:33+0400\n"
+"Last-Translator: Zayed Al-Saidi <zayed.alsaidi@gmail.com>\n"
+"Language-Team: ar\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
+"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
+"X-Generator: Lokalize 21.12.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "‏%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "زر صفحة %1 في متجر كِيدِي"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "أرسِل بريد الإلكتروني إلى %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "شاركنا"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "تبرّع"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "أبلغ عن علّة"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "حقوق النسخ"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "الرّخصة:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "الترخيص: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "المكتبات المستخدمة"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "المؤلفين"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "أظهر صور المطور"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "إشادات"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "المترجمون"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "حول %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "أنهِ"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "إجراءات أخرى"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "أزل الوسم"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "إجراءات"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "عُد"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "أغلق الشريط الجانبي"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "افتح الشريط الجانبي"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "يحمّل..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "كلمة السّرّ"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "أغلق القائمة"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "افتح القائمة"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "ابحث..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "ابحث"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "مسح سجل البحث"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "إعدادات"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "إعدادات — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "الصفحة الحالية. التقدم: %1 بالمائة"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "انتقل إلى %1. التقدم: %2 بالمائة"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "الصّفحة الحاليّة"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "انتقل إلى %1. تطلب اهتمام."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "تنقّل إلى %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "أغلق الخزنة"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "افتح الخزنة"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "تنقّل للخلف"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "تنقّل للأمام"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "إجراءات أخرى"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "انسخ الوصلة إلى الحافظة"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "إصدارة إطار كِيدِي %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "نظام نوافذ %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "كيوت %2 (بني على %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "انسخ عنوان الوصلة"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#, fuzzy
+#~| msgctxt "ContextDrawer|"
+#~| msgid "Actions"
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "إجراءات"
diff --git a/poqm/az/libkirigami2plugin_qt.po b/poqm/az/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..2d52104
--- /dev/null
@@ -0,0 +1,289 @@
+# Xəyyam <xxmn77@gmail.com>, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-07-20 08:58+0400\n"
+"Last-Translator: Kheyyam <xxmn77@gmail.com>\n"
+"Language-Team: Azerbaijani <kde-i18n-doc@kde.org>\n"
+"Language: az\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.04.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1 KDE Mağazası səhifəsini ziyarət edin"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 ünvanına e-poçt göndərin"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Iştirak edin"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Xəta hesabatı göndərin..."
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Müəllif hüquqları"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lisenziya:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lisenziya: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "İstifadə olunan kitabxana"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Müəlliflər"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Müəllifin fotoşəklləri"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Minnətdarlıq"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tərcüməçilər"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 haqqında"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Çıxış"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Daha Çox Fəaliyyətlər"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Yarlığı silin"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Əməllər"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Geriyə"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Yandakı paneli bağla"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Yan paneli açın"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Yüklənir..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Şifrə"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Bağlamaq"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Açmaq"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Axtarış"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Axtarış"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Ayarlar"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Ayarlar — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Cari səhifə. Gedişat: %1 faiz."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "İstiqamət %1. Gedişat: %2 faiz."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Cari səhifə."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "İstiqamət %1. Diqqət tələb olunur."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "İstiqamət %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Çəkməcəni bağlayın"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Çəkməcəni açın"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Geriyə hərəkət"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "İrəli hərəkət"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Bir çox işlər"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Keçidi mübadilə yaddaşına kopyalayın"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 qrafik server platforması"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 versiyadan yığılıb)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Keçid ünvanını kopyalamaq"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Axtarış..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
diff --git a/poqm/bg/libkirigami2plugin_qt.po b/poqm/bg/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..b82135f
--- /dev/null
@@ -0,0 +1,265 @@
+# Mincho Kondarev <mkondarev@yahoo.de>, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: 2022-12-23 18:28+0100\n"
+"Last-Translator: Mincho Kondarev <mkondarev@yahoo.de>\n"
+"Language-Team: Bulgarian <kde-i18n-doc@kde.org>\n"
+"Language: bg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"X-Generator: Lokalize 22.12.0\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Посетете %1 на страницата на KDE магазин"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Изпращане на имейл до %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Включете се"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Дарение"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Подаване на сигнал за грешка"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Авторско право"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Лиценз:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Лиценз: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Използвани библиотеки"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Автори"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Показване на снимки на автора"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Кредити"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Преводачи"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Относно %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Изход"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Повече действия"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Премахване на етикет"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Действия"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Назад"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Затваряне на страничната лента"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Отваряне на страничната лента"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Зареждане…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Парола"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Затваряне на меню"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Отваряне на меню"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Търсене…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Търсене"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Изчистване на търсенето"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Настройки"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Настройки — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Текуща страница. Прогрес: %1 %."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Навигиране до %1. Прогрес: %2 %."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Текуща страница."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Навигиране до %1. Изисква внимание."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Навигиране до %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Затваряне на чекмеджето"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Отваряне на чекмеджето"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Навигиране обратно"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Навигиране напред"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Повече действия"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Копиране на връзката в клипборда"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Система за прозорци %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (изграден върху %3)"
diff --git a/poqm/ca/libkirigami2plugin_qt.po b/poqm/ca/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..c772f90
--- /dev/null
@@ -0,0 +1,273 @@
+# Translation of libkirigami2plugin_qt.po to Catalan
+# Copyright (C) 2016-2022 This_file_is_part_of_KDE
+# This file is distributed under the license LGPL version 2.1 or
+# version 3 or later versions approved by the membership of KDE e.V.
+#
+# Antoni Bella Pérez <antonibella5@yahoo.com>, 2016, 2020, 2021.
+# Josep M. Ferrer <txemaq@gmail.com>, 2017, 2018, 2019, 2020, 2021, 2022.
+# Empar Montoro Martín <montoro_mde@gva.es>, 2019.
+msgid ""
+msgstr ""
+"Project-Id-Version: kirigami\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"PO-Revision-Date: 2022-12-21 10:18+0100\n"
+"Last-Translator: Josep M. Ferrer <txemaq@gmail.com>\n"
+"Language-Team: Catalan <kde-i18n-ca@kde.org>\n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Accelerator-Marker: &\n"
+"X-Generator: Lokalize 20.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visita la pàgina %1 de la KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Envia un correu a %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Col·laboreu-hi"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Donatius"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Informa d'un error"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Drets d'autor"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Llicència:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Llicència: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Biblioteques en ús"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autors"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostra les fotos dels autors"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Atribucions"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traductors"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Quant al %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Surt"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Més accions"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Elimina l'etiqueta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Accions"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Enrere"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Tanca la barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Obre la barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "S'està carregant…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Contrasenya"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Tanca el menú"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Obre el menú"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Cerca…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Cerca"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Neteja la cerca"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Arranjament"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configuració — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Pàgina actual. Progrés: %1 per cent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navega a %1. Progrés: %2 per cent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Pàgina actual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navega a %1. Exigeix atenció."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navega a %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Tanca el calaix"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Obre el calaix"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navega enrere"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navega endavant"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Més accions"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copia l'enllaç al porta-retalls"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "Frameworks %1 del KDE"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "El sistema de finestres %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (construïdes amb %3)"
diff --git a/poqm/ca@valencia/libkirigami2plugin_qt.po b/poqm/ca@valencia/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..156c057
--- /dev/null
@@ -0,0 +1,273 @@
+# Translation of libkirigami2plugin_qt.po to Catalan (Valencian)
+# Copyright (C) 2016-2022 This_file_is_part_of_KDE
+# This file is distributed under the license LGPL version 2.1 or
+# version 3 or later versions approved by the membership of KDE e.V.
+#
+# Antoni Bella Pérez <antonibella5@yahoo.com>, 2016, 2020, 2021.
+# Josep M. Ferrer <txemaq@gmail.com>, 2017, 2018, 2019, 2020, 2021, 2022.
+# Empar Montoro Martín <montoro_mde@gva.es>, 2019.
+msgid ""
+msgstr ""
+"Project-Id-Version: kirigami\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"PO-Revision-Date: 2022-12-21 10:18+0100\n"
+"Last-Translator: Josep M. Ferrer <txemaq@gmail.com>\n"
+"Language-Team: Catalan <kde-i18n-ca@kde.org>\n"
+"Language: ca@valencia\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Accelerator-Marker: &\n"
+"X-Generator: Lokalize 20.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visita la pàgina %1 de la KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Envia un correu a %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Col·laboreu-hi"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Donatius"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Informeu d'un error"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Drets d'autoria"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Llicència:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Llicència: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Biblioteques en ús"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autoria"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostra les fotos dels autors"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Atribucions"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Equip de traducció"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Quant a %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Ix"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Més accions"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Elimina l'etiqueta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Accions"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Arrere"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Tanca la barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Obri la barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "S'està carregant…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Contrasenya"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Tanca el menú"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Obri el menú"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Busca…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Busca"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Neteja la busca"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configuració"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configuració — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Pàgina actual. Progrés: %1 per cent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navega a %1. Progrés: %2 per cent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Pàgina actual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navega a %1. Exigix atenció."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navega a %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Tanca el calaix"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Obri el calaix"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navega arrere"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navega avant"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Més accions"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copia l'enllaç al porta-retalls"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "Frameworks %1 de KDE"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "El sistema de finestres %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (construïdes amb %3)"
diff --git a/poqm/cs/libkirigami2plugin_qt.po b/poqm/cs/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..c6bb76c
--- /dev/null
@@ -0,0 +1,265 @@
+# Vít Pelčák <vit@pelcak.org>, 2016, 2017, 2018, 2019, 2020.
+# Vit Pelcak <vit@pelcak.org>, 2021, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-02-21 09:39+0100\n"
+"Last-Translator: Vit Pelcak <vit@pelcak.org>\n"
+"Language-Team: Czech <kde-i18n-doc@kde.org>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Lokalize 22.12.2\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Navštívit stránku KDE Store od %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Odeslat e-mail na %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Zapojte se"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Přispět"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Nahlásit chybu"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Autorská práva"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licence:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licence: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Použité knihovny"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autoři"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Zobrazit fotografie autora"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Poděkování"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Překladatelé"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "O aplikaci %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Ukončit"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Další činnosti"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Odstranit značku"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Činnosti"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Zpět"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Zavřít postranní panel"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Otevřít postranní panel"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Probíhá načítání…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Heslo"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Uzavřít nabídku"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Otevřít nabídku"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Hledat…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Hledat"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Vyprázdnit hledání"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Nastavení"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Nastavení — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Současná stránka. Postup: %1 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Přejít na %1. Postup: %2 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Aktuální stránka."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Přejít na %1. Vyžadovat pozornost."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Přejít na %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Zavřít šuplík"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Otevřít šuplík"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Přejít zpět"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Přejít vpřed"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Další činnosti"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopírovat odkaz do schránky"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Okenní systém %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (sestaveno oproti %3)"
diff --git a/poqm/da/libkirigami2plugin_qt.po b/poqm/da/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..d7b0eae
--- /dev/null
@@ -0,0 +1,318 @@
+# Martin Schlander <mschlander@opensuse.org>, 2017, 2018, 2019, 2020.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2020-10-27 19:16+0100\n"
+"Last-Translator: Martin Schlander <mschlander@opensuse.org>\n"
+"Language-Team: Danish <kde-i18n-doc@kde.org>\n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 20.04.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Send an email to %1"
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Send en e-mail til %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Ophavsret"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licens:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licens: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Biblioteker i brug"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Ophavsmænd"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Tak"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Oversættere"
+
+#: controls/AboutPage.qml:101
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "About"
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Om"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr ""
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Flere handlinger"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr ""
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Handlinger"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Tilbage"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Luk sidepanel"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Luk sidepanel"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Adgangskode"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Luk sidepanel"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search"
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Søg"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Søg"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, fuzzy, qt-format
+#| msgctxt "ForwardButton|"
+#| msgid "Navigate Forward"
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigér fremad"
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Luk sidepanel"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigér tilbage"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigér fremad"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Flere handlinger"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Vinduessystemet %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (bygget op imod %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopiér linkadresse"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Søg..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Flere handlinger"
diff --git a/poqm/de/libkirigami2plugin_qt.po b/poqm/de/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..cf445ec
--- /dev/null
@@ -0,0 +1,289 @@
+# Frederik Schwarzer <schwarzer@kde.org>, 2016, 2018, 2020, 2021, 2022, 2023.
+# Burkhard Lück <lueck@hube-lueck.de>, 2017, 2018, 2019, 2020, 2021.
+# Alois Spitzbart <spitz234@hotmail.com>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-03-21 19:49+0100\n"
+"Last-Translator: Frederik Schwarzer <schwarzer@kde.org>\n"
+"Language-Team: German <kde-i18n-de@kde.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 22.12.3\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Besuchen Sie %1 im KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Eine E-Mail an %1 senden"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Machen Sie mit"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Spenden"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Probleme oder Wünsche berichten"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lizenz:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lizenz: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Verwendete Bibliotheken"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autoren"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Fotos von Autoren anzeigen"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Danksagungen"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Übersetzer"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Über %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Beenden"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Weitere Aktionen"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Stichwort entfernen"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Aktionen"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Zurück"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Seitenleiste schließen"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Seitenleiste öffnen"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Wird geladen ..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Passwort"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Menü schließen"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Menü öffnen"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Suchen ..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Suchen"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Suche leeren"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Einstellungen — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Aktuelle Seite. Fortschritt: %1 Prozent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Zu %1 gehen. Fortschritt: %2 Prozent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Aktuelle Seite."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Zu %1 gehen. Erfordert Aufmerksamkeit."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Zu %1 gehen."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Seitenleiste schließen"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Seitenleiste öffnen"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Zurück gehen"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Vorwärts gehen"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Weitere Aktionen"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Verknüpfung in die Zwischenablage kopieren"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Das Fenstersystem %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (kompiliert gegen %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Verknüpfungsadresse kopieren"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Suchen ..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Weitere Aktionen"
diff --git a/poqm/el/libkirigami2plugin_qt.po b/poqm/el/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..37ee6a2
--- /dev/null
@@ -0,0 +1,298 @@
+# Stelios <sstavra@gmail.com>, 2017, 2020, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2021-10-06 10:25+0300\n"
+"Last-Translator: Stelios <sstavra@gmail.com>\n"
+"Language-Team: Greek <kde-i18n-el@kde.org>\n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 20.04.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Επισκεφθείτε τη σελίδα της αποθήκης του KDE για το %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Αποστολή μηνύματος στο %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Ασχοληθείτε"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Αναφορά σφάλματος…"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Άδεια χρήσης:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Άδεια χρήσης: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Βιβλιοθήκες σε χρήση"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Συγγραφείς"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Εμφάνιση φωτογραφιών συγγραφέων"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Ευχαριστίες"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Μεταφραστές"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Περίγραμμα %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Έξοδος"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Περισσότερες ενέργειες"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Ενέργειες"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Πίσω"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Κλείσιμο πλευρικής γραμμής"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Κλείσιμο πλευρικής γραμμής"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Κωδικός πρόσβασης"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Κλείσιμο"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Άνοιγμα"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Αναζήτηση…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Αναζήτηση"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Ρυθμίσεις"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Ρυθμίσεις — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Τρέχουσα σελίδα. Πρόοδος: %1 τοις εκατό."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Πλοήγηση προς το %1. Πρόοδος: %2 τοις εκατό."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Τρέχουσα σελίδα."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Πλοήγηση προς το %1. Δώστε προσοχή."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Πλοήγηση προς το %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Κλείσιμο πλευρικής γραμμής"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Πλοήγηση προς τα πίσω"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Πλοήγηση προς τα εμπρός"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Περισσότερες ενέργειες"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Το %1 παραθυρικό σύστημα"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (κατασκευάστηκε με βάση το %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Αντιγραφή διεύθυνσης δεσμού"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Αναζήτηση..."
+
+#, fuzzy
+#~| msgctxt "ContextDrawer|"
+#~| msgid "Actions"
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Ενέργειες"
diff --git a/poqm/en_GB/libkirigami2plugin_qt.po b/poqm/en_GB/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..8ed8cd5
--- /dev/null
@@ -0,0 +1,284 @@
+# Steve Allewell <steve.allewell@gmail.com>, 2016, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-31 15:37+0000\n"
+"Last-Translator: Steve Allewell <steve.allewell@gmail.com>\n"
+"Language-Team: British English <kde-l10n-en_gb@kde.org>\n"
+"Language: en_GB\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 21.12.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visit %1's KDE Store page"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Send an email to %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Get Involved"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Donate"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Report a Bug"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licence:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licence: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Libraries in use"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Authors"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Show author photos"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Credits"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Translators"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "About %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Quit"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "More Actions"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Remove Tag"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Actions"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Back"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Close Sidebar"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Open Sidebar"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Loading…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Password"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Close menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Open menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Search…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Search"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Clear search"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Settings"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Settings — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Current page. Progress: %1 percent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navigate to %1. Progress: %2 percent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Current page."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navigate to %1. Demanding attention."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigate to %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Close drawer"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Open drawer"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigate Back"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigate Forward"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "More Actions"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copy Link to Clipboard"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "The %1 windowing system"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (built against %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copy link address"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Search..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "More Actions"
diff --git a/poqm/es/libkirigami2plugin_qt.po b/poqm/es/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..6e149a2
--- /dev/null
@@ -0,0 +1,288 @@
+# Eloy Cuadra <ecuadra@eloihr.net>, 2016, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigamiplugin_qt\n"
+"PO-Revision-Date: 2022-12-22 00:22+0100\n"
+"Last-Translator: Eloy Cuadra <ecuadra@eloihr.net>\n"
+"Language-Team: Spanish <kde-l10n-es@kde.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visitar la página de %1 en KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Enviar un correo electrónico a %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Involucrarse"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Donar"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Informar de fallo"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licencia:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licencia: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliotecas en uso"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autores"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostrar fotos del autor"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Créditos"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traductores"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Acerca de %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Salir"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Más acciones"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Eliminar etiqueta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Acciones"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Atrás"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Cerrar la barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Abrir la barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Cargando..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Contraseña"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Cerrar menú"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Abrir menú"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Buscar..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Buscar"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Borrar la búsqueda"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Preferencias"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Preferencias — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Página actual. Avance: %1 por ciento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navegar hasta %1. Avance: %2 por ciento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Página actual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navegar hasta %1. Solicitando atención."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navegar hasta %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Cerrar cajón"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Abrir cajón"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Retroceder una página"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Avanzar una página"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Más acciones"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copiar enlace en el portapapeles"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "El sistema de ventanas %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (compilado para %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copiar la dirección del enlace"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Todavía en carga. Espere por favor..."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Buscar..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Más acciones"
diff --git a/poqm/et/libkirigami2plugin_qt.po b/poqm/et/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..7e5d07e
--- /dev/null
@@ -0,0 +1,315 @@
+# Marek Laane <qiilaq69@gmail.com>, 2019, 2020.
+# Mihkel Tõnnov <mihhkel@gmail.com>, 2020.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2020-10-07 19:58+0200\n"
+"Last-Translator: Mihkel Tõnnov <mihhkel@gmail.com>\n"
+"Language-Team: Estonian <>\n"
+"Language: et\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 20.08.1\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Send an email to %1"
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Saada e-kiri aadressile %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Autoriõigus"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Litsents:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Litsents: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Kasutatavad teegid"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autorid"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Tunnustus"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tõlkijad"
+
+#: controls/AboutPage.qml:101
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "About"
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Teave"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr ""
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Veel toiminguid"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr ""
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Toimingud"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Tagasi"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Sulge külgriba"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Sulge külgriba"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Parool"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Sulge külgriba"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search"
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Otsi"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Otsi"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, fuzzy, qt-format
+#| msgctxt "ForwardButton|"
+#| msgid "Navigate Forward"
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Liigu edasi"
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Sulge külgriba"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Liigu tagasi"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Liigu edasi"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Veel toiminguid"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 aknahaldur"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (ehitatud %3 peale)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopeeri lingi aadress"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Otsi ..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
diff --git a/poqm/eu/libkirigami2plugin_qt.po b/poqm/eu/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..800ca16
--- /dev/null
@@ -0,0 +1,293 @@
+# Translation for libkirigami2plugin_qt.po to Euskara/Basque (eu).
+# Copyright (C) 2017-2022 This file is copyright:
+# This file is distributed under the same license as the original file.
+# KDE euskaratzeko proiektuko arduraduna <xalba@ni.eus>.
+#
+# Translators:
+# Iñigo Salvador Azurmendi <xalba@ni.eus>, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: kirigami\n"
+"PO-Revision-Date: 2022-12-28 22:20+0100\n"
+"Last-Translator: Iñigo Salvador Azurmendi <xalba@ni.eus>\n"
+"Language-Team: Basque <kde-i18n-eu@kde.org>\n"
+"Language: eu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 22.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Bisitatu %1(r)(e)n KDE Biltegiko orria"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Bidali e-posta honi: %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Engaia zaitez"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Egin dohaintza"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Akats baten berri ematea"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lizentzia:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lizentzia: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Erabiltzen ari diren liburutegiak"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Egileak"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Erakutsi egilearen argazkiak"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Merituak"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Itzultzaileak"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1(e)ri buruz"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Irten"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Ekintza gehiago"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Kendu etiketa"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Ekintzak"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Atzera"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Itxi alboko-barra"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Itxi alboko-barra"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Zamatzen…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Pasahitza"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Itxi menua"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Ireki menua"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Bilatu..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Bilatu..."
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Garbitu bilaketa"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Ezarpenak"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Ezarpenak — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Uneko orria. Aurrerapena: ehuneko %1."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Nabigatu %1(e)ra. Aurrerapena: ehuneko %2."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Uneko orria."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Nabigatu %1(e)ra. Arreta eskatuz."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Nabigatu %1(e)ra."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Itxi tiradera"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Ireki tiradera"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Nabigatu atzera"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Nabigatu aurrera"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Ekintza gehiago"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopiatu esteka arbelera"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 leiho-sistema"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 erabiliz eraikia)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopiatu estekaren helbidea"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Bilatu..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Ekintza gehiago"
diff --git a/poqm/fi/libkirigami2plugin_qt.po b/poqm/fi/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..324df15
--- /dev/null
@@ -0,0 +1,281 @@
+# Lasse Liehu <lasse.liehu@gmail.com>, 2017.
+# Tommi Nieminen <translator@legisign.org>, 2018, 2019, 2020, 2021, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-03-12 10:42+0200\n"
+"Last-Translator: Tommi Nieminen <translator@legisign.org>\n"
+"Language-Team: Finnish <kde-i18n-doc@kde.org>\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 22.12.3\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Käy KDE Storen %1 -sivulla"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Lähetä sähköpostia: %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Ota osaa"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Lahjoita"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Ilmoita viasta"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Tekijänoikeudet"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lisenssi:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lisenssi: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Käytetyt kirjastot"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Tekijät"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Näytä tekijöiden valokuvat"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Kiitokset"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Kääntäjät"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Tietoa – %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Lopeta"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Lisää toimintoja"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Poista luokitus"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Toiminnot"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Takaisin"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Sulje sivupaneeli"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Avaa sivupaneeli"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Ladataan…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Salasana"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Sulje valikko"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Avaa valikko"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Etsi…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Etsi"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Tyhjennä haku"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Asetukset"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Asetukset – %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Nykyinen sivu. Edistyminen: %1 %."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Siirry kohteeseen %1. Edistyminen: %2 %."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Nykyinen sivu."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Siirry kohteeseen %1. Vaatii huomiota."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Siirry kohteeseen %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Sulje laatikko"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Avaa laatikko"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Siirry taaksepäin"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Siirry eteenpäin"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Lisää toimintoja"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopioi linkki leikepöydälle"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Ikkunointijärjestelmä %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (koostettu kirjastolla %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopioi linkin osoite"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Etsi…"
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Lisää toimintoja"
diff --git a/poqm/fr/libkirigami2plugin_qt.po b/poqm/fr/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..6ca4d7a
--- /dev/null
@@ -0,0 +1,293 @@
+# Vincent Pinon <vpinon@kde.org>, 2016, 2017.
+# Simon Depiets <sdepiets@gmail.com>, 2018, 2019.
+# Xavier Besnard <xavier.besnard@neuf.fr>, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-22 18:50+0100\n"
+"Last-Translator: Xavier Besnard <xavier.besnard@neuf.fr>\n"
+"Language-Team: French <kde-francophone@kde.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Lokalize 22.12.0\n"
+"X-Environment: kde\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visiter la page de la boutique de KDE pour %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Envoyer un courriel à %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Soyez impliqué"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Faire un don"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Signaler un bogue"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Droit d'auteur"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licence :"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licence : %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliothèques en cours d'utilisation"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Auteurs"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Afficher les photos des auteurs"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Crédits"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traducteurs"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "À propos de %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Quitter"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Plus d'actions"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Supprimer une étiquette"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Actions"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Retour"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Fermer la barre latérale"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Ouvrir une barre latérale"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Chargement en cours..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Mot de passe"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Fermer le menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Ouvrir le menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Rechercher…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Rechercher"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Effacer une recherche"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configuration"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configuration — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Page actuelle. Avancement : %1 pourcent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Naviguer vers %1. Avancement : %2 pourcent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Page actuelle."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Naviguer vers %1. Attention requise."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigation vers %1"
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Fermer un tiroir"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Ouvrir un tiroir"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigation arrière"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigation avant"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Actions supplémentaires"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copier un lien dans le presse-papier"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Le système de fenêtres %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (compilé avec %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copier l'adresse du lien"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Encore en cours de téléchargement, veuillez patienter."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Chercher…"
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Actions supplémentaires"
diff --git a/poqm/gl/libkirigami2plugin_qt.po b/poqm/gl/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..dcfd265
--- /dev/null
@@ -0,0 +1,281 @@
+# Adrián Chaves Fernández (Gallaecio) <adriyetichaves@gmail.com>, 2017.
+# Adrián Chaves (Gallaecio) <adrian@chaves.io>, 2018, 2019, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-05-12 00:14+0200\n"
+"Last-Translator: Adrián Chaves (Gallaecio) <adrian@chaves.io>\n"
+"Language-Team: Galician <proxecto@trasno.gal>\n"
+"Language: gl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 23.04.0\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visitar a páxina na tenda de KDE de %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Enviar unha mensaxe de correo electrónico a %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Involucrarse"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Doar"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Informar dun fallo"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Dereitos de copia"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licenza:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licenza: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliotecas usadas"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autoría"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostrar fotos das persoas autoras"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Recoñecementos"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tradución"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Sobre %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Saír"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Máis accións"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Retirar a etiqueta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Accións"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Atrás"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Pechar a barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Abrir a barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Cargando…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Contrasinal"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Pechar o menú"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Abre o menú"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Buscar…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Buscar"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Borrar a busca"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configuración"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configuración — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Páxina actual. Progreso: %1 por cento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navegar a %1. Progreso: %2 por cento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Páxina actual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navegar a %1. Require atención."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navegar a %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Pechar o caixón"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Abrir o caixón"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Volver"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Continuar"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Máis accións"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copiar a ligazón no portapapeis"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "Versión %1 das infraestruturas de KDE"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "O sistema de xanelas %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (construído sobre %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copiar o enderezo da ligazón"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Buscar…"
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Máis accións"
diff --git a/poqm/hi/libkirigami2plugin_qt.po b/poqm/hi/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..33f1c02
--- /dev/null
@@ -0,0 +1,319 @@
+# Raghavendra Kamath <raghu@raghukamath.com>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2021-08-21 10:53+0530\n"
+"Last-Translator: Raghavendra Kamath <raghu@raghukamath.com>\n"
+"Language-Team: kde-hindi\n"
+"Language: hi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=(n!=1);\n"
+"X-Generator: Lokalize 21.08.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Visit %1's KDE Store page"
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1 के केडीई स्टोर पृष्ठ पर जाएँ"
+
+#: controls/AboutItem.qml:151
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Send an email to %1"
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 को ईमेल भेजें"
+
+#: controls/AboutItem.qml:201
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Get Involved"
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "सहभाग लें"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "सर्वाधिकार"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "अनुज्ञापत्र :"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "अनुज्ञापत्र : %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "उपयोग किए गए संग्रह"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "लेखक"
+
+#: controls/AboutItem.qml:353
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Show author photos"
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "लेखक की फोटोओं को दिखाएँ"
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "आभार सूची"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "अनुवादक"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 के बारे में"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "बाहर जाएँ"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "अधिक क्रियाएँ"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "क्रियाएं"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "पीछे"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "बाजूपट्टी बंद करें"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "बाजूपट्टी बंद करें"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "कूटशब्द"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "बंद करें"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "खोलें"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "खोजें…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "खोजें"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "विन्यास"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, fuzzy, qt-format
+#| msgctxt "CategorizedSettings|"
+#| msgid "Settings"
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "विन्यास"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "वर्तमान पृष्ठ। प्रगती : %1 प्रतिशत।"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1 पर जाएँ। प्रगती : %2 प्रतिशत।"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "वर्तमान पृष्ठ।"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1 पर जाएँ। ध्यान देने की मांग कर रहा है।"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1 पर जाएँ।"
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "बाजूपट्टी बंद करें"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "पीछे जाएँ"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "आगे जाएँ"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "अधिक क्रियाएँ"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "केडीई फ्रेमवर्कस %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 विंडो प्रणाली"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "क्यूट %2 (%3 के प्रती निर्मित)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "लिंक पता नक़ल करें"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
diff --git a/poqm/hu/libkirigami2plugin_qt.po b/poqm/hu/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..6e624e3
--- /dev/null
@@ -0,0 +1,296 @@
+# Kiszel Kristóf <kiszel.kristof@gmail.com>, 2017, 2018, 2020, 2021.
+# Kristof Kiszel <ulysses@fsf.hu>, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-02-09 17:29+0100\n"
+"Last-Translator: Kristof Kiszel <ulysses@fsf.hu>\n"
+"Language-Team: Hungarian <kde-l10n-hu@kde.org>\n"
+"Language: hu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 21.07.70\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Ugrás a(z) %1 KDE Store oldalára"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "E-mail küldése neki: %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Közreműködés"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Hibabejelentés…"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licenc:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licenc: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Felhasznált függvénykönyvtárak"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Szerzők"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "A szerző fényképének megjelenítése"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Köszönetnyilvánítás"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Fordítók"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Névjegy: %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Kilépés"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "További műveletek"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Címke eltávolítása"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Műveletek"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Vissza"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Oldalsáv bezárása"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Oldalsáv bezárása"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Jelszó"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Bezárás"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Megnyitás"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Keresés…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Keresés"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Beállítások"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Beállítások — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Jelenlegi oldal. Folyamat: %1 százalék."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Ugrás ide: %1. Folyamat. %2 százalék."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Jelenlegi oldal."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Ugrás ide: %1. Beavatkozást igényel."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Ugrás ide: %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Oldalsáv bezárása"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Vissza"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Előre"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "További műveletek"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "A(z) %1 ablakkezelő rendszer"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (fordítva ezzel: %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Hivatkozás címének másolása"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Keresés…"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "További műveletek"
diff --git a/poqm/ia/libkirigami2plugin_qt.po b/poqm/ia/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..632f53f
--- /dev/null
@@ -0,0 +1,291 @@
+# giovanni <g.sora@tiscali.it>, 2017, 2019, 2020, 2021, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-02-08 22:40+0100\n"
+"Last-Translator: giovanni <g.sora@tiscali.it>\n"
+"Language-Team: Interlingua <kde-i18n-doc@kde.org>\n"
+"Language: ia\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.12.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visita pagina %1 de KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Invia un message de e-posta a  %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Sea involvite"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Dona"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Reporta un bug"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licentia:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licentia: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliothecas in uso"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autores"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Monstra photos de autor"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Gratias"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traductores"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "A proposito de %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Abandona"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Ulterior Actiones"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Remove etiquetta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Actiones"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Retro"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Claude barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Aperi barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Cargante..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Contrasigno"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Claude menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Aperi menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Cerca…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Cerca"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Netta cerca"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Preferentias"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Preferentias — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Pagina currente. Progresso: %1 percent"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Naviga a %1. Progresso: %2 percent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Pagina currente."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navigante a %1. Demandante attention."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Naviga a %1"
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Claude designator"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Aperi designator"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigation de retro"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Naviga Avante"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Ulterior Actiones"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copia ligame a area de transferentia"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Le %1 systema de fenestra"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (construite sur %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copia adresse de ligamine"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Ancora cargante, pro favor, tu attende."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Cerca..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#, fuzzy
+#~| msgctxt "ContextDrawer|"
+#~| msgid "Actions"
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Actiones"
diff --git a/poqm/id/libkirigami2plugin_qt.po b/poqm/id/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..886c588
--- /dev/null
@@ -0,0 +1,286 @@
+# Wantoyo <wantoyek@gmail.com>, 2018, 2019, 2020, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-09-27 23:06+0700\n"
+"Last-Translator: Wantoyèk <wantoyek@gmail.com>\n"
+"Language-Team: Indonesian <kde-i18n-doc@kde.org>\n"
+"Language: id\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 21.12.3\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Kunjungilah halaman KDE Store %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Kirim sebuah email ke %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Ikut Terlibat"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Laporkan Bug..."
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Hak Cipta"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lisensi:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lisensi: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Pustaka lib yang digunakan"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Penulis"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Tampilkan foto penulis"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Kredit"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Penerjemah"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Tentang %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Berhenti"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Aksi Selebihnya"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Hapus Tag"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Aksi"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Mundur"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Tutup Bilah Sisi"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Buka Bilah Sisi"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Memuat..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Password"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close drawer"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Tutup penggambar"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Cari..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Cari"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Pengaturan"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Pengaturan — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Halaman saat ini. Progres: %1 persen."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navigasi ke %1. Progres: %2 persen."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Halaman saat ini."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navigasi ke %1. Memerlukan perhatian."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigasi ke %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Tutup penggambar"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Buka penggambar"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigasi Mundur"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigasi Maju"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Aksi Selebihnya"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Salin Tautan ke Papan Klip"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Sistem perjendelaan %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (dibangun terhadap %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Salin alamat tautan"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Cari..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Aksi Selebihnya"
diff --git a/poqm/it/libkirigami2plugin_qt.po b/poqm/it/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..f44fe24
--- /dev/null
@@ -0,0 +1,284 @@
+# Vincenzo Reale <smart2128vr@gmail.com>, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-21 23:00+0100\n"
+"Last-Translator: Vincenzo Reale <smart2128vr@gmail.com>\n"
+"Language-Team: Italian <kde-i18n-it@kde.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 22.12.0\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visita la pagina del KDE Store di %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Invia un messaggio di posta elettronica a %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Partecipa"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Fai una donazione"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Segnala un bug"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licenza:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licenza: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Librerie in uso"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autori"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostra le foto degli autori"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Riconoscimenti"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traduttori"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Informazioni su %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Esci"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Altre azioni"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Rimuovi etichetta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Azioni"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Indietro"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Chiudi la barra laterale"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Apri la barra laterale"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Caricamento…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Password"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Chiudi il menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Apri il menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Cerca…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Cerca"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Cancella la ricerca"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Impostazioni"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Impostazioni — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Pagina attuale. Avanzamento: %1 percento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Vai a %1. Avanzamento: %2 percento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Pagina attuale."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Vai a %1. Richiede attenzione."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Vai a %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Chiudi il cassetto"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Apri il cassetto"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Naviga indietro"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Naviga in avanti"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Altre azioni"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copia il collegamento negli appunti"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Il sistema di gestione delle finestre %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (compilato con %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copia indirizzo del collegamento"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Cerca..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Altre azioni"
diff --git a/poqm/ja/libkirigami2plugin_qt.po b/poqm/ja/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..38632a7
--- /dev/null
@@ -0,0 +1,266 @@
+# Ryuichi Yamada <ryuichi_ya220@outlook.jp>, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigamiplugin_qt\n"
+"PO-Revision-Date: 2023-02-22 22:44+0900\n"
+"Last-Translator: Ryuichi Yamada <ryuichi_ya220@outlook.jp>\n"
+"Language-Team: Japanese <kde-jp@kde.org>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Qt-Contexts: true\n"
+"X-Generator: Lokalize 22.12.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1 の KDE Store ページを訪れる"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 にメールを送る"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "参加する"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "寄付する"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "バグを報告"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "著作権について"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "ライセンス:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "ライセンス: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "使用されているライブラリ"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "作者"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "作者の写真を表示"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "クレジット"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "翻訳者"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 について"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "終了"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "その他のアクション"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "タグを削除"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "アクション"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "戻る"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "サイドバーを閉じる"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "サイドバーを開く"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "読み込み中..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "パスワード"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "メニューを閉じる"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "メニューを開く"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "検索..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "検索"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "検索をクリア"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "設定"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "設定 — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "現在のページです。進捗: %1 パーセント。"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "現在のページです。"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr ""
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "ドロアを閉じる"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "ドロアを開く"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "戻る"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "進む"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "その他のアクション"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "リンクをクリップボードにコピー"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 ウィンドウシステム"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 を用いてビルドされました)"
diff --git a/poqm/ka/libkirigami2plugin_qt.po b/poqm/ka/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..8100cd1
--- /dev/null
@@ -0,0 +1,264 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
+"Language-Team: \n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Qt-Contexts: true\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "ეწვიეთ %1-ის KDE მაღაზიის გვერდს"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1-სთვის ელფოსტის გაგზავნა"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "შემოგვიერთდით"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "შემოწირულობა"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "შეცდომის პატაკი"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "საავტორო უფლებები"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "ლიცენზია:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "ლიცენზია: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "გამოყენებული ბიბლიოთეკები"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "ავტორები"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "ავტორის ფოტოების ჩვენება"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "კრედიტები"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "მთარგმნელები"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1-ის შესახებ"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "დასრულება"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "მეტი ქმედება"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "ჭდის წაშლა"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "ქმედებები"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "უკან"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "გვერდითი ზოლის დახურვა"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "გვერდითი ზოლის გახსნა"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "ჩატვირთვა…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "პაროლი"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "მენიუს დახურვა"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "მენიუს გახსნა"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "ძებნა…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "ძებნა"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "ძიების გასუფთავება"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "&მორგება"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "პარამეტრები — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "მიმდინარე გვერდი. პროგრესი: %1 პროცენტი."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1-ზეგადასვლა. პროგრესი: %2 პროცენტი."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "მიმდინარე გვერდი."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1-ზე გადასვლა. საჭიროა ყურადღება."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1-ზე გადასვლა."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "უჯრის დახურვა"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "უჯრის გახსნა"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "უკან გადასვლა"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "წინ გადასვლა"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "მეტი ქმედება"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "ბმულის ბმულის ბაფერში კოპირება"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "ფანჯრული სისტემა: %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (აგებულია %3-ით)"
diff --git a/poqm/ko/libkirigami2plugin_qt.po b/poqm/ko/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..2694c4e
--- /dev/null
@@ -0,0 +1,288 @@
+# Shinjo Park <kde@peremen.name>, 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-03-01 02:06+0100\n"
+"Last-Translator: Shinjo Park <kde@peremen.name>\n"
+"Language-Team: Korean <kde-kr@kde.org>\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.3\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1(%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1의 KDE 스토어 페이지 방문"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1(으)로 이메일 보내기"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "참여하기"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "기부하기"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "버그 보고"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "저작권"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "라이선스:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "라이선스: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "사용하는 라이브러리"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "작성자"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "작성자 사진 표시"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "제작진"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "번역자"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 정보"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "끝내기"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "더 많은 동작"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "태그 삭제"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "동작"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "뒤로"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "사이드바 닫기"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "사이드바 열기"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "불러오는 중…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "암호"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "메뉴 닫기"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "메뉴 열기"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "검색…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "검색"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "검색 지우기"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "설정"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "설정 — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "현재 쪽입니다. 진행 상황: %1퍼센트."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1(으)로 탐색합니다. 진행 상황: %2퍼센트."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "현재 쪽입니다."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1(으)로 탐색합니다. 주목을 기다립니다."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1(으)로 탐색합니다."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "서랍 닫기"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "서랍 열기"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "이전 탐색"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "다음 탐색"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "더 많은 동작"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "클립보드에 링크 복사"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE 프레임워크 %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 창 시스템"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2(%3(으)로 빌드됨)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "링크 주소 복사"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "불러오고 있습니다. 잠시 기다려 주십시오."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "찾기..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "더 많은 동작"
diff --git a/poqm/lt/libkirigami2plugin_qt.po b/poqm/lt/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..056dd03
--- /dev/null
@@ -0,0 +1,323 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: trunk-kf 5\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Moo\n"
+"Language-Team: lt\n"
+"Language: lt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n%10>=2 && (n%100<10 || n"
+"%100>=20) ? 1 : n%10==0 || (n%100>10 && n%100<20) ? 2 : 3);\n"
+"X-Generator: Poedit 2.2.4\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Autorių teisės"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licencija:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licencija: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Naudojamos bibliotekos"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autoriai"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Padėkos"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Vertėjai"
+
+#: controls/AboutPage.qml:101
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "About"
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Apie"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr ""
+
+#: controls/ActionToolBar.qml:208
+#, fuzzy
+#| msgctxt "ToolBarApplicationHeader|"
+#| msgid "More Actions"
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Daugiau veiksmų"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr ""
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Veiksmai"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Atgal"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Užverti šoninę juostą"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Užverti šoninę juostą"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Slaptažodis"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Užverti šoninę juostą"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search..."
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Ieškoti..."
+
+#: controls/SearchField.qml:96
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search..."
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Ieškoti..."
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, fuzzy, qt-format
+#| msgctxt "ForwardButton|"
+#| msgid "Navigate Forward"
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Naršyti pirmyn"
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Užverti šoninę juostą"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Naršyti atgal"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Naršyti pirmyn"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Daugiau veiksmų"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 langų sistema"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (sudaryta remiantis %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopijuoti nuorodos adresą"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Ieškoti..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Daugiau veiksmų"
diff --git a/poqm/ml/libkirigami2plugin_qt.po b/poqm/ml/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..3c0454f
--- /dev/null
@@ -0,0 +1,262 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: Swathanthra|സ്വതന്ത്ര Malayalam|മലയാളം Computing|കമ്പ്യൂട്ടിങ്ങ് <smc."
+"org.in>\n"
+"Language: ml\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr ""
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr ""
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr ""
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr ""
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr ""
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr ""
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr ""
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr ""
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr ""
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr ""
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr ""
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr ""
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr ""
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr ""
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr ""
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr ""
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr ""
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr ""
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr ""
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr ""
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr ""
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr ""
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr ""
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
diff --git a/poqm/nl/libkirigami2plugin_qt.po b/poqm/nl/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..110932e
--- /dev/null
@@ -0,0 +1,288 @@
+# Freek de Kruijf <freekdekruijf@kde.nl>, 2016, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-21 16:42+0100\n"
+"Last-Translator: Freek de Kruijf <freekdekruijf@kde.nl>\n"
+"Language-Team: \n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Bezoek de KDE Store-pagina van %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Een e-mail sturen naar %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Doe mee"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Doneren"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Een bug rapporteren"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licentie:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licentie: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliotheken in gebruik"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Auteurs"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Foto's van auteurs tonen"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Dankbetuigingen"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Vertalers"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Info over %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Afsluiten"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Meer acties"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Tag verwijderen"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Acties"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Terug"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Zijbalk sluiten"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Zijbalk openen"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Bezig met laden…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Wachtwoord"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Menu sluiten"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Menu openen"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Zoeken…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Zoeken"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Gezochte wissen"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Instellingen"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Instellingen — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Huidige pagina. Voortgang: %1 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Naar %1 navigeren. Voortgang: %2 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Huidige pagina."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Naar %1 navigeren. Vraagt om aandacht."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Naar %1 navigeren."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Schuiflade sluiten"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Schuiflade openen"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Naar achteren navigeren"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Naar voren navigeren"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Meer acties"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Koppeling naar klembord kopiëren"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Het venstersysteem %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (gebouwd tegen %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Koppelingsadres kopiëren"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Nog steeds bezig met laden, even geduld."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Zoeken..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Meer acties"
diff --git a/poqm/nn/libkirigami2plugin_qt.po b/poqm/nn/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..2075fa6
--- /dev/null
@@ -0,0 +1,269 @@
+# Translation of libkirigami6_qt to Norwegian Nynorsk
+#
+# Karl Ove Hufthammer <karl@huftis.org>, 2016, 2018, 2019, 2020, 2021, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-03-04 17:18+0100\n"
+"Last-Translator: Karl Ove Hufthammer <karl@huftis.org>\n"
+"Language-Team: Norwegian Nynorsk <l10n-no@lister.huftis.org>\n"
+"Language: nn\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.12.2\n"
+"X-Environment: kde\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Gå til %1-sida i KDE-butikken"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Send e-post til %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Vert med"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Gje pengegåve"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Meld frå om feil"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Opphavsrett"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lisens:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lisens: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Brukte bibliotek"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Opphavspersonar"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Vis bilete av opphavspersonane"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Kreditering"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Omsetjarar"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Om %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Avslutt"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Fleire handlingar"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 – %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Fjern merkelapp"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Handlingar"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Tilbake"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Lukk sidestolpen"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Opna sidestolpe"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Lastar …"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Passord"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Lukk meny"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Opna meny"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Søk …"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Søk"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Tøm søkjefelt"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Innstillingar"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Innstillingar – %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Gjeldande side. Framgang: %1 prosent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Gå til %1. Framgang: %2 prosent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Gjeldande side."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Gå til %1. Ber om merksemd."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Gå til %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Lukk skuff"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Opna skuff"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Naviger tilbake"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Naviger fram"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Fleire handlingar"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopier lenkja til utklippstavla"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Vindaugssystemet %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (bygd mot %3)"
diff --git a/poqm/pa/libkirigami2plugin_qt.po b/poqm/pa/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..c47f5b3
--- /dev/null
@@ -0,0 +1,322 @@
+# A S Alam <aalam.yellow@gmail.com>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2021-04-25 13:00-0700\n"
+"Last-Translator: A S Alam <aalam@satluj.org>\n"
+"Language-Team: Punjabi <punjabi-users@lists.sf.net>\n"
+"Language: pa\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 20.08.1\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Send an email to %1"
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 ਨੂੰ ਈਮੇਲ ਭੇਜੋ"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "ਕਾਪੀਰਾਈਟ"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "ਲਸੰਸ:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "ਲਸੰਸ: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "ਵਰਤਣ ਲਈ ਲਾਇਬਰੇਰੀਆਂ"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "ਲੇਖਕ"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "ਮਾਣ"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "ਉਲੱਥਾਕਾਰ"
+
+#: controls/AboutPage.qml:101
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "About"
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "ਇਸ ਬਾਰੇ"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "ਬਾਹਰ"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "ਹੋਰ ਕਾਰਵਾਈਆਂ"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "ਕਾਰਵਾਈਆਂ"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "ਪਿੱਛੇ"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "ਬਾਹੀ ਬੰਦ ਕਰੋ"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "ਬਾਹੀ ਬੰਦ ਕਰੋ"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "ਪਾਸਵਰਡ"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "ਬੰਦ ਕਰੋ"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "ਖੋਲ੍ਹੋ"
+
+#: controls/SearchField.qml:94
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search"
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "ਖੋਜੋ"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "ਖੋਜੋ"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, fuzzy, qt-format
+#| msgctxt "PrivateSwipeTab|"
+#| msgid "Current page. Progress: %1 percent."
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "ਮੌਜੂਦਾ ਸਫ਼ਾ। ਤਰੱਕੀ: %1 ਫ਼ੀਸਦੀ"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, fuzzy, qt-format
+#| msgctxt "PrivateSwipeTab|"
+#| msgid "Navigate to %1. Progress: %2 percent."
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1 ਉੱਤੇ ਜਾਓ। ਤਰੱਕੀ: %2 ਫ਼ੀਸਦੀ"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+#, fuzzy
+#| msgctxt "PrivateSwipeTab|"
+#| msgid "Current page."
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "ਮੌਜੂਦਾ ਸਫ਼ਾ।"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, fuzzy, qt-format
+#| msgctxt "PrivateSwipeTab|"
+#| msgid "Navigate to %1. Demanding attention."
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1 ਉੱਤੇ ਜਾਓ। ਧਿਆਨ ਦੇਣ ਦੀ ਲੋੜ ਹੈ।"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, fuzzy, qt-format
+#| msgctxt "PrivateSwipeTab|"
+#| msgid "Navigate to %1."
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1 ਉੱਤੇ ਜਾਓ।"
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "ਬਾਹੀ ਬੰਦ ਕਰੋ"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "ਪਿੱਛੇ ਜਾਓ"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "ਅੱਗੇ ਜਾਓ"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "ਹੋਰ ਕਾਰਵਾਈਆਂ"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE ਫਰੇਮਵਰਕਸ %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 ਵਿੰਡੋਇੰਗ ਸਿਸਟਮ"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 ਨਾਲ ਬਣਾਇਆ)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "ਲਿੰਕ ਐਡਰੈਸ ਕਾਪੀ ਕਰੋ"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "ਖੋਜੋ..."
diff --git a/poqm/pl/libkirigami2plugin_qt.po b/poqm/pl/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..31b3692
--- /dev/null
@@ -0,0 +1,285 @@
+# Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>, 2016, 2017, 2018, 2019, 2021, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-02-12 09:20+0100\n"
+"Last-Translator: Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>\n"
+"Language-Team: Polish <kde-i18n-doc@kde.org>\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Lokalize 22.12.2\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Odwiedź stronę sklepu KDE %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Wyślij wiadomość do %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Współtwórz"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Darowizna"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Zgłoś błąd"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Prawa autorskie"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licencja:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licencja: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Używane biblioteki"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autorzy"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Pokaż zdjęcia autorów"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Zasługi"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tłumacze"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "O %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Zakończ"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Więcej działań"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Usuń znacznik"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Działania"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Wstecz"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Zamknij pasek boczny"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Otwórz pasek boczny"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Wczytywanie..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Hasło"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Zamknij menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Otwórz menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Poszukaj..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Szukaj"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Wyczyść wyszukiwanie"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Ustawienia"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Ustawienia — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Bieżąca strona. Postęp: %1 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Przejdź do %1. Postęp: %2 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Bieżąca strona."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Przejdź do %1. Wymaga uwagi."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Przejdź do %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Zamknij szufladę"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Otwórz szufladę"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Przejdź wstecz"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Przejdź naprzód"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Więcej działań"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Skopiuj odnośnik do schowka"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "Szkielety KDE %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "System okien %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (zbudowany na %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Skopiuj adres odnośnika"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Szukaj..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Więcej działań"
diff --git a/poqm/pt/libkirigami2plugin_qt.po b/poqm/pt/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..a54a349
--- /dev/null
@@ -0,0 +1,266 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2022-12-23 20:43+0000\n"
+"Last-Translator: José Nuno Coelho Pires <zepires@gmail.com>\n"
+"Language-Team: Portuguese <kde-i18n-pt@kde.org>\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visitar a página do %1 na Loja do KDE"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Enviar um e-mail para %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Envolva-se"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Doar"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Comunicar um Erro"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "'Copyright'"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licença:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licença: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliotecas usadas"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autores"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostrar as fotografias dos autores"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Créditos"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tradutores"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Acerca do %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Sair"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Mais Acções"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Remover a Marca"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Acções"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Recuar"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Fechar a Barra Lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Abrir a Barra Lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "A carregar…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Senha"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Fechar o menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Abrir o menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Procurar…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Procurar"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Limpar a pesquisa"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configuração"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configuração ̣̣— %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Página actual. Progresso: %1 por cento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navegar para %1. Progresso: %2 por cento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Página actual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navegar para %1. A chamar a atenção."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navegar para %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Fechar a área"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Abrir a área"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navegar para Trás"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navegar para a Frente"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Mais Acções"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copiar a Ligação para a Área de Transferência"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "Plataformas do KDE %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "O sistema de janelas %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (compilado com o %3)"
diff --git a/poqm/pt_BR/libkirigami2plugin_qt.po b/poqm/pt_BR/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..adaef94
--- /dev/null
@@ -0,0 +1,287 @@
+# Translation of libkirigami2plugin_qt.po to Brazilian Portuguese
+# Copyright (C) 2016-2019 This_file_is_part_of_KDE
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Luiz Fernando Ranghetti <elchevive@opensuse.org>, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+# André Marcelo Alvarenga <alvarenga@kde.org>, 2018, 2019.
+# Thiago Masato Costa Sueto <herzenschein@gmail.com>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"PO-Revision-Date: 2023-05-31 14:23-0300\n"
+"Last-Translator: Luiz Fernando Ranghetti <elchevive@opensuse.org>\n"
+"Language-Team: Brazilian Portuguese <kde-i18n-pt_BR@kde.org>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Lokalize 21.12.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Visite a página do %1 na KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Enviar e-mail para %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Participe"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Doar"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Relatar erro"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licença:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licença: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Bibliotecas em uso"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autores"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Mostrar as fotos dos autores"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Créditos"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Tradutores"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Sobre %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Sair"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Mais ações"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Remover etiqueta"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Ações"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Voltar"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Fechar barra lateral"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Abrir barra lateral"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Carregando..."
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Senha"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Fechar menu"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Abrir menu"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Pesquisar..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Pesquisar"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Limpar pesquisa"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configurações"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configurações — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Página atual. Progresso: %1 porcento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navegar para %1. Progresso: %2 porcento."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Página atual."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navegar para %1. Demandando atenção."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navegar para %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Fechar gaveta"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Abrir gaveta"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Voltar"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Avançar"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Mais ações"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copiar link para a área de transferência"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "O sistema de janelas %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (compilado com %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copiar endereço do link"
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Pesquisar..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
diff --git a/poqm/ro/libkirigami2plugin_qt.po b/poqm/ro/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..757a8fd
--- /dev/null
@@ -0,0 +1,289 @@
+# Sergiu Bivol <sergiu@cip.md>, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-28 20:59+0000\n"
+"Last-Translator: Sergiu Bivol <sergiu@cip.md>\n"
+"Language-Team: Romanian <kde-i18n-ro@kde.org>\n"
+"Language: ro\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
+"20)) ? 1 : 2;\n"
+"X-Generator: Lokalize 21.12.3\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Vizitează pagina %1 din Magazinul KDE"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Trimite scrisoare către %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Implică-te"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Raportează defect…"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Drept de autor"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licență:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licență: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Biblioteci folosite"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autori"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Arată pozele autorilor"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Mulțumiri"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Traducători"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Despre %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Termină"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Acțiuni suplimentare"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Elimină marcajul"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Acțiuni"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Înapoi"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Închide bara laterală"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Deschide bara laterală"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Se încarcă…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Parolă"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Închide"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Deschide"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Caută…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Caută"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Configurări"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Configurări — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Pagina actuală. Progres: %1 procente."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navighează la %1. Progres: %2 procente."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Pagina actuală."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navighează la %1. Cere atenție."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navighează la %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Închide sertarul"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Deschide sertarul"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navighează înapoi"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navighează înainte"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Acțiuni suplimentare"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Copiază legătura în clipboard"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Sistemul de ferestre %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (construit cu %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Copiază adresa legăturii"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Caută..."
diff --git a/poqm/ru/libkirigami2plugin_qt.po b/poqm/ru/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..9fb29be
--- /dev/null
@@ -0,0 +1,283 @@
+# Alexander Potashev <aspotashev@gmail.com>, 2016, 2017, 2019.
+# Alexander Yavorsky <kekcuha@gmail.com>, 2019, 2020, 2021.
+# Мария Шикунова <translation-team@basealt.ru>, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-05-04 11:41+0300\n"
+"Last-Translator: Olesya Gerasimenko <translation-team@basealt.ru>\n"
+"Language-Team: Basealt Translation Team\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Lokalize 22.12.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Перейти на страницу %1 в магазине приложений KDE"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Отправить письмо %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Присоединиться к команде"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Сделать пожертвование"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Сообщить об ошибке"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Авторские права"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Лицензия:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Лицензия: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Используемые библиотеки"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Авторы"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Показать фотографии авторов"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Благодарности"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Переводчики"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "О программе %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Выход"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Больше действий"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Удаление метки"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Действия"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Назад"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Закрыть боковую панель"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Открыть боковую панель"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Загрузка…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Пароль"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Закрыть меню"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Открыть меню"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Поиск…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Поиск"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Очистить поиск"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Параметры"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Параметры — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Текущая страница. Ход выполнения: %1%."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Переход к %1. Ход выполнения: %2%."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Текущая страница."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Перейти к %1. Требует внимания."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Перейти к %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Закрыть панель"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Открыть панель"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Перейти назад"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Перейти вперёд"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Больше действий"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Копировать ссылку в буфер обмена"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Платформа графического сервера %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (собрана с версией %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Скопировать адрес ссылки"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Поиск..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Больше действий"
diff --git a/poqm/sk/libkirigami2plugin_qt.po b/poqm/sk/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..58008af
--- /dev/null
@@ -0,0 +1,339 @@
+# translation of libkirigamiplugin_qt.po to Slovak
+# Roman Paholik <wizzardsk@gmail.com>, 2016, 2019.
+# Mthw <jari_45@hotmail.com>, 2019.
+# Matej Mrenica <matejm98mthw@gmail.com>, 2019, 2020, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigamiplugin_qt\n"
+"Report-Msgid-Bugs-To: http://bugs.kde.org\n"
+"POT-Creation-Date: 2016-08-05 07:24+0000\n"
+"PO-Revision-Date: 2021-09-05 19:34+0200\n"
+"Last-Translator: Matej Mrenica <matejm98mthw@gmail.com>\n"
+"Language-Team: Slovak <kde-i18n-doc@kde.org>\n"
+"Language: sk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 21.08.1\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Visit %1's KDE Store page"
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Navštívte stránku obchodu KDE %1"
+
+#: controls/AboutItem.qml:151
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "Send an email to %1"
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Odoslať email na adresu %1"
+
+#: controls/AboutItem.qml:201
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Get Involved"
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Zapojiť sa"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Nahlásiť chybu..."
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licencia:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licencia: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Použité knižnice"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Autori"
+
+#: controls/AboutItem.qml:353
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Show author photos"
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Zobraziť fotografie autora"
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Zásluhy"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Preklady"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "O  %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Ukončiť"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Viac akcií"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Akcie"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Späť"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Zatvoriť bočný panel"
+
+#: controls/GlobalDrawer.qml:602
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Zatvoriť bočný panel"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Heslo"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Zatvoriť"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Otvoriť"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Hľadať..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Hľadať"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Nastavenia"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, fuzzy, qt-format
+#| msgctxt "CategorizedSettings|"
+#| msgid "Settings"
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Nastavenia"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Aktuálna stránka. Postup: %1 percent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navigovať do %1. Postup: %2 percent. "
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Aktuálna stránka."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navigovať do %1. Vyžaduje si pozornosť."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigovať do %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+#, fuzzy
+#| msgctxt "GlobalDrawer|"
+#| msgid "Close Sidebar"
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Zatvoriť bočný panel"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigovať späť"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigovať dopredu"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Viac akcií"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworky %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "The %1 systém okien"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (zostavené s %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Skopírovať adresu odkazu"
+
+#, fuzzy
+#~| msgctxt "AboutPage|"
+#~| msgid "(%1)"
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Hľadať..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Viac akcií"
diff --git a/poqm/sl/libkirigami2plugin_qt.po b/poqm/sl/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..6fee399
--- /dev/null
@@ -0,0 +1,278 @@
+# Slovenian translation of kirigami
+# Copyright (C) YEAR This_file_is_part_of_KDE
+# This file is distributed under the same license as the PACKAGE package.
+# Andrej Mernik <andrejm@ubuntu.si>, 2016, 2018.
+# Matjaž Jeran <matjaz.jeran@amis.net>, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: 2022-12-22 07:42+0100\n"
+"Last-Translator: Matjaž Jeran <matjaz.jeran@amis.net>\n"
+"Language-Team: Slovenian <lugos-slo@lugos.si>\n"
+"Language: sl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n"
+"%100<=4 ? 2 : 3);\n"
+"X-Qt-Contexts: true\n"
+"X-Generator: Lokalize 22.08.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Obiščite stran %1 v KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Pošljite e-pošto za %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Sodelujte"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Donirajte"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Poročajte o napaki"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Dovoljenje:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Dovoljenje: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Knjižnice v rabi"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Avtorji"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Prikaži fotografije avtorjev"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Zasluge"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Prevajalci"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "O programu %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Zapusti"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Več dejanj"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Odstrani značko"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Dejanja"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Nazaj"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Zapri stransko letvico"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Odpri stransko letvico"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Nalaganje…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Geslo"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Zapri meni"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Odpri meni"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Poišči …"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Išči"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Počisti iskanje"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Nastavitve"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Nastavitve — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Trenutna stran. Napredek: %1 odst."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Krmari proti %1. Napredek: %2 odst."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Trenutna stran."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Krmari proti %1. Zahteva pozornost."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Krmari proti %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Zapri predal"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Odpri predal"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Krmari nazaj"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Krmari naprej"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Več dejanj"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopiraj povezavo na odložišče"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Sistem oken %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (zgrajeno za %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopiraj naslov povezave"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Še vedno nalagam, prosim počakajte."
diff --git a/poqm/sr/libkirigami2plugin_qt.po b/poqm/sr/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..9a24d6c
--- /dev/null
@@ -0,0 +1,131 @@
+# Translation of libkirigami2plugin_qt.po into Serbian.
+# Chusslove Illich <caslav.ilic@gmx.net>, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2017-10-06 17:14+0200\n"
+"Last-Translator: Chusslove Illich <caslav.ilic@gmx.net>\n"
+"Language-Team: Serbian <kde-i18n-sr@kde.org>\n"
+"Language: sr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Environment: kde\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutPage.qml:66
+msgctxt "AboutPage|"
+msgid "About"
+msgstr ""
+
+#: controls/AboutPage.qml:103
+#, qt-format
+msgctxt "AboutPage|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutPage.qml:152
+msgctxt "AboutPage|"
+msgid "Copyright"
+msgstr ""
+
+#: controls/AboutPage.qml:175
+msgctxt "AboutPage|"
+msgid "License:"
+msgstr ""
+
+#: controls/AboutPage.qml:188
+#, qt-format
+msgctxt "AboutPage|"
+msgid "License: %1"
+msgstr ""
+
+#: controls/AboutPage.qml:199
+msgctxt "AboutPage|"
+msgid "Libraries in use"
+msgstr ""
+
+#: controls/AboutPage.qml:213
+msgctxt "AboutPage|"
+msgid "Authors"
+msgstr ""
+
+#: controls/AboutPage.qml:223
+msgctxt "AboutPage|"
+msgid "Credits"
+msgstr ""
+
+#: controls/AboutPage.qml:234
+msgctxt "AboutPage|"
+msgid "Translators"
+msgstr ""
+
+#: controls/ContextDrawer.qml:66
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Радње"
+
+#: controls/GlobalDrawer.qml:466
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Назад"
+
+#: controls/GlobalDrawer.qml:557
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/PasswordField.qml:32
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr ""
+
+#: controls/SearchField.qml:31
+msgctxt "SearchField|"
+msgid "Search..."
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:34
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Иди назад"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Иди напред"
+
+#: controls/ToolBarApplicationHeader.qml:113
+#, fuzzy
+#| msgctxt "ContextDrawer|"
+#| msgid "Actions"
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Радње"
+
+#: controls/UrlButton.qml:45
+msgctxt "UrlButton|"
+msgid "Copy link address"
+msgstr ""
+
+#: settings.cpp:197
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:199
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:200
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
diff --git a/poqm/sr@ijekavian/libkirigami2plugin_qt.po b/poqm/sr@ijekavian/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..a12310f
--- /dev/null
@@ -0,0 +1,131 @@
+# Translation of libkirigami2plugin_qt.po into Serbian.
+# Chusslove Illich <caslav.ilic@gmx.net>, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2017-10-06 17:14+0200\n"
+"Last-Translator: Chusslove Illich <caslav.ilic@gmx.net>\n"
+"Language-Team: Serbian <kde-i18n-sr@kde.org>\n"
+"Language: sr@ijekavian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Environment: kde\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutPage.qml:66
+msgctxt "AboutPage|"
+msgid "About"
+msgstr ""
+
+#: controls/AboutPage.qml:103
+#, qt-format
+msgctxt "AboutPage|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutPage.qml:152
+msgctxt "AboutPage|"
+msgid "Copyright"
+msgstr ""
+
+#: controls/AboutPage.qml:175
+msgctxt "AboutPage|"
+msgid "License:"
+msgstr ""
+
+#: controls/AboutPage.qml:188
+#, qt-format
+msgctxt "AboutPage|"
+msgid "License: %1"
+msgstr ""
+
+#: controls/AboutPage.qml:199
+msgctxt "AboutPage|"
+msgid "Libraries in use"
+msgstr ""
+
+#: controls/AboutPage.qml:213
+msgctxt "AboutPage|"
+msgid "Authors"
+msgstr ""
+
+#: controls/AboutPage.qml:223
+msgctxt "AboutPage|"
+msgid "Credits"
+msgstr ""
+
+#: controls/AboutPage.qml:234
+msgctxt "AboutPage|"
+msgid "Translators"
+msgstr ""
+
+#: controls/ContextDrawer.qml:66
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Радње"
+
+#: controls/GlobalDrawer.qml:466
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Назад"
+
+#: controls/GlobalDrawer.qml:557
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/PasswordField.qml:32
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr ""
+
+#: controls/SearchField.qml:31
+msgctxt "SearchField|"
+msgid "Search..."
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:34
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Иди назад"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Иди напред"
+
+#: controls/ToolBarApplicationHeader.qml:113
+#, fuzzy
+#| msgctxt "ContextDrawer|"
+#| msgid "Actions"
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Радње"
+
+#: controls/UrlButton.qml:45
+msgctxt "UrlButton|"
+msgid "Copy link address"
+msgstr ""
+
+#: settings.cpp:197
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:199
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:200
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
diff --git a/poqm/sr@ijekavianlatin/libkirigami2plugin_qt.po b/poqm/sr@ijekavianlatin/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..bd40cb7
--- /dev/null
@@ -0,0 +1,131 @@
+# Translation of libkirigami2plugin_qt.po into Serbian.
+# Chusslove Illich <caslav.ilic@gmx.net>, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2017-10-06 17:14+0200\n"
+"Last-Translator: Chusslove Illich <caslav.ilic@gmx.net>\n"
+"Language-Team: Serbian <kde-i18n-sr@kde.org>\n"
+"Language: sr@ijekavianlatin\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Environment: kde\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutPage.qml:66
+msgctxt "AboutPage|"
+msgid "About"
+msgstr ""
+
+#: controls/AboutPage.qml:103
+#, qt-format
+msgctxt "AboutPage|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutPage.qml:152
+msgctxt "AboutPage|"
+msgid "Copyright"
+msgstr ""
+
+#: controls/AboutPage.qml:175
+msgctxt "AboutPage|"
+msgid "License:"
+msgstr ""
+
+#: controls/AboutPage.qml:188
+#, qt-format
+msgctxt "AboutPage|"
+msgid "License: %1"
+msgstr ""
+
+#: controls/AboutPage.qml:199
+msgctxt "AboutPage|"
+msgid "Libraries in use"
+msgstr ""
+
+#: controls/AboutPage.qml:213
+msgctxt "AboutPage|"
+msgid "Authors"
+msgstr ""
+
+#: controls/AboutPage.qml:223
+msgctxt "AboutPage|"
+msgid "Credits"
+msgstr ""
+
+#: controls/AboutPage.qml:234
+msgctxt "AboutPage|"
+msgid "Translators"
+msgstr ""
+
+#: controls/ContextDrawer.qml:66
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Radnje"
+
+#: controls/GlobalDrawer.qml:466
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Nazad"
+
+#: controls/GlobalDrawer.qml:557
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/PasswordField.qml:32
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr ""
+
+#: controls/SearchField.qml:31
+msgctxt "SearchField|"
+msgid "Search..."
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:34
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Idi nazad"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Idi napred"
+
+#: controls/ToolBarApplicationHeader.qml:113
+#, fuzzy
+#| msgctxt "ContextDrawer|"
+#| msgid "Actions"
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Radnje"
+
+#: controls/UrlButton.qml:45
+msgctxt "UrlButton|"
+msgid "Copy link address"
+msgstr ""
+
+#: settings.cpp:197
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:199
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:200
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
diff --git a/poqm/sr@latin/libkirigami2plugin_qt.po b/poqm/sr@latin/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..ac6ea34
--- /dev/null
@@ -0,0 +1,131 @@
+# Translation of libkirigami2plugin_qt.po into Serbian.
+# Chusslove Illich <caslav.ilic@gmx.net>, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2017-10-06 17:14+0200\n"
+"Last-Translator: Chusslove Illich <caslav.ilic@gmx.net>\n"
+"Language-Team: Serbian <kde-i18n-sr@kde.org>\n"
+"Language: sr@latin\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Accelerator-Marker: &\n"
+"X-Text-Markup: qtrich\n"
+"X-Environment: kde\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutPage.qml:66
+msgctxt "AboutPage|"
+msgid "About"
+msgstr ""
+
+#: controls/AboutPage.qml:103
+#, qt-format
+msgctxt "AboutPage|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutPage.qml:152
+msgctxt "AboutPage|"
+msgid "Copyright"
+msgstr ""
+
+#: controls/AboutPage.qml:175
+msgctxt "AboutPage|"
+msgid "License:"
+msgstr ""
+
+#: controls/AboutPage.qml:188
+#, qt-format
+msgctxt "AboutPage|"
+msgid "License: %1"
+msgstr ""
+
+#: controls/AboutPage.qml:199
+msgctxt "AboutPage|"
+msgid "Libraries in use"
+msgstr ""
+
+#: controls/AboutPage.qml:213
+msgctxt "AboutPage|"
+msgid "Authors"
+msgstr ""
+
+#: controls/AboutPage.qml:223
+msgctxt "AboutPage|"
+msgid "Credits"
+msgstr ""
+
+#: controls/AboutPage.qml:234
+msgctxt "AboutPage|"
+msgid "Translators"
+msgstr ""
+
+#: controls/ContextDrawer.qml:66
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Radnje"
+
+#: controls/GlobalDrawer.qml:466
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Nazad"
+
+#: controls/GlobalDrawer.qml:557
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/PasswordField.qml:32
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr ""
+
+#: controls/SearchField.qml:31
+msgctxt "SearchField|"
+msgid "Search..."
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:34
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Idi nazad"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Idi napred"
+
+#: controls/ToolBarApplicationHeader.qml:113
+#, fuzzy
+#| msgctxt "ContextDrawer|"
+#| msgid "Actions"
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Radnje"
+
+#: controls/UrlButton.qml:45
+msgctxt "UrlButton|"
+msgid "Copy link address"
+msgstr ""
+
+#: settings.cpp:197
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:199
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:200
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
diff --git a/poqm/sv/libkirigami2plugin_qt.po b/poqm/sv/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..eeed1ff
--- /dev/null
@@ -0,0 +1,297 @@
+# Stefan Asserhäll <stefan.asserhall@bredband.net>, 2016, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-08-03 10:08+0200\n"
+"Last-Translator: Stefan Asserhäll <stefan.asserhall@bredband.net>\n"
+"Language-Team: Swedish <kde-i18n-doc@kde.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 20.08.1\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Besök KDE-butikens sida för %1"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Skicka e-post till %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Engagera dig"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Report Bug…"
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Rapportera fel…"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Licens:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Licens: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Använda bibliotek"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Upphovsmän"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Visa foton av upphovsmän"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Tack till"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Översättare"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Om %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Avsluta"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Fler åtgärder"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Ta bort etikett"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Åtgärder"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Tillbaka"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Stäng sidorad"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Öppna sidorad"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Läser in…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Lösenord"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Close"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Stäng"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+#, fuzzy
+#| msgctxt "OverlayDrawer|"
+#| msgid "Open"
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Öppna"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Sök…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Sök"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Inställningar"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Inställningar — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Aktuell sida. Förlopp: %1 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Navigera till %1. Förlopp: %2 procent."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Aktuell sida."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Navigera till %1. Kräver uppmärksamhet."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Navigera till %1."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Stäng låda"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Öppna låda"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Navigera bakåt"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Navigera framåt"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Fler åtgärder"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Kopiera länk till klippbordet"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Ramverk %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Fönsterhanteringssystemet %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (byggt för %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Kopiera länkadress"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Läser fortfarande in, vänta."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Sök..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Fler åtgärder"
diff --git a/poqm/ta/libkirigami2plugin_qt.po b/poqm/ta/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..77839b6
--- /dev/null
@@ -0,0 +1,276 @@
+# Kishore G <kishore96@gmail.com>, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2022-12-22 21:27+0530\n"
+"Last-Translator: Kishore G <kishore96@gmail.com>\n"
+"Language-Team: Tamil <kde-i18n-doc@kde.org>\n"
+"Language: ta\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 22.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1-இன் கே.டீ.யீ. கடைவீதி பக்கத்தை பாருங்கள்"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 என்பவருக்கு மின்னஞ்சல் அனுப்பு"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "பங்களித்தல்"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "நன்கொடை அளி"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "பிழையை தெரிவி"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "பதிப்புரிமை"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "உரிமம்:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "உரிமம்: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "பயன்படுத்தும் நிரலகங்கள்"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "இயற்றியவர்கள்"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "இயற்றியவர்களின் படங்களைக் காட்டு"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "நன்றி"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "மொழிபெயர்ப்பாளர்கள்"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 பற்றி "
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "வெளியேறு"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "மேலும் செயல்கள்"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "குறிச்சொல்லை நீக்கு"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "செயல்கள்"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "பின்னே"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "ஓரப்பட்டையை மூடு"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "ஓரப்பட்டையை திற"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "ஏற்றப்படுகிறது…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "கடவுச்சொல்"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "பட்டியை மூடு"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "பட்டியைத் திற"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "தேடு..."
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "தேடல்"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "தேடலை காலியாக்கு"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "அமைப்புகள்"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "அமைப்புகள் — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "தற்போதைய பக்கம். முன்னேற்றம்: %1 சதவீதம்."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1-க்கு செல். முன்னேற்றம்: %2 சதவீதம்."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "தற்போதைய பக்கம்"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1-க்கு செல். கவனத்தை கோருகிறது."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1-க்கு செல்."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "மேல்தோன்றும் பலகையை மூடு"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "மேல்தோன்றும் பலகையைத் திற"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "பின்னே செல்"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "முன்னே செல்"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "மேலும் செயல்கள்"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "இணைப்பை பிடிப்புப்பலகைக்கு நகலெடு"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "கே.டீ.யீ. நிரலகங்கள் %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 சாளர நெறிமுறை"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 கொண்டு தொகுக்கப்பட்டது)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "இணைப்பின் முகவரியை நகலெடு"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "இன்னும் ஏற்றப்படுகிறது, காத்திருங்கள்."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
diff --git a/poqm/tg/libkirigami2plugin_qt.po b/poqm/tg/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..284302e
--- /dev/null
@@ -0,0 +1,313 @@
+# Victor Ibragimov <victor.ibragimov@gmail.com>, 2019.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2019-08-16 20:02+0500\n"
+"Last-Translator: Victor Ibragimov <victor.ibragimov@gmail.com>\n"
+"Language-Team: English <kde-i18n-doc@kde.org>\n"
+"Language: tg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Lokalize 19.04.3\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr ""
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr ""
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr ""
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr ""
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr ""
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr ""
+
+#: controls/AboutItem.qml:237
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Copyright"
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Ҳуқуқи муаллиф"
+
+#: controls/AboutItem.qml:281
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "License:"
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Иҷозатнома:"
+
+#: controls/AboutItem.qml:303
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "License: %1"
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Иҷозатнома: %1"
+
+#: controls/AboutItem.qml:314
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Libraries in use"
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Китобхонаҳое, ки истифода мешаванд"
+
+#: controls/AboutItem.qml:344
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Authors"
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Муаллифон"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr ""
+
+#: controls/AboutItem.qml:384
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Credits"
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Сипосгузорӣ"
+
+#: controls/AboutItem.qml:397
+#, fuzzy
+#| msgctxt "AboutPage|"
+#| msgid "Translators"
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Тарҷумонон"
+
+#: controls/AboutPage.qml:101
+#, fuzzy, qt-format
+#| msgctxt "AboutPage|"
+#| msgid "About"
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Дар бораи барнома"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr ""
+
+#: controls/ActionToolBar.qml:208
+#, fuzzy
+#| msgctxt "ToolBarApplicationHeader|"
+#| msgid "More Actions"
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Амалҳои бештар"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr ""
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr ""
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Амалҳо"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Ба қафо"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr ""
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr ""
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr ""
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Ниҳонвожа"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr ""
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr ""
+
+#: controls/SearchField.qml:94
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search..."
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Ҷустуҷӯ..."
+
+#: controls/SearchField.qml:96
+#, fuzzy
+#| msgctxt "SearchField|"
+#| msgid "Search..."
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Ҷустуҷӯ..."
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr ""
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr ""
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr ""
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, fuzzy, qt-format
+#| msgctxt "ForwardButton|"
+#| msgid "Navigate Forward"
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Ба пеш паймудан"
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr ""
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr ""
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Ба қафо паймудан"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Ба пеш паймудан"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Амалҳои бештар"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr ""
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr ""
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr ""
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr ""
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Нусха бардоштани нишонии пайванд"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Ҷустуҷӯ..."
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Амалҳои бештар"
diff --git a/poqm/tr/libkirigami2plugin_qt.po b/poqm/tr/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..0306758
--- /dev/null
@@ -0,0 +1,281 @@
+# Volkan Gezer <volkangezer@gmail.com>, 2021.
+# Emir SARI <emir_sari@icloud.com>, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2023-02-01 14:04+0300\n"
+"Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
+"Language-Team: Turkish <kde-l10n-tr@kde.org>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Lokalize 22.12.1\n"
+"X-Qt-Contexts: true\n"
+"X-POOTLE-MTIME: 1497529432.000000\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "%1 KDE Mağaza sayfasını ziyaret et"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "%1 kişisine bir e-posta gönder"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Katıl"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Bağış Yap"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Hata Bildir"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Telif hakkı"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Lisans:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Lisans: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Kullanılan kitaplıklar"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Yazarlar"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Yazar fotoğraflarını göster"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Emeği Geçenler"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Çevirmenler"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "%1 Hakkında"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Çık"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Daha Fazla Eylem"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Etiketi Kaldır"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Eylemler"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Geri"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Kenar Çubuğunu Kapat"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Kenar Çubuğunu Aç"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Yükleniyor…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Parola"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Menüyü kapat"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Menüyü aç"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Ara…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Ara"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Aramayı temizle"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Ayarlar"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Ayarlar — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Geçerli sayfa. İlerleme: %​%1."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "%1 konumuna geç. İlerleme: %​%2."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Geçerli sayfa."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "%1 konumuna geç. İlgi bekleniyor."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "%1 konumuna geç."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Çekmeceyi kapat"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Çekmeceyi aç"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Geri Git"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "İleri Git"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Daha Fazla Eylem"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Bağlantıyı Panoya Kopyala"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 pencereleme sistemi"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (%3 üzerine yapılı)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Bağlantı adresini kopyala"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Hâlâ yükleniyor, lütfen bekleyin."
+
+#, fuzzy
+#~| msgctxt "ContextDrawer|"
+#~| msgid "Actions"
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Eylemler"
diff --git a/poqm/uk/libkirigami2plugin_qt.po b/poqm/uk/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..f773df8
--- /dev/null
@@ -0,0 +1,295 @@
+# Translation of libkirigami2plugin_qt.po to Ukrainian
+# Copyright (C) 2016-2021 This_file_is_part_of_KDE
+# This file is distributed under the license LGPL version 2.1 or
+# version 3 or later versions approved by the membership of KDE e.V.
+#
+# Yuri Chornoivan <yurchor@ukr.net>, 2016, 2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigami2plugin_qt\n"
+"PO-Revision-Date: 2022-12-21 08:03+0200\n"
+"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
+"Language-Team: Ukrainian <kde-i18n-uk@kde.org>\n"
+"Language: uk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n"
+"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Lokalize 20.12.0\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "Відвідайте сторінку %1 у KDE Store"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "Надіслати повідомлення ел. пошти до %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "Участь у команді"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "Підтримати фінансово"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "Повідомити про ваду"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Авторські права"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "Ліцензування:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "Ліцензування: %1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "Використані бібліотеки"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "Автори"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "Показати фотографії авторів"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "Подяки"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "Перекладачі"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "Про %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "Вийти"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "Інші дії"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "Вилучити мітку"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "Дії"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "Назад"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "Закрити бічну панель"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "Відкрити бічну панель"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "Завантаження…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "Пароль"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "Закрити меню"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "Відкрити меню"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "Шукати…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "Пошук"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "Спорожнити поле пошуку"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "Параметри"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "Параметри — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "Поточна сторінка. Поступ: %1 відсотків."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "Перехід до %1. Поступ: %2 відсотків."
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "Поточна сторінка."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "Перейти до %1. Вимагає уваги."
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "Перейти до «%1»."
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "Закрити висувну панель"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "Відкрити висувну панель"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "Перейти назад"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "Перейти далі"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "Інші дії"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "Копіювати посилання до буфера"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "Система керування вікнами %1"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (зібрано з використанням %3)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "Копіювати адресу посилання"
+
+#~ msgctxt "LoadingPlaceholder|"
+#~ msgid "Still loading, please wait."
+#~ msgstr "Завантажуємо. Будь ласка, зачекайте."
+
+#~ msgctxt "AboutItem|"
+#~ msgid "(%1)"
+#~ msgstr "(%1)"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "Шукати…"
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "Інші дії"
diff --git a/poqm/zh_CN/libkirigami2plugin_qt.po b/poqm/zh_CN/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..af1a276
--- /dev/null
@@ -0,0 +1,266 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: kdeorg\n"
+"PO-Revision-Date: 2023-05-22 14:00\n"
+"Language-Team: Chinese Simplified\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Qt-Contexts: true\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: kdeorg\n"
+"X-Crowdin-Project-ID: 269464\n"
+"X-Crowdin-Language: zh-CN\n"
+"X-Crowdin-File: /kf5-trunk/messages/kirigami/libkirigami2plugin_qt.pot\n"
+"X-Crowdin-File-ID: 6217\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "访问 %1 的 KDE 商店页面"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "发送电子邮件到 %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "参与贡献"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "捐款"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "报告程序缺陷"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "Copyright"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "许可证:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "许可证:%1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "使用的程序库"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "开发人员"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "显示开发人员照片"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "致谢"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "翻译人员"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "关于 %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "退出"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "更多操作"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "移除标签"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "操作"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "返回"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "关闭侧栏"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "打开侧边栏"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "正在加载…"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "密码"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "关闭菜单"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "打开菜单"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "搜索…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "搜索"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "清除搜索内容"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "设置"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "设置 — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "当前页面。进度:百分之 %1。"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "前往 %1。进度:百分之 %2。"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "当前页面。"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "前往 %1。要求关注。"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "前往 %1。"
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "关闭抽屉栏"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "打开抽屉栏"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "后退"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "前进"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "更多操作"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "复制链接至剪贴板"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE 程序框架 %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 窗口系统"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (使用 %3 构建)"
diff --git a/poqm/zh_TW/libkirigami2plugin_qt.po b/poqm/zh_TW/libkirigami2plugin_qt.po
new file mode 100644 (file)
index 0000000..3937492
--- /dev/null
@@ -0,0 +1,285 @@
+# Jeff Huang <s8321414@gmail.com>, 2016.
+# Franklin Weng <franklin@goodhorse.idv.tw>, 2017.
+# pan93412 <pan93412@gmail.com>, 2018, 2019.
+# Kisaragi Hiu <mail@kisaragi-hiu.com>, 2022, 2023.
+msgid ""
+msgstr ""
+"Project-Id-Version: libkirigamiplugin_qt\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-09-22 20:41+0800\n"
+"PO-Revision-Date: 2023-01-06 23:15+0900\n"
+"Last-Translator: Kisaragi Hiu <mail@kisaragi-hiu.com>\n"
+"Language-Team: Traditional Chinese <zh-l10n@linux.org.tw>\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 22.12.0\n"
+"X-Qt-Contexts: true\n"
+
+#: controls/AboutItem.qml:133
+#, qt-format
+msgctxt "AboutItem|"
+msgid "%1 (%2)"
+msgstr "%1(%2)"
+
+#: controls/AboutItem.qml:142
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Visit %1's KDE Store page"
+msgstr "造訪 %1 在 KDE Store 的頁面"
+
+#: controls/AboutItem.qml:151
+#, qt-format
+msgctxt "AboutItem|"
+msgid "Send an email to %1"
+msgstr "傳送電子郵件給 %1"
+
+#: controls/AboutItem.qml:201
+msgctxt "AboutItem|"
+msgid "Get Involved"
+msgstr "參與"
+
+#: controls/AboutItem.qml:207
+msgctxt "AboutItem|"
+msgid "Donate"
+msgstr "贊助"
+
+#: controls/AboutItem.qml:224
+msgctxt "AboutItem|"
+msgid "Report a Bug"
+msgstr "回報臭蟲"
+
+#: controls/AboutItem.qml:237
+msgctxt "AboutItem|"
+msgid "Copyright"
+msgstr "著作權"
+
+#: controls/AboutItem.qml:281
+msgctxt "AboutItem|"
+msgid "License:"
+msgstr "授權條款:"
+
+#: controls/AboutItem.qml:303
+#, qt-format
+msgctxt "AboutItem|"
+msgid "License: %1"
+msgstr "授權條款:%1"
+
+#: controls/AboutItem.qml:314
+msgctxt "AboutItem|"
+msgid "Libraries in use"
+msgstr "使用函式庫"
+
+#: controls/AboutItem.qml:344
+msgctxt "AboutItem|"
+msgid "Authors"
+msgstr "作者群"
+
+#: controls/AboutItem.qml:353
+msgctxt "AboutItem|"
+msgid "Show author photos"
+msgstr "顯示作者照片"
+
+#: controls/AboutItem.qml:384
+msgctxt "AboutItem|"
+msgid "Credits"
+msgstr "致謝"
+
+#: controls/AboutItem.qml:397
+msgctxt "AboutItem|"
+msgid "Translators"
+msgstr "翻譯者"
+
+#: controls/AboutPage.qml:101
+#, qt-format
+msgctxt "AboutPage|"
+msgid "About %1"
+msgstr "關於 %1"
+
+#: controls/AbstractApplicationWindow.qml:201
+msgctxt "AbstractApplicationWindow|"
+msgid "Quit"
+msgstr "離開"
+
+#: controls/ActionToolBar.qml:208
+msgctxt "ActionToolBar|"
+msgid "More Actions"
+msgstr "更多動作"
+
+#: controls/Avatar.qml:171
+#, qt-format
+msgctxt "Avatar|"
+msgid "%1 — %2"
+msgstr "%1 — %2"
+
+#: controls/Chip.qml:82
+msgctxt "Chip|"
+msgid "Remove Tag"
+msgstr "移除標籤"
+
+#: controls/ContextDrawer.qml:67
+msgctxt "ContextDrawer|"
+msgid "Actions"
+msgstr "動作"
+
+#: controls/GlobalDrawer.qml:506
+msgctxt "GlobalDrawer|"
+msgid "Back"
+msgstr "返回"
+
+#: controls/GlobalDrawer.qml:599
+msgctxt "GlobalDrawer|"
+msgid "Close Sidebar"
+msgstr "關閉側邊欄"
+
+#: controls/GlobalDrawer.qml:602
+msgctxt "GlobalDrawer|"
+msgid "Open Sidebar"
+msgstr "開啟側邊欄"
+
+#: controls/LoadingPlaceholder.qml:55
+msgctxt "LoadingPlaceholder|"
+msgid "Loading…"
+msgstr "載入中……"
+
+#: controls/PasswordField.qml:41
+msgctxt "PasswordField|"
+msgid "Password"
+msgstr "密碼"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Close menu"
+msgstr "關閉選單"
+
+#: controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml:70
+msgctxt "PageRowGlobalToolBarUI|"
+msgid "Open menu"
+msgstr "開啟選單"
+
+#: controls/SearchField.qml:94
+msgctxt "SearchField|"
+msgid "Search…"
+msgstr "搜尋…"
+
+#: controls/SearchField.qml:96
+msgctxt "SearchField|"
+msgid "Search"
+msgstr "搜尋"
+
+#: controls/SearchField.qml:107
+msgctxt "SearchField|"
+msgid "Clear search"
+msgstr "清除搜尋"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#: controls/settingscomponents/CategorizedSettings.qml:105
+msgctxt "CategorizedSettings|"
+msgid "Settings"
+msgstr "設定"
+
+#: controls/settingscomponents/CategorizedSettings.qml:41
+#, qt-format
+msgctxt "CategorizedSettings|"
+msgid "Settings — %1"
+msgstr "設定 — %1"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+#: controls/swipenavigator/templates/PageTab.qml:38
+#, qt-format
+msgctxt "PageTab|"
+msgid "Current page. Progress: %1 percent."
+msgstr "目前頁面。進度:百分之 %1。"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:41
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Progress: %2 percent."
+msgstr "前往 %1。進度:百分之 %2。"
+
+#. Accessibility text for a page tab. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:46
+msgctxt "PageTab|"
+msgid "Current page."
+msgstr "目前頁面。"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:49
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1. Demanding attention."
+msgstr "前往 %1。正在請求注意。"
+
+#. Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+#: controls/swipenavigator/templates/PageTab.qml:52
+#, qt-format
+msgctxt "PageTab|"
+msgid "Navigate to %1."
+msgstr "前往 %1。"
+
+#: controls/templates/OverlayDrawer.qml:141
+msgctxt "OverlayDrawer|"
+msgid "Close drawer"
+msgstr "關閉抽屜"
+
+#: controls/templates/OverlayDrawer.qml:147
+msgctxt "OverlayDrawer|"
+msgid "Open drawer"
+msgstr "開啟抽屜"
+
+#: controls/templates/private/BackButton.qml:53
+msgctxt "BackButton|"
+msgid "Navigate Back"
+msgstr "返回"
+
+#: controls/templates/private/ForwardButton.qml:30
+msgctxt "ForwardButton|"
+msgid "Navigate Forward"
+msgstr "往前"
+
+#: controls/ToolBarApplicationHeader.qml:118
+msgctxt "ToolBarApplicationHeader|"
+msgid "More Actions"
+msgstr "更多動作"
+
+#: controls/UrlButton.qml:54
+msgctxt "UrlButton|"
+msgid "Copy Link to Clipboard"
+msgstr "複製連結至剪貼簿"
+
+#: settings.cpp:220
+#, qt-format
+msgctxt "Settings|"
+msgid "KDE Frameworks %1"
+msgstr "KDE Frameworks %1"
+
+#: settings.cpp:222
+#, qt-format
+msgctxt "Settings|"
+msgid "The %1 windowing system"
+msgstr "%1 視窗系統"
+
+#: settings.cpp:223
+#, qt-format
+msgctxt "Settings|"
+msgid "Qt %2 (built against %3)"
+msgstr "Qt %2 (建置於 %3 上)"
+
+#~ msgctxt "UrlButton|"
+#~ msgid "Copy link address"
+#~ msgstr "複製連結網址"
+
+#~ msgctxt "SearchField|"
+#~ msgid "Search..."
+#~ msgstr "搜尋…"
+
+#~ msgctxt "AboutPage|"
+#~ msgid "%1 <%2>"
+#~ msgstr "%1 <%2>"
+
+#~ msgctxt "ToolBarPageHeader|"
+#~ msgid "More Actions"
+#~ msgstr "更多動作"
diff --git a/scripts/gen_icons_qrc.sh b/scripts/gen_icons_qrc.sh
new file mode 100755 (executable)
index 0000000..e8850a3
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+SRC_DIR="src/"
+BREEZEICONS_DIR="breeze-icons"
+ICONS_SIZES=(48 32 22)
+TAB="    "
+
+kirigami_dir="$(cd $(dirname $(readlink -f $0))/.. && pwd)"
+
+case $1 in
+-h|--help)
+       echo "usage: $(basename $0)"
+       exit 1
+       ;;
+esac
+
+if [[ ! -d ${kirigami_dir}/${BREEZEICONS_DIR} ]]; then
+       echo "could not find ${BREEZEICONS_DIR}, please clone breeze-icons first into ${BREEZEICONS_DIR}:"
+       echo "cd ${kirigami_dir} && git clone --depth 1 https://invent.kde.org/frameworks/breeze-icons.git ${BREEZEICONS_DIR}"
+       exit 1
+fi
+
+pushd ${kirigami_dir} > /dev/null
+
+# find strings associated to variable with 'icon' in name and put them into an array
+if [[ -n $(which ag 2>/dev/null) ]]; then
+       possible_icons=($(ag --ignore Icon.qml --file-search-regex "\.qml" --only-matching --nonumbers --noheading --nofilename "icon.*\".+\"" ${SRC_DIR} | egrep -o "*\".+\""))
+       # try to find in Icon { ... source: "xyz" ... }
+       possible_icons+=($(ag --ignore Icon.qml --file-search-regex "\.qml" -A 15 "Icon\s*{" ${SRC_DIR} | egrep "source:" | egrep -o "*\".+\""))
+else
+       possible_icons=($(find ${SRC_DIR} -name "*.qml" -and -not -name "Icon.qml" -exec egrep "icon.*\".+\"" {} \; | egrep -o "*\".+\""))
+fi
+
+# sort array and filter out all entry which are not a string ("...")
+IFS=$'\n' icons=($(sort -u <<<"${possible_icons[*]}" | egrep -o "*\".+\"" | sed 's/\"//g'))
+unset IFS
+
+#printf "%s\n" "${icons[@]}"
+
+# generate .qrc
+echo "<RCC>"
+echo "${TAB}<qresource prefix=\"/\">"
+
+for icon in ${icons[@]}; do
+       for size in ${ICONS_SIZES[@]}; do
+               file=$(find breeze-icons/icons/*/${size}/ -name "${icon}.*" -print -quit)
+
+               if [[ -n ${file} ]]; then
+                       echo -e "${TAB}${TAB}<file alias=\"icons/$(basename ${file})\">${file}</file>"
+                       #echo ${file}
+                       break
+               fi
+       done
+done
+
+echo "${TAB}</qresource>"
+echo "</RCC>"
+
+popd > /dev/null
diff --git a/scripts/gen_qmltypes.sh b/scripts/gen_qmltypes.sh
new file mode 100755 (executable)
index 0000000..8606f99
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+QMLPLUGINDUMP=${QMLPLUGINDUMP-qmlplugindump}
+
+case $1 in
+-h|--help)
+       echo "usage: $(basename $0) IMPORT_PATH"
+       echo "it uses either '$(which qmlplugindump)' or the one set by 'QMLPLUGINDUMP'"
+       exit 1
+       ;;
+esac
+
+[[ -z ${1} ]] && { echo "no import path not given, exit"; exit 1; }
+
+echo "using '${QMLPLUGINDUMP}' as dump tool" >&2
+
+${QMLPLUGINDUMP} -noinstantiate -notrelocatable -platform xcb org.kde.kirigami 2.0 "${1}"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e3e7b35
--- /dev/null
@@ -0,0 +1,316 @@
+
+add_subdirectory(libkirigami)
+
+ecm_add_qml_module(KirigamiPlugin URI "org.kde.kirigami" VERSION 2.0 CLASSNAME KirigamiPlugin)
+
+ecm_add_qml_module_dependencies(KirigamiPlugin DEPENDS
+    "QtQuick.Controls 2.15"
+    "QtGraphicalEffects 1.0"
+)
+
+ecm_create_qm_loader(kirigami_QM_LOADER libkirigami2plugin_qt)
+target_sources(KirigamiPlugin PRIVATE ${kirigami_QM_LOADER})
+
+target_include_directories(KirigamiPlugin PRIVATE
+    ${CMAKE_CURRENT_SOURCE_DIR}/libkirigami
+    ${CMAKE_CURRENT_BINARY_DIR}/libkirigami
+)
+
+target_sources(KirigamiPlugin PRIVATE
+    avatar.cpp
+    avatar.h
+    colorutils.cpp
+    colorutils.h
+    columnview.cpp
+    columnview.h
+    columnview_p.h
+    delegaterecycler.cpp
+    delegaterecycler.h
+    enums.cpp
+    enums.h
+    formlayoutattached.cpp
+    formlayoutattached.h
+    icon.cpp
+    icon.h
+    imagecolors.cpp
+    imagecolors.h
+    kirigamiplugin.cpp
+    kirigamiplugin.h
+    mnemonicattached.cpp
+    mnemonicattached.h
+    pagepool.cpp
+    pagepool.h
+    pagerouter.cpp
+    pagerouter.h
+    scenepositionattached.cpp
+    scenepositionattached.h
+    settings.cpp
+    settings.h
+    shadowedrectangle.cpp
+    shadowedrectangle.h
+    shadowedtexture.cpp
+    shadowedtexture.h
+    sizegroup.cpp
+    sizegroup.h
+    spellcheckinghint.cpp
+    spellcheckinghint.h
+    toolbarlayout.cpp
+    toolbarlayoutdelegate.cpp
+    toolbarlayoutdelegate.h
+    toolbarlayout.h
+    wheelhandler.cpp
+    wheelhandler.h
+    inputmethod.cpp
+
+    scenegraph/managedtexturenode.cpp
+    scenegraph/managedtexturenode.h
+    scenegraph/paintedrectangleitem.cpp
+    scenegraph/paintedrectangleitem.h
+    ${CMAKE_CURRENT_BINARY_DIR}/libkirigami/loggingcategory.cpp
+
+    scenegraph/shaders/shaders.qrc
+)
+if (HAVE_QTGUI_OPENGL AND NOT KF6_PORTING_TODO)
+    target_sources(KirigamiPlugin PRIVATE
+        scenegraph/shadowedborderrectanglematerial.cpp
+        scenegraph/shadowedborderrectanglematerial.h
+        scenegraph/shadowedbordertexturematerial.cpp
+        scenegraph/shadowedbordertexturematerial.h
+        scenegraph/shadowedrectanglematerial.cpp
+        scenegraph/shadowedrectanglematerial.h
+        scenegraph/shadowedrectanglenode.cpp
+        scenegraph/shadowedrectanglenode.h
+        scenegraph/shadowedtexturematerial.cpp
+        scenegraph/shadowedtexturematerial.h
+        scenegraph/shadowedtexturenode.cpp
+        scenegraph/shadowedtexturenode.h
+    )
+endif()
+add_subdirectory(scenegraph/shaders6)
+
+ecm_target_qml_sources(KirigamiPlugin SOURCES
+    controls/Action.qml
+    controls/AbstractApplicationHeader.qml
+    controls/AbstractApplicationWindow.qml
+    controls/AbstractListItem.qml
+    controls/ApplicationHeader.qml
+    controls/ToolBarApplicationHeader.qml
+    controls/ApplicationWindow.qml
+    controls/BasicListItem.qml
+    controls/OverlayDrawer.qml
+    controls/ContextDrawer.qml
+    controls/GlobalDrawer.qml
+    controls/Heading.qml
+    controls/Separator.qml
+    controls/PageRow.qml
+    controls/Label.qml
+    controls/OverlaySheet.qml
+    controls/Page.qml
+    controls/ScrollablePage.qml
+    controls/SwipeListItem.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.1 SOURCES
+    controls/AbstractItemViewHeader.qml
+    controls/ItemViewHeader.qml
+    controls/AbstractApplicationItem.qml
+    controls/ApplicationItem.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.3 SOURCES
+    controls/FormLayout.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.4 SOURCES
+    controls/AbstractCard.qml
+    controls/Card.qml
+    controls/CardsListView.qml
+    controls/CardsGridView.qml
+    controls/CardsLayout.qml
+    controls/InlineMessage.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.5 SOURCES
+    controls/ListItemDragHandle.qml
+    controls/ActionToolBar.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.6 SOURCES
+    controls/AboutPage.qml
+    controls/LinkButton.qml
+    controls/UrlButton.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.7 SOURCES
+    controls/ActionTextField.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.8 SOURCES
+    controls/SearchField.qml
+    controls/PasswordField.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.10 SOURCES
+    controls/ListSectionHeader.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.11 SOURCES
+    controls/PagePoolAction.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.12 SOURCES
+    controls/ShadowedImage.qml
+    controls/PlaceholderMessage.qml
+    controls/RouterWindow.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.13 SOURCES
+    controls/Avatar.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.13 PATH swipenavigator SOURCES
+    controls/swipenavigator/SwipeNavigator.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.14 SOURCES
+    controls/FlexColumn.qml
+    controls/CheckableListItem.qml
+    controls/Hero.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.17 PATH swipenavigator SOURCES
+    controls/swipenavigator/TabViewLayout.qml
+    controls/swipenavigator/PageTab.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.18 PATH settingscomponents SOURCES
+    controls/settingscomponents/CategorizedSettings.qml
+    controls/settingscomponents/SettingAction.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.19 SOURCES
+    controls/AboutItem.qml
+    controls/NavigationTabBar.qml
+    controls/NavigationTabButton.qml
+    controls/Dialog.qml
+    controls/MenuDialog.qml
+    controls/PromptDialog.qml
+    controls/AbstractChip.qml
+    controls/Chip.qml
+    controls/LoadingPlaceholder.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin VERSION 2.20 SOURCES
+    controls/SelectableLabel.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH private SOURCES
+    controls/private/ActionButton.qml
+    controls/private/ActionIconGroup.qml
+    controls/private/ActionMenuItem.qml
+    controls/private/ActionsMenu.qml
+    controls/private/BannerImage.qml
+    controls/private/CardsGridViewPrivate.qml
+    controls/private/ContextDrawerActionItem.qml
+    controls/private/CornerShadow.qml
+    controls/private/DefaultCardBackground.qml
+    controls/private/DefaultChipBackground.qml
+    controls/private/DefaultListItemBackground.qml
+    controls/private/DefaultPageTitleDelegate.qml
+    controls/private/EdgeShadow.qml
+    controls/private/GlobalDrawerActionItem.qml
+    controls/private/PageActionPropertyGroup.qml
+    controls/private/PrivateActionToolButton.qml
+    controls/private/SwipeItemEventFilter.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH private/globaltoolbar SOURCES
+    controls/private/globaltoolbar/AbstractPageHeader.qml
+    controls/private/globaltoolbar/BreadcrumbControl.qml
+    controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml
+    controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml
+    controls/private/globaltoolbar/TabBarControl.qml
+    controls/private/globaltoolbar/TitlesPageHeader.qml
+    controls/private/globaltoolbar/ToolBarPageHeader.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH swipenavigator SOURCES
+    controls/swipenavigator/PrivateSwipeHighlight.qml
+    controls/swipenavigator/PrivateSwipeProgress.qml
+    controls/swipenavigator/PrivateSwipeStack.qml
+    controls/swipenavigator/PrivateSwipeTab.qml
+    controls/swipenavigator/PrivateSwipeTabBar.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH swipenavigator/templates SOURCES
+    controls/swipenavigator/templates/PageTab.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH templates SOURCES
+    controls/templates/AbstractApplicationHeader.qml
+    controls/templates/AbstractCard.qml
+    controls/templates/AbstractChip.qml
+    controls/templates/AbstractListItem.qml
+    controls/templates/ApplicationHeader.qml
+    controls/templates/InlineMessage.qml
+    controls/templates/OverlayDrawer.qml
+    controls/templates/OverlaySheet.qml
+    controls/templates/SingletonHeaderSizeGroup.qml
+    controls/templates/SwipeListItem.qml
+    controls/templates/qmldir
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH templates/private SOURCES
+    controls/templates/private/BackButton.qml
+    controls/templates/private/BorderPropertiesGroup.qml
+    controls/templates/private/ContextIcon.qml
+    controls/templates/private/ForwardButton.qml
+    controls/templates/private/GenericDrawerIcon.qml
+    controls/templates/private/IconPropertiesGroup.qml
+    controls/templates/private/MenuIcon.qml
+    controls/templates/private/PassiveNotificationsManager.qml
+)
+
+ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH styles/Material SOURCES
+    styles/Material/AbstractListItem.qml
+    styles/Material/InlineMessage.qml
+    styles/Material/Label.qml
+    styles/Material/SwipeListItem.qml
+    styles/Material/Theme.qml
+)
+
+if (DESKTOP_ENABLED)
+    ecm_target_qml_sources(KirigamiPlugin PRIVATE PATH styles/org.kde.desktop SOURCES
+        styles/org.kde.desktop/AbstractApplicationHeader.qml
+        styles/org.kde.desktop/AbstractListItem.qml
+        styles/org.kde.desktop/SwipeListItem.qml
+        styles/org.kde.desktop/Theme.qml
+    )
+endif()
+
+target_link_libraries(KirigamiPlugin
+    PUBLIC Qt${QT_MAJOR_VERSION}::Core
+    PRIVATE
+    ${Kirigami_EXTRA_LIBS}
+    Qt${QT_MAJOR_VERSION}::GuiPrivate
+    Qt${QT_MAJOR_VERSION}::Qml
+    Qt${QT_MAJOR_VERSION}::Quick
+    Qt${QT_MAJOR_VERSION}::QuickControls2
+    Qt${QT_MAJOR_VERSION}::Concurrent
+    KF5Kirigami2
+)
+
+if (HAVE_OpenMP)
+target_link_libraries(KirigamiPlugin PRIVATE OpenMP::OpenMP_CXX)
+endif()
+
+ecm_finalize_qml_module(KirigamiPlugin DESTINATION ${KDE_INSTALL_QMLDIR})
+
+ecm_generate_qmltypes(org.kde.kirigami 2.0 DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2)
+
+if (ANDROID)
+    install(FILES KF5Kirigami2-android-dependencies.xml
+        DESTINATION ${KDE_INSTALL_LIBDIR}
+        RENAME KF5Kirigami2_${CMAKE_ANDROID_ARCH_ABI}-android-dependencies.xml
+    )
+endif()
diff --git a/src/KF5Kirigami2-android-dependencies.xml b/src/KF5Kirigami2-android-dependencies.xml
new file mode 100644 (file)
index 0000000..c494299
--- /dev/null
@@ -0,0 +1,9 @@
+<rules>
+    <dependencies>
+        <lib name="KF5Kirigami2">
+            <depends>
+                <bundled file="plugins/kf5/kirigami" />
+            </depends>
+        </lib>
+    </dependencies>
+</rules>
diff --git a/src/Messages.sh b/src/Messages.sh
new file mode 100644 (file)
index 0000000..1c52975
--- /dev/null
@@ -0,0 +1,4 @@
+#! /usr/bin/env bash
+$EXTRACT_TR_STRINGS `find . -name \*.qml -o -name \*.cpp` -o $podir/libkirigami2plugin_qt.pot
+rm -f rc.cpp
+
diff --git a/src/avatar.cpp b/src/avatar.cpp
new file mode 100644 (file)
index 0000000..05a99eb
--- /dev/null
@@ -0,0 +1,155 @@
+// SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+//
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "avatar.h"
+#include <QDebug>
+#include <QMap>
+#include <QQuickStyle>
+#include <QTextBoundaryFinder>
+#include <QVector>
+
+bool contains(const QString &str, QChar::Script s)
+{
+    for (auto rune : str) {
+        if (rune.script() == s) {
+            return true;
+        }
+    }
+    return false;
+}
+
+QString NameUtils::initialsFromString(const QString &string)
+{
+    // "" -> ""
+    if (string.isEmpty()) {
+        return {};
+    }
+
+    QString normalized = string.normalized(QString::NormalizationForm_D);
+
+    if (normalized.startsWith(QLatin1Char('#')) || normalized.startsWith(QLatin1Char('@'))) {
+        normalized.remove(0, 1);
+    }
+
+    // Names written with Han and Hangul characters generally can be initialised by taking the
+    // first character
+    if (contains(normalized, QChar::Script_Han) || contains(normalized, QChar::Script_Hangul)) {
+        return normalized.at(0);
+    }
+
+    // "FirstName Name Name LastName"
+    normalized = normalized.trimmed();
+    if (normalized.contains(QLatin1Char(' '))) {
+        // "FirstName Name Name LastName" -> "FirstName" "Name" "Name" "LastName"
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+        const auto split = QStringView(normalized).split(QLatin1Char(' '));
+#else
+        const auto split = normalized.splitRef(QLatin1Char(' '));
+#endif
+
+        // "FirstName"
+        auto first = split.first();
+        // "LastName"
+        auto last = split.last();
+        if (first.isEmpty()) {
+            // "" "LastName" -> "L"
+            return QString(last.front());
+        }
+        if (last.isEmpty()) {
+            // "FirstName" "" -> "F"
+            return QString(first.front());
+        }
+        // "FirstName" "LastName" -> "FL"
+        return QString(first.front()) + last.front();
+        // "OneName"
+    } else {
+        // "OneName" -> "O"
+        return QString(normalized.front());
+    }
+}
+
+/* clang-format off */
+const QMap<QString,QList<QColor>> c_colors = {
+    {
+        QStringLiteral("default"),
+        {
+            QColor("#e93a9a"),
+            QColor("#e93d58"),
+            QColor("#e9643a"),
+            QColor("#ef973c"),
+            QColor("#e8cb2d"),
+            QColor("#b6e521"),
+            QColor("#3dd425"),
+            QColor("#00d485"),
+            QColor("#00d3b8"),
+            QColor("#3daee9"),
+            QColor("#b875dc"),
+            QColor("#926ee4"),
+        }
+    },
+    {
+        QStringLiteral("Material"),
+        {
+            QColor("#f44336"),
+            QColor("#e91e63"),
+            QColor("#9c27b0"),
+            QColor("#673ab7"),
+            QColor("#3f51b5"),
+            QColor("#2196f3"),
+            QColor("#03a9f4"),
+            QColor("#00bcd4"),
+            QColor("#009688"),
+            QColor("#4caf50"),
+            QColor("#8bc34a"),
+            QColor("#cddc39"),
+            QColor("#ffeb3b"),
+            QColor("#ffc107"),
+            QColor("#ff9800"),
+            QColor("#ff5722"),
+        }
+    }
+};
+/* clang-format on */
+
+QList<QColor> grabColors()
+{
+    if (c_colors.contains(QQuickStyle::name())) {
+        return c_colors[QQuickStyle::name()];
+    }
+    return c_colors[QStringLiteral("default")];
+}
+
+auto NameUtils::colorsFromString(const QString &string) -> QColor
+{
+    // We use a hash to get a "random" number that's always the same for
+    // a given string.
+    auto hash = qHash(string);
+    // hash modulo the length of the colors list minus one will always get us a valid
+    // index
+    auto index = hash % (grabColors().length() - 1);
+    // return a colour
+    return grabColors()[index];
+}
+
+auto NameUtils::isStringUnsuitableForInitials(const QString &string) -> bool
+{
+    if (string.isEmpty()) {
+        return true;
+    }
+
+    bool isNumber;
+    string.toFloat(&isNumber);
+    if (isNumber) {
+        return true;
+    }
+
+    const auto scripts = QList<QChar::Script>{QChar::Script_Common, QChar::Script_Inherited, QChar::Script_Latin, QChar::Script_Han, QChar::Script_Hangul};
+
+    for (auto character : string) {
+        if (!scripts.contains(character.script())) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/avatar.h b/src/avatar.h
new file mode 100644 (file)
index 0000000..7719543
--- /dev/null
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+//
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <QColor>
+#include <QObject>
+#include <QVariant>
+
+class NameUtils : public QObject
+{
+    Q_OBJECT
+
+public:
+    Q_INVOKABLE QString initialsFromString(const QString &name);
+    Q_INVOKABLE QColor colorsFromString(const QString &name);
+    Q_INVOKABLE bool isStringUnsuitableForInitials(const QString &name);
+};
+
+class AvatarGroup : public QObject
+{
+    Q_OBJECT
+
+public:
+    Q_PROPERTY(QVariant main MEMBER mainAction NOTIFY mainActionChanged)
+    QVariant mainAction;
+    Q_SIGNAL void mainActionChanged();
+
+    Q_PROPERTY(QVariant secondary MEMBER secondaryAction NOTIFY secondaryActionChanged)
+    QVariant secondaryAction;
+    Q_SIGNAL void secondaryActionChanged();
+};
diff --git a/src/colorutils.cpp b/src/colorutils.cpp
new file mode 100644 (file)
index 0000000..0e13d82
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "colorutils.h"
+
+#include "loggingcategory.h"
+#include <QIcon>
+#include <QtMath>
+#include <cmath>
+#include <map>
+
+ColorUtils::ColorUtils(QObject *parent)
+    : QObject(parent)
+{
+}
+
+ColorUtils::Brightness ColorUtils::brightnessForColor(const QColor &color)
+{
+    auto luma = [](const QColor &color) {
+        return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
+    };
+
+    return luma(color) > 0.5 ? ColorUtils::Brightness::Light : ColorUtils::Brightness::Dark;
+}
+
+qreal ColorUtils::grayForColor(const QColor &color)
+{
+    return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
+}
+
+QColor ColorUtils::alphaBlend(const QColor &foreground, const QColor &background)
+{
+    const auto foregroundAlpha = foreground.alpha();
+    const auto inverseForegroundAlpha = 0xff - foregroundAlpha;
+    const auto backgroundAlpha = background.alpha();
+
+    if (foregroundAlpha == 0x00) {
+        return background;
+    }
+
+    if (backgroundAlpha == 0xff) {
+        return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseForegroundAlpha * background.red()),
+                               (foregroundAlpha * foreground.green()) + (inverseForegroundAlpha * background.green()),
+                               (foregroundAlpha * foreground.blue()) + (inverseForegroundAlpha * background.blue()),
+                               0xff);
+    } else {
+        const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha) / 255;
+        const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
+        Q_ASSERT(finalAlpha != 0x00);
+        return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseBackgroundAlpha * background.red()),
+                               (foregroundAlpha * foreground.green()) + (inverseBackgroundAlpha * background.green()),
+                               (foregroundAlpha * foreground.blue()) + (inverseBackgroundAlpha * background.blue()),
+                               finalAlpha);
+    }
+}
+
+QColor ColorUtils::linearInterpolation(const QColor &one, const QColor &two, double balance)
+{
+    auto scaleAlpha = [](const QColor &color, double factor) {
+        return QColor::fromRgb(color.red(), color.green(), color.blue(), color.alpha() * factor);
+    };
+    auto linearlyInterpolateDouble = [](double one, double two, double factor) {
+        return one + (two - one) * factor;
+    };
+
+    if (one == Qt::transparent) {
+        return scaleAlpha(two, balance);
+    }
+    if (two == Qt::transparent) {
+        return scaleAlpha(one, 1 - balance);
+    }
+    // QColor returns -1 when hue is undefined, which happens whenever
+    // saturation is 0. When this happens, interpolation can go wrong so handle
+    // it by first trying to use the other color's hue and if that is also -1,
+    // just skip the hue interpolation by using 0 for both.
+    auto sourceHue = std::max(float(one.hueF() > 0.0 ? one.hueF() : two.hueF()), 0.0f);
+    auto targetHue = std::max(float(two.hueF() > 0.0 ? two.hueF() : one.hueF()), 0.0f);
+
+    auto hue = std::fmod(linearlyInterpolateDouble(sourceHue, targetHue, balance), 1.0);
+    auto saturation = std::clamp(linearlyInterpolateDouble(one.saturationF(), two.saturationF(), balance), 0.0, 1.0);
+    auto value = std::clamp(linearlyInterpolateDouble(one.valueF(), two.valueF(), balance), 0.0, 1.0);
+    auto alpha = std::clamp(linearlyInterpolateDouble(one.alphaF(), two.alphaF(), balance), 0.0, 1.0);
+
+    return QColor::fromHsvF(hue, saturation, value, alpha);
+}
+
+// Some private things for the adjust, change, and scale properties
+struct ParsedAdjustments {
+    double red = 0.0;
+    double green = 0.0;
+    double blue = 0.0;
+
+    double hue = 0.0;
+    double saturation = 0.0;
+    double value = 0.0;
+
+    double alpha = 0.0;
+};
+
+ParsedAdjustments parseAdjustments(const QJSValue &value)
+{
+    ParsedAdjustments parsed;
+
+    auto checkProperty = [](const QJSValue &value, const QString &property) {
+        if (value.hasProperty(property)) {
+            auto val = value.property(property);
+            if (val.isNumber()) {
+                return QVariant::fromValue(val.toNumber());
+            }
+        }
+        return QVariant();
+    };
+
+    std::vector<std::pair<QString, double &>> items{{QStringLiteral("red"), parsed.red},
+                                                    {QStringLiteral("green"), parsed.green},
+                                                    {QStringLiteral("blue"), parsed.blue},
+                                                    //
+                                                    {QStringLiteral("hue"), parsed.hue},
+                                                    {QStringLiteral("saturation"), parsed.saturation},
+                                                    {QStringLiteral("value"), parsed.value},
+                                                    {QStringLiteral("lightness"), parsed.value},
+                                                    //
+                                                    {QStringLiteral("alpha"), parsed.alpha}};
+
+    for (const auto &item : items) {
+        auto val = checkProperty(value, item.first);
+        if (val.isValid()) {
+            item.second = val.toDouble();
+        }
+    }
+
+    if ((parsed.red || parsed.green || parsed.blue) && (parsed.hue || parsed.saturation || parsed.value)) {
+        qCCritical(KirigamiLog) << "It is an error to have both RGB and HSL values in an adjustment.";
+    }
+
+    return parsed;
+}
+
+QColor ColorUtils::adjustColor(const QColor &color, const QJSValue &adjustments)
+{
+    auto adjusts = parseAdjustments(adjustments);
+
+    if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) {
+        qCCritical(KirigamiLog) << "Hue is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) {
+        qCCritical(KirigamiLog) << "Red is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) {
+        qCCritical(KirigamiLog) << "Green is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) {
+        qCCritical(KirigamiLog) << "Green is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) {
+        qCCritical(KirigamiLog) << "Saturation is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) {
+        qCCritical(KirigamiLog) << "Value is out of bounds";
+    }
+    if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) {
+        qCCritical(KirigamiLog) << "Alpha is out of bounds";
+    }
+
+    auto copy = color;
+
+    if (adjusts.alpha) {
+        copy.setAlpha(adjusts.alpha);
+    }
+
+    if (adjusts.red || adjusts.green || adjusts.blue) {
+        copy.setRed(copy.red() + adjusts.red);
+        copy.setGreen(copy.green() + adjusts.green);
+        copy.setBlue(copy.blue() + adjusts.blue);
+    } else if (adjusts.hue || adjusts.saturation || adjusts.value) {
+        copy.setHsl(std::fmod(copy.hue() + adjusts.hue, 360.0), //
+                    copy.saturation() + adjusts.saturation, //
+                    copy.value() + adjusts.value,
+                    copy.alpha());
+    }
+
+    return copy;
+}
+
+QColor ColorUtils::scaleColor(const QColor &color, const QJSValue &adjustments)
+{
+    auto adjusts = parseAdjustments(adjustments);
+    auto copy = color;
+
+    if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) {
+        qCCritical(KirigamiLog) << "Red is out of bounds";
+    }
+    if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) {
+        qCCritical(KirigamiLog) << "Green is out of bounds";
+    }
+    if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) {
+        qCCritical(KirigamiLog) << "Blue is out of bounds";
+    }
+    if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) {
+        qCCritical(KirigamiLog) << "Saturation is out of bounds";
+    }
+    if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) {
+        qCCritical(KirigamiLog) << "Value is out of bounds";
+    }
+    if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) {
+        qCCritical(KirigamiLog) << "Alpha is out of bounds";
+    }
+
+    if (adjusts.hue != 0) {
+        qCCritical(KirigamiLog) << "Hue cannot be scaled";
+    }
+
+    auto shiftToAverage = [](double current, double factor) {
+        auto scale = qBound(-100.0, factor, 100.0) / 100;
+        return current + (scale > 0 ? 255 - current : current) * scale;
+    };
+
+    if (adjusts.red || adjusts.green || adjusts.blue) {
+        copy.setRed(qBound(0.0, shiftToAverage(copy.red(), adjusts.red), 255.0));
+        copy.setGreen(qBound(0.0, shiftToAverage(copy.green(), adjusts.green), 255.0));
+        copy.setBlue(qBound(0.0, shiftToAverage(copy.blue(), adjusts.blue), 255.0));
+    } else {
+        copy.setHsl(copy.hue(),
+                    qBound(0.0, shiftToAverage(copy.saturation(), adjusts.saturation), 255.0),
+                    qBound(0.0, shiftToAverage(copy.value(), adjusts.value), 255.0),
+                    qBound(0.0, shiftToAverage(copy.alpha(), adjusts.alpha), 255.0));
+    }
+
+    return copy;
+}
+
+QColor ColorUtils::tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha)
+{
+    qreal tintAlpha = tintColor.alphaF() * alpha;
+    qreal inverseAlpha = 1.0 - tintAlpha;
+
+    if (qFuzzyCompare(tintAlpha, 1.0)) {
+        return tintColor;
+    } else if (qFuzzyIsNull(tintAlpha)) {
+        return targetColor;
+    }
+
+    return QColor::fromRgbF(tintColor.redF() * tintAlpha + targetColor.redF() * inverseAlpha,
+                            tintColor.greenF() * tintAlpha + targetColor.greenF() * inverseAlpha,
+                            tintColor.blueF() * tintAlpha + targetColor.blueF() * inverseAlpha,
+                            tintAlpha + inverseAlpha * targetColor.alphaF());
+}
+
+ColorUtils::XYZColor ColorUtils::colorToXYZ(const QColor &color)
+{
+    // http://wiki.nuaj.net/index.php/Color_Transforms#RGB_.E2.86.92_XYZ
+    qreal r = color.redF();
+    qreal g = color.greenF();
+    qreal b = color.blueF();
+    // Apply gamma correction (i.e. conversion to linear-space)
+    auto correct = [](qreal &v) {
+        if (v > 0.04045) {
+            v = std::pow((v + 0.055) / 1.055, 2.4);
+        } else {
+            v = v / 12.92;
+        }
+    };
+
+    correct(r);
+    correct(g);
+    correct(b);
+
+    // Observer. = 2°, Illuminant = D65
+    const qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805;
+    const qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722;
+    const qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505;
+
+    return XYZColor{x, y, z};
+}
+
+ColorUtils::LabColor ColorUtils::colorToLab(const QColor &color)
+{
+    // First: convert to XYZ
+    const auto xyz = colorToXYZ(color);
+
+    // Second: convert from XYZ to L*a*b
+    qreal x = xyz.x / 0.95047; // Observer= 2°, Illuminant= D65
+    qreal y = xyz.y / 1.0;
+    qreal z = xyz.z / 1.08883;
+
+    auto pivot = [](qreal &v) {
+        if (v > 0.008856) {
+            v = std::pow(v, 1.0 / 3.0);
+        } else {
+            v = (7.787 * v) + (16.0 / 116.0);
+        }
+    };
+
+    pivot(x);
+    pivot(y);
+    pivot(z);
+
+    LabColor labColor;
+    labColor.l = std::max(0.0, (116 * y) - 16);
+    labColor.a = 500 * (x - y);
+    labColor.b = 200 * (y - z);
+
+    return labColor;
+}
+
+qreal ColorUtils::chroma(const QColor &color)
+{
+    LabColor labColor = colorToLab(color);
+
+    // Chroma is hypotenuse of a and b
+    return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2));
+}
+
+qreal ColorUtils::luminance(const QColor &color)
+{
+    const auto &xyz = colorToXYZ(color);
+    // Luminance is equal to Y
+    return xyz.y;
+}
diff --git a/src/colorutils.h b/src/colorutils.h
new file mode 100644 (file)
index 0000000..e78415f
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QColor>
+#include <QJSValue>
+#include <QObject>
+#include <QQuickItem>
+
+/**
+ * Utilities for processing items to obtain colors and information useful for
+ * UIs that need to adjust to variable elements.
+ */
+class ColorUtils : public QObject
+{
+    Q_OBJECT
+public:
+    /**
+     * @brief Describes the contrast of an item.
+     */
+    enum Brightness {
+        /**
+         * @brief The item is dark and requires a light foreground color to achieve readable contrast.
+         */
+        Dark,
+
+        /**
+         * @brief The item is light and requires a dark foreground color to achieve readable contrast.
+         */
+        Light,
+    };
+    Q_ENUM(Brightness)
+
+    explicit ColorUtils(QObject *parent = nullptr);
+
+    /**
+     * @brief This method returns whether a color is light or dark.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import QtQuick 2.0
+     * import org.kde.kirigami 2.12 as Kirigami
+     *
+     * Kirigami.Heading {
+     *     text: {
+     *         if (Kirigami.ColorUtils.brightnessForColor("pink") == Kirigami.ColorUtils.Light) {
+     *             return "The color is light"
+     *         } else {
+     *             return "The color is dark"
+     *         }
+     *     }
+     * }
+     * @endcode
+     *
+     * @since KDE Frameworks 5.69
+     * @since org.kde.kirigami 2.12
+     */
+    Q_INVOKABLE ColorUtils::Brightness brightnessForColor(const QColor &color);
+
+    /**
+     * @brief This method returns the color's estimated brightness.
+     *
+     * Similar to brightnessForColor but returns a 0 to 1 value for an
+     * estimate of the equivalent gray light value (luma).
+     * 0 as full black, 1 as full white and 0.5 equivalent to a 50% gray.
+     *
+     * @since KDE Frameworks 5.81
+     * @since org.kde.kirigami 2.16
+     */
+    Q_INVOKABLE qreal grayForColor(const QColor &color);
+
+    /**
+     * @brief This method returns the result of overlaying the foreground color
+     * on the background color.
+     *
+     * @param foreground The color to overlay on the background.
+     * @param background The color to overlay the foreground on.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import QtQuick 2.0
+     * import org.kde.kirigami 2.12 as Kirigami
+     *
+     * Rectangle {
+     *     color: Kirigami.ColorUtils.alphaBlend(Qt.rgba(0, 0, 0, 0.5), Qt.rgba(1, 1, 1, 1))
+     * }
+     * @endcode
+     *
+     * @since KDE Frameworks 5.69
+     * @since org.kde.kirigami 2.12
+     */
+    Q_INVOKABLE QColor alphaBlend(const QColor &foreground, const QColor &background);
+
+    /**
+     * @brief This method returns a linearly interpolated color between color
+     * one and color two.
+     *
+     * @param one The color to linearly interpolate from.
+     * @param two The color to linearly interpolate to.
+     * @param balance The balance between the two colors. 0.0 will return the
+     * first color, 1.0 will return the second color. Values beyond these bounds
+     * are valid, and will result in extrapolation.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import QtQuick 2.0
+     * import org.kde.kirigami 2.12 as Kirigami
+     *
+     * Rectangle {
+     *     color: Kirigami.ColorUtils.linearInterpolation("black", "white", 0.5)
+     * }
+     * @endcode
+     *
+     * @since KDE Frameworks 5.69
+     * @since org.kde.kirigami 2.12
+     */
+    Q_INVOKABLE QColor linearInterpolation(const QColor &one, const QColor &two, double balance);
+
+    /**
+     * @brief Increases or decreases either RGB or HSL properties of the color
+     * by fixed amounts.
+     *
+     * @param color The color to adjust.
+     * @param adjustments The adjustments to apply to the color.
+     *
+     * @note `value` and `lightness` are aliases for the same value.
+     *
+     * @code{.js}
+     * {
+     *     red: real, // Range: -255 to 255
+     *     green: real, // Range: -255 to 255
+     *     blue: real, // Range: -255 to 255
+     *     hue: real, // Range: -360 to 360
+     *     saturation: real, // Range: -255 to 255
+     *     value: real, // Range: -255 to 255
+     *     lightness: real, // Range: -255 to 255
+     *     alpha: real // Range: -255 to 255
+     * }
+     * @endcode
+     *
+     * @since KDE Frameworks 5.69
+     * @since org.kde.kirigami 2.12
+     */
+    Q_INVOKABLE QColor adjustColor(const QColor &color, const QJSValue &adjustments);
+
+    /**
+     * @brief Smoothly scales colors by changing either RGB or HSL properties of
+     * the color.
+     *
+     * @param color The color to adjust.
+     * @param adjustments The adjustments to apply to the color. Each value must
+     * be between `-100.0` and `100.0`. This indicates how far the property
+     * should be scaled from its original to the maximum if positive or to the
+     * minimum if negative.
+     *
+     * @note `value` and `lightness` are aliases for the same value.
+     *
+     * @code{.js}
+     * {
+     *     red: real,
+     *     green: real,
+     *     blue: real,
+     *     saturation: real,
+     *     lightness: real,
+     *     value: real,
+     *     alpha: real
+     * }
+     * @endcode
+     *
+     * @since KDE Frameworks 5.69
+     * @since org.kde.kirigami 2.12
+     */
+    Q_INVOKABLE QColor scaleColor(const QColor &color, const QJSValue &adjustments);
+
+    /**
+     * @brief Tint a color using a separate alpha value.
+     *
+     * This does the same as Qt.tint() except that rather than using the tint
+     * color's alpha value, it uses a separate value that gets multiplied with
+     * the tint color's alpha. This avoids needing to create a new color just to
+     * adjust an alpha value.
+     *
+     * @param targetColor The color to tint.
+     * @param tintColor The color to tint with.
+     * @param alpha The amount of tinting to apply.
+     *
+     * @returns The tinted color.
+     *
+     * @see QtQml.Qt.tint
+     */
+    Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha);
+
+    /**
+     * @brief Returns the CIELAB chroma of the given color.
+     *
+     * CIELAB chroma may give a better quantification of how vibrant a color is compared to HSV saturation.
+     *
+     * @see https://en.wikipedia.org/wiki/Colorfulness
+     * @see https://en.wikipedia.org/wiki/CIELAB_color_space
+     */
+    Q_INVOKABLE static qreal chroma(const QColor &color);
+
+    struct XYZColor {
+        qreal x = 0;
+        qreal y = 0;
+        qreal z = 0;
+    };
+
+    struct LabColor {
+        qreal l = 0;
+        qreal a = 0;
+        qreal b = 0;
+    };
+
+    // Not for QML, returns the comvertion from srgb of a QColor and XYZ colorspace
+    static ColorUtils::XYZColor colorToXYZ(const QColor &color);
+
+    // Not for QML, returns the comvertion from srgb of a QColor and Lab colorspace
+    static ColorUtils::LabColor colorToLab(const QColor &color);
+
+    static qreal luminance(const QColor &color);
+};
diff --git a/src/columnview.cpp b/src/columnview.cpp
new file mode 100644 (file)
index 0000000..69fe3bb
--- /dev/null
@@ -0,0 +1,1791 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "columnview.h"
+#include "columnview_p.h"
+
+#include "loggingcategory.h"
+#include <QAbstractItemModel>
+#include <QGuiApplication>
+#include <QPropertyAnimation>
+#include <QQmlComponent>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QStyleHints>
+
+#include "units.h"
+
+class QmlComponentsPoolSingleton
+{
+public:
+    QmlComponentsPoolSingleton()
+    {
+    }
+    static QmlComponentsPool *instance(QQmlEngine *engine);
+
+private:
+    QHash<QQmlEngine *, QmlComponentsPool *> m_instances;
+};
+
+Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf)
+
+QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine)
+{
+    Q_ASSERT(engine);
+    auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(engine);
+
+    if (componentPool) {
+        return componentPool;
+    }
+
+    componentPool = new QmlComponentsPool(engine);
+
+    const auto removePool = [engine]() {
+        // NB: do not derefence engine. it may be dangling already!
+        if (privateQmlComponentsPoolSelf) {
+            privateQmlComponentsPoolSelf->m_instances.remove(engine);
+        }
+    };
+    QObject::connect(engine, &QObject::destroyed, engine, removePool);
+    QObject::connect(componentPool, &QObject::destroyed, componentPool, removePool);
+
+    privateQmlComponentsPoolSelf->m_instances[engine] = componentPool;
+    return componentPool;
+}
+
+QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine)
+    : QObject(engine)
+{
+    QQmlComponent *component = new QQmlComponent(engine, this);
+
+    /* clang-format off */
+    component->setData(QByteArrayLiteral(R"(
+import QtQuick 2.7
+import org.kde.kirigami 2.7 as Kirigami
+
+QtObject {
+    readonly property Component leadingSeparator: Kirigami.Separator {
+        property Item column
+
+        // positioning trick to hide the very first separator
+        visible: {
+            const view = column.Kirigami.ColumnView.view;
+            return view && (LayoutMirroring.enabled
+                ? view.contentX + view.width > column.x + column.width
+                : view.contentX < column.x);
+        }
+
+        anchors.top: column.top
+        anchors.left: column.left
+        anchors.bottom: column.bottom
+        Kirigami.Theme.colorSet: Kirigami.Theme.Window
+        Kirigami.Theme.inherit: false
+    }
+
+    readonly property Component trailingSeparator: Kirigami.Separator {
+        property Item column
+
+        anchors.top: column.top
+        anchors.right: column.right
+        anchors.bottom: column.bottom
+        Kirigami.Theme.colorSet: Kirigami.Theme.Window
+        Kirigami.Theme.inherit: false
+    }
+}
+)"), QUrl(QStringLiteral("columnview.cpp")));
+    /* clang-format on */
+
+    m_instance = component->create();
+    // qCWarning(KirigamiLog)<<component->errors();
+    Q_ASSERT(m_instance);
+    m_instance->setParent(this);
+
+    m_leadingSeparatorComponent = m_instance->property("leadingSeparator").value<QQmlComponent *>();
+    Q_ASSERT(m_leadingSeparatorComponent);
+
+    m_trailingSeparatorComponent = m_instance->property("trailingSeparator").value<QQmlComponent *>();
+    Q_ASSERT(m_trailingSeparatorComponent);
+
+    m_units = engine->singletonInstance<Kirigami::Units *>(qmlTypeId("org.kde.kirigami", 2, 0, "Units"));
+    Q_ASSERT(m_units);
+
+    connect(m_units, &Kirigami::Units::gridUnitChanged, this, &QmlComponentsPool::gridUnitChanged);
+    connect(m_units, &Kirigami::Units::longDurationChanged, this, &QmlComponentsPool::longDurationChanged);
+}
+
+QmlComponentsPool::~QmlComponentsPool()
+{
+}
+
+/////////
+
+ColumnViewAttached::ColumnViewAttached(QObject *parent)
+    : QObject(parent)
+{
+}
+
+ColumnViewAttached::~ColumnViewAttached()
+{
+}
+
+void ColumnViewAttached::setIndex(int index)
+{
+    if (!m_customFillWidth && m_view) {
+        const bool oldFillWidth = m_fillWidth;
+        m_fillWidth = index == m_view->count() - 1;
+        if (oldFillWidth != m_fillWidth) {
+            Q_EMIT fillWidthChanged();
+        }
+    }
+
+    if (index == m_index) {
+        return;
+    }
+
+    m_index = index;
+    Q_EMIT indexChanged();
+}
+
+int ColumnViewAttached::index() const
+{
+    return m_index;
+}
+
+void ColumnViewAttached::setFillWidth(bool fill)
+{
+    if (m_view) {
+        disconnect(m_view.data(), &ColumnView::countChanged, this, nullptr);
+    }
+    m_customFillWidth = true;
+
+    if (fill == m_fillWidth) {
+        return;
+    }
+
+    m_fillWidth = fill;
+    Q_EMIT fillWidthChanged();
+
+    if (m_view) {
+        m_view->polish();
+    }
+}
+
+bool ColumnViewAttached::fillWidth() const
+{
+    return m_fillWidth;
+}
+
+qreal ColumnViewAttached::reservedSpace() const
+{
+    return m_reservedSpace;
+}
+
+void ColumnViewAttached::setReservedSpace(qreal space)
+{
+    if (m_view) {
+        disconnect(m_view.data(), &ColumnView::columnWidthChanged, this, nullptr);
+    }
+    m_customReservedSpace = true;
+
+    if (qFuzzyCompare(space, m_reservedSpace)) {
+        return;
+    }
+
+    m_reservedSpace = space;
+    Q_EMIT reservedSpaceChanged();
+
+    if (m_view) {
+        m_view->polish();
+    }
+}
+
+ColumnView *ColumnViewAttached::view()
+{
+    return m_view;
+}
+
+void ColumnViewAttached::setView(ColumnView *view)
+{
+    if (view == m_view) {
+        return;
+    }
+
+    if (m_view) {
+        disconnect(m_view.data(), nullptr, this, nullptr);
+    }
+    m_view = view;
+
+    if (!m_customFillWidth && m_view) {
+        m_fillWidth = m_index == m_view->count() - 1;
+        connect(m_view.data(), &ColumnView::countChanged, this, [this]() {
+            m_fillWidth = m_index == m_view->count() - 1;
+            Q_EMIT fillWidthChanged();
+        });
+    }
+    if (!m_customReservedSpace && m_view) {
+        m_reservedSpace = m_view->columnWidth();
+        connect(m_view.data(), &ColumnView::columnWidthChanged, this, [this]() {
+            m_reservedSpace = m_view->columnWidth();
+            Q_EMIT reservedSpaceChanged();
+        });
+    }
+
+    Q_EMIT viewChanged();
+}
+
+QQuickItem *ColumnViewAttached::originalParent() const
+{
+    return m_originalParent;
+}
+
+void ColumnViewAttached::setOriginalParent(QQuickItem *parent)
+{
+    m_originalParent = parent;
+}
+
+bool ColumnViewAttached::shouldDeleteOnRemove() const
+{
+    return m_shouldDeleteOnRemove;
+}
+
+void ColumnViewAttached::setShouldDeleteOnRemove(bool del)
+{
+    m_shouldDeleteOnRemove = del;
+}
+
+bool ColumnViewAttached::preventStealing() const
+{
+    return m_preventStealing;
+}
+
+void ColumnViewAttached::setPreventStealing(bool prevent)
+{
+    if (prevent == m_preventStealing) {
+        return;
+    }
+
+    m_preventStealing = prevent;
+    Q_EMIT preventStealingChanged();
+}
+
+bool ColumnViewAttached::isPinned() const
+{
+    return m_pinned;
+}
+
+void ColumnViewAttached::setPinned(bool pinned)
+{
+    if (pinned == m_pinned) {
+        return;
+    }
+
+    m_pinned = pinned;
+
+    Q_EMIT pinnedChanged();
+
+    if (m_view) {
+        m_view->polish();
+    }
+}
+
+bool ColumnViewAttached::inViewport() const
+{
+    return m_inViewport;
+}
+
+void ColumnViewAttached::setInViewport(bool inViewport)
+{
+    if (m_inViewport == inViewport) {
+        return;
+    }
+
+    m_inViewport = inViewport;
+
+    Q_EMIT inViewportChanged();
+}
+
+/////////
+
+ContentItem::ContentItem(ColumnView *parent)
+    : QQuickItem(parent)
+    , m_view(parent)
+{
+    setFlags(flags() | ItemIsFocusScope);
+    m_slideAnim = new QPropertyAnimation(this);
+    m_slideAnim->setTargetObject(this);
+    m_slideAnim->setPropertyName("x");
+    // NOTE: the duration will be taken from kirigami units upon classBegin
+    m_slideAnim->setDuration(0);
+    m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::InOutQuad));
+    connect(m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
+        if (!m_view->currentItem()) {
+            m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
+        } else {
+            QRectF mapped = m_view->currentItem()->mapRectToItem(m_view, QRectF(QPointF(0, 0), m_view->currentItem()->size()));
+            if (!QRectF(QPointF(0, 0), m_view->size()).intersects(mapped)) {
+                m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem));
+            }
+        }
+    });
+
+    connect(this, &QQuickItem::xChanged, this, &ContentItem::layoutPinnedItems);
+}
+
+ContentItem::~ContentItem()
+{
+}
+
+void ContentItem::setBoundedX(qreal x)
+{
+    if (!parentItem()) {
+        return;
+    }
+    m_slideAnim->stop();
+    setX(qRound(qBound(qMin(0.0, -width() + parentItem()->width()), x, 0.0)));
+}
+
+void ContentItem::animateX(qreal newX)
+{
+    if (!parentItem()) {
+        return;
+    }
+
+    const qreal to = qRound(qBound(qMin(0.0, -width() + parentItem()->width()), newX, 0.0));
+
+    m_slideAnim->stop();
+    m_slideAnim->setStartValue(x());
+    m_slideAnim->setEndValue(to);
+    m_slideAnim->start();
+}
+
+void ContentItem::snapToItem()
+{
+    QQuickItem *firstItem = childAt(viewportLeft(), 0);
+    if (!firstItem) {
+        return;
+    }
+    QQuickItem *nextItem = childAt(firstItem->x() + firstItem->width() + 1, 0);
+
+    // need to make the last item visible?
+    if (nextItem && //
+        ((m_view->dragging() && m_lastDragDelta < 0) //
+         || (!m_view->dragging() //
+             && (width() - viewportRight()) < (viewportLeft() - firstItem->x())))) {
+        m_viewAnchorItem = nextItem;
+        animateX(-nextItem->x() + m_leftPinnedSpace);
+
+        // The first one found?
+    } else if ((m_view->dragging() && m_lastDragDelta >= 0) //
+               || (!m_view->dragging() && (viewportLeft() <= (firstItem->x() + (firstItem->width() / 2)))) //
+               || !nextItem) {
+        m_viewAnchorItem = firstItem;
+        animateX(-firstItem->x() + m_leftPinnedSpace);
+
+        // the second?
+    } else {
+        m_viewAnchorItem = nextItem;
+        animateX(-nextItem->x() + m_leftPinnedSpace);
+    }
+}
+
+qreal ContentItem::viewportLeft() const
+{
+    return -x() + m_leftPinnedSpace;
+}
+
+qreal ContentItem::viewportRight() const
+{
+    return -x() + m_view->width() - m_rightPinnedSpace;
+}
+
+qreal ContentItem::childWidth(QQuickItem *child)
+{
+    if (!parentItem()) {
+        return 0.0;
+    }
+
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
+
+    if (m_columnResizeMode == ColumnView::SingleColumn) {
+        return qRound(parentItem()->width());
+
+    } else if (attached->fillWidth()) {
+        return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), std::max(m_columnWidth, parentItem()->width())));
+
+    } else if (m_columnResizeMode == ColumnView::FixedColumns) {
+        return qRound(qMin(parentItem()->width(), m_columnWidth));
+
+        // DynamicColumns
+    } else {
+        // TODO:look for Layout size hints
+        qreal width = child->implicitWidth();
+
+        if (width < 1.0) {
+            width = m_columnWidth;
+        }
+
+        return qRound(qMin(m_view->width(), width));
+    }
+}
+
+void ContentItem::layoutItems()
+{
+    setY(m_view->topPadding());
+    setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding());
+
+    qreal implicitWidth = 0;
+    qreal implicitHeight = 0;
+    qreal partialWidth = 0;
+    int i = 0;
+    m_leftPinnedSpace = 0;
+    m_rightPinnedSpace = 0;
+
+    bool reverse = qApp->layoutDirection() == Qt::RightToLeft;
+    auto it = !reverse ? m_items.begin() : m_items.end();
+    int increment = reverse ? -1 : +1;
+    auto lastPos = reverse ? m_items.begin() : m_items.end();
+
+    for (; it != lastPos; it += increment) {
+        // for (QQuickItem *child : std::as_const(m_items)) {
+        QQuickItem *child = reverse ? *(it - 1) : *it;
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
+
+        if (child->isVisible()) {
+            if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) {
+                QQuickItem *sep = nullptr;
+                int sepWidth = 0;
+                if (m_view->separatorVisible()) {
+                    sep = ensureTrailingSeparator(child);
+                    sepWidth = (sep ? sep->width() : 0);
+                }
+                const qreal width = childWidth(child);
+                child->setSize(QSizeF(width + sepWidth, height()));
+
+                child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0));
+                child->setZ(1);
+
+                if (partialWidth <= -x()) {
+                    m_leftPinnedSpace = qMax(m_leftPinnedSpace, width);
+                } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
+                    m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
+                }
+
+                partialWidth += width;
+
+            } else {
+                child->setSize(QSizeF(childWidth(child), height()));
+
+                auto it = m_trailingSeparators.find(child);
+                if (it != m_trailingSeparators.end()) {
+                    it.value()->deleteLater();
+                    m_trailingSeparators.erase(it);
+                }
+                child->setPosition(QPointF(partialWidth, 0.0));
+                child->setZ(0);
+
+                partialWidth += child->width();
+            }
+        }
+
+        if (reverse) {
+            attached->setIndex(m_items.count() - (++i));
+        } else {
+            attached->setIndex(i++);
+        }
+
+        implicitWidth += child->implicitWidth();
+
+        implicitHeight = qMax(implicitHeight, child->implicitHeight());
+    }
+
+    setWidth(partialWidth);
+
+    setImplicitWidth(implicitWidth);
+    setImplicitHeight(implicitHeight);
+
+    m_view->setImplicitWidth(implicitWidth);
+    m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding());
+
+    const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0;
+    if (m_shouldAnimate) {
+        animateX(newContentX);
+    } else {
+        setBoundedX(newContentX);
+    }
+
+    updateVisibleItems();
+}
+
+void ContentItem::layoutPinnedItems()
+{
+    if (m_view->columnResizeMode() == ColumnView::SingleColumn) {
+        return;
+    }
+
+    qreal partialWidth = 0;
+    m_leftPinnedSpace = 0;
+    m_rightPinnedSpace = 0;
+
+    for (QQuickItem *child : std::as_const(m_items)) {
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(child, true));
+
+        if (child->isVisible()) {
+            if (attached->isPinned()) {
+                QQuickItem *sep = nullptr;
+                int sepWidth = 0;
+                if (m_view->separatorVisible()) {
+                    sep = ensureTrailingSeparator(child);
+                    sepWidth = (sep ? sep->width() : 0);
+                }
+
+                child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0));
+
+                if (partialWidth <= -x()) {
+                    m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth);
+                } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) {
+                    m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width());
+                }
+            }
+
+            partialWidth += child->width();
+        }
+    }
+}
+
+void ContentItem::updateVisibleItems()
+{
+    QList<QObject *> newItems;
+
+    for (auto *item : std::as_const(m_items)) {
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+
+        if (item->isVisible() && item->x() + x() < m_view->width() && item->x() + item->width() + x() > 0) {
+            newItems << item;
+            connect(item, &QObject::destroyed, this, [this, item] {
+                m_visibleItems.removeAll(item);
+            });
+            attached->setInViewport(true);
+        } else {
+            attached->setInViewport(false);
+        }
+    }
+
+    for (auto *item : std::as_const(m_visibleItems)) {
+        disconnect(item, &QObject::destroyed, this, nullptr);
+    }
+    const QQuickItem *oldFirstVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast<QQuickItem *>(m_visibleItems.first());
+    const QQuickItem *oldLastVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast<QQuickItem *>(m_visibleItems.last());
+
+    if (newItems != m_visibleItems) {
+        m_visibleItems = newItems;
+        Q_EMIT m_view->visibleItemsChanged();
+        if (!m_visibleItems.isEmpty() && m_visibleItems.first() != oldFirstVisibleItem) {
+            Q_EMIT m_view->firstVisibleItemChanged();
+        }
+        if (!m_visibleItems.isEmpty() && m_visibleItems.last() != oldLastVisibleItem) {
+            Q_EMIT m_view->lastVisibleItemChanged();
+        }
+    }
+}
+
+void ContentItem::forgetItem(QQuickItem *item)
+{
+    if (!m_items.contains(item)) {
+        return;
+    }
+
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+    attached->setView(nullptr);
+    attached->setIndex(-1);
+
+    disconnect(attached, nullptr, this, nullptr);
+    disconnect(item, nullptr, this, nullptr);
+    disconnect(item, nullptr, m_view, nullptr);
+
+    QQuickItem *separatorItem = m_leadingSeparators.take(item);
+    if (separatorItem) {
+        separatorItem->deleteLater();
+    }
+    separatorItem = m_trailingSeparators.take(item);
+    if (separatorItem) {
+        separatorItem->deleteLater();
+    }
+
+    const int index = m_items.indexOf(item);
+    m_items.removeAll(item);
+    disconnect(item, &QObject::destroyed, this, nullptr);
+    updateVisibleItems();
+    m_shouldAnimate = true;
+    m_view->polish();
+
+    if (index <= m_view->currentIndex()) {
+        m_view->setCurrentIndex(m_items.isEmpty() ? 0 : qBound(0, index - 1, m_items.count() - 1));
+    }
+    Q_EMIT m_view->countChanged();
+}
+
+QQuickItem *ContentItem::ensureLeadingSeparator(QQuickItem *item)
+{
+    QQuickItem *separatorItem = m_leadingSeparators.value(item);
+
+    if (!separatorItem) {
+        separatorItem = qobject_cast<QQuickItem *>(
+            QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
+        if (separatorItem) {
+            separatorItem->setParent(this);
+            separatorItem->setParentItem(item);
+            separatorItem->setZ(9999);
+            separatorItem->setProperty("column", QVariant::fromValue(item));
+            QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_leadingSeparatorComponent->completeCreate();
+            m_leadingSeparators[item] = separatorItem;
+        }
+    }
+
+    return separatorItem;
+}
+
+QQuickItem *ContentItem::ensureTrailingSeparator(QQuickItem *item)
+{
+    QQuickItem *separatorItem = m_trailingSeparators.value(item);
+
+    if (!separatorItem) {
+        separatorItem = qobject_cast<QQuickItem *>(
+            QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item)));
+        if (separatorItem) {
+            separatorItem->setParent(this);
+            separatorItem->setParentItem(item);
+            separatorItem->setZ(9999);
+            separatorItem->setProperty("column", QVariant::fromValue(item));
+            QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_trailingSeparatorComponent->completeCreate();
+            m_trailingSeparators[item] = separatorItem;
+        }
+    }
+
+    return separatorItem;
+}
+
+void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
+{
+    switch (change) {
+    case QQuickItem::ItemChildAddedChange: {
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(value.item, true));
+        attached->setView(m_view);
+
+        // connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish);
+        connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this] {
+            m_view->polish();
+        });
+        connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish);
+
+        value.item->setVisible(true);
+
+        if (!m_items.contains(value.item)) {
+            connect(value.item, &QQuickItem::widthChanged, m_view, &ColumnView::polish);
+            QQuickItem *item = value.item;
+            m_items << item;
+            connect(item, &QObject::destroyed, this, [this, item]() {
+                m_view->removeItem(item);
+            });
+        }
+
+        if (m_view->separatorVisible()) {
+            ensureLeadingSeparator(value.item);
+        }
+
+        m_shouldAnimate = true;
+        m_view->polish();
+        Q_EMIT m_view->countChanged();
+        break;
+    }
+    case QQuickItem::ItemChildRemovedChange: {
+        forgetItem(value.item);
+        break;
+    }
+    case QQuickItem::ItemVisibleHasChanged:
+        updateVisibleItems();
+        if (value.boolValue) {
+            m_view->polish();
+        }
+        break;
+    default:
+        break;
+    }
+    QQuickItem::itemChange(change, value);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ContentItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+    updateVisibleItems();
+    QQuickItem::geometryChanged(newGeometry, oldGeometry);
+}
+#else
+void ContentItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+    updateVisibleItems();
+    QQuickItem::geometryChange(newGeometry, oldGeometry);
+}
+#endif
+
+void ContentItem::syncItemsOrder()
+{
+    if (m_items == childItems()) {
+        return;
+    }
+
+    m_items = childItems();
+    // NOTE: polish() here sometimes gets indefinitely delayed and items changing order isn't seen
+    layoutItems();
+}
+
+void ContentItem::updateRepeaterModel()
+{
+    if (!sender()) {
+        return;
+    }
+
+    QObject *modelObj = sender()->property("model").value<QObject *>();
+
+    if (!modelObj) {
+        m_models.remove(sender());
+        return;
+    }
+
+    if (m_models[sender()]) {
+        disconnect(m_models[sender()], nullptr, this, nullptr);
+    }
+
+    m_models[sender()] = modelObj;
+
+    QAbstractItemModel *qaim = qobject_cast<QAbstractItemModel *>(modelObj);
+
+    if (qaim) {
+        connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder);
+
+    } else {
+        connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder()));
+    }
+}
+
+ColumnView::ColumnView(QQuickItem *parent)
+    : QQuickItem(parent)
+    , m_contentItem(nullptr)
+{
+    // NOTE: this is to *not* trigger itemChange
+    m_contentItem = new ContentItem(this);
+    setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton);
+    setAcceptTouchEvents(false); // Relies on synthetized mouse events
+    setFiltersChildMouseEvents(true);
+
+    connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this]() {
+        m_moving = false;
+        Q_EMIT movingChanged();
+    });
+    connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged);
+    connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged);
+
+    connect(this, &ColumnView::activeFocusChanged, this, [this]() {
+        if (hasActiveFocus() && m_currentItem) {
+            m_currentItem->forceActiveFocus();
+        }
+    });
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(this, true));
+    attached->setView(this);
+    attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(m_contentItem, true));
+    attached->setView(this);
+}
+
+ColumnView::~ColumnView()
+{
+}
+
+ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const
+{
+    return m_contentItem->m_columnResizeMode;
+}
+
+void ColumnView::setColumnResizeMode(ColumnResizeMode mode)
+{
+    if (m_contentItem->m_columnResizeMode == mode) {
+        return;
+    }
+
+    m_contentItem->m_columnResizeMode = mode;
+    if (mode == SingleColumn && m_currentItem) {
+        m_contentItem->m_viewAnchorItem = m_currentItem;
+    }
+    m_contentItem->m_shouldAnimate = false;
+    polish();
+    Q_EMIT columnResizeModeChanged();
+}
+
+qreal ColumnView::columnWidth() const
+{
+    return m_contentItem->m_columnWidth;
+}
+
+void ColumnView::setColumnWidth(qreal width)
+{
+    // Always forget the internal binding when the user sets anything, even the same value
+    disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr);
+
+    if (m_contentItem->m_columnWidth == width) {
+        return;
+    }
+
+    m_contentItem->m_columnWidth = width;
+    m_contentItem->m_shouldAnimate = false;
+    polish();
+    Q_EMIT columnWidthChanged();
+}
+
+int ColumnView::currentIndex() const
+{
+    return m_currentIndex;
+}
+
+void ColumnView::setCurrentIndex(int index)
+{
+    if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) {
+        return;
+    }
+
+    m_currentIndex = index;
+
+    if (index == -1) {
+        m_currentItem.clear();
+
+    } else {
+        m_currentItem = m_contentItem->m_items[index];
+        Q_ASSERT(m_currentItem);
+        m_currentItem->forceActiveFocus();
+
+        // If the current item is not on view, scroll
+        QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size()));
+
+        if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) {
+            mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt());
+        }
+
+        // m_contentItem->m_slideAnim->stop();
+
+        QRectF contentsRect(m_contentItem->m_leftPinnedSpace, //
+                            0,
+                            width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace,
+                            height());
+
+        if (!m_mouseDown) {
+            if (!contentsRect.contains(mappedCurrent)) {
+                m_contentItem->m_viewAnchorItem = m_currentItem;
+                m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace);
+            } else {
+                m_contentItem->snapToItem();
+            }
+        }
+    }
+
+    Q_EMIT currentIndexChanged();
+    Q_EMIT currentItemChanged();
+}
+
+QQuickItem *ColumnView::currentItem()
+{
+    return m_currentItem;
+}
+
+QList<QObject *> ColumnView::visibleItems() const
+{
+    return m_contentItem->m_visibleItems;
+}
+
+QQuickItem *ColumnView::firstVisibleItem() const
+{
+    if (m_contentItem->m_visibleItems.isEmpty()) {
+        return nullptr;
+    }
+
+    return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.first());
+}
+
+QQuickItem *ColumnView::lastVisibleItem() const
+{
+    if (m_contentItem->m_visibleItems.isEmpty()) {
+        return nullptr;
+    }
+
+    return qobject_cast<QQuickItem *>(m_contentItem->m_visibleItems.last());
+}
+
+int ColumnView::count() const
+{
+    return m_contentItem->m_items.count();
+}
+
+qreal ColumnView::topPadding() const
+{
+    return m_topPadding;
+}
+
+void ColumnView::setTopPadding(qreal padding)
+{
+    if (padding == m_topPadding) {
+        return;
+    }
+
+    m_topPadding = padding;
+    polish();
+    Q_EMIT topPaddingChanged();
+}
+
+qreal ColumnView::bottomPadding() const
+{
+    return m_bottomPadding;
+}
+
+void ColumnView::setBottomPadding(qreal padding)
+{
+    if (padding == m_bottomPadding) {
+        return;
+    }
+
+    m_bottomPadding = padding;
+    polish();
+    Q_EMIT bottomPaddingChanged();
+}
+
+QQuickItem *ColumnView::contentItem() const
+{
+    return m_contentItem;
+}
+
+int ColumnView::scrollDuration() const
+{
+    return m_contentItem->m_slideAnim->duration();
+}
+
+void ColumnView::setScrollDuration(int duration)
+{
+    disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr);
+
+    if (m_contentItem->m_slideAnim->duration() == duration) {
+        return;
+    }
+
+    m_contentItem->m_slideAnim->setDuration(duration);
+    Q_EMIT scrollDurationChanged();
+}
+
+bool ColumnView::separatorVisible() const
+{
+    return m_separatorVisible;
+}
+
+void ColumnView::setSeparatorVisible(bool visible)
+{
+    if (visible == m_separatorVisible) {
+        return;
+    }
+
+    m_separatorVisible = visible;
+
+    if (visible) {
+        for (QQuickItem *item : std::as_const(m_contentItem->m_items)) {
+            QQuickItem *sep = m_contentItem->ensureLeadingSeparator(item);
+            if (sep) {
+                sep->setVisible(true);
+            }
+
+            ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+            if (attached->isPinned()) {
+                QQuickItem *sep = m_contentItem->ensureTrailingSeparator(item);
+                if (sep) {
+                    sep->setVisible(true);
+                }
+            }
+        }
+
+    } else {
+        for (QQuickItem *sep : std::as_const(m_contentItem->m_leadingSeparators)) {
+            sep->setVisible(false);
+        }
+        for (QQuickItem *sep : std::as_const(m_contentItem->m_trailingSeparators)) {
+            sep->setVisible(false);
+        }
+    }
+
+    Q_EMIT separatorVisibleChanged();
+}
+
+bool ColumnView::dragging() const
+{
+    return m_dragging;
+}
+
+bool ColumnView::moving() const
+{
+    return m_moving;
+}
+
+qreal ColumnView::contentWidth() const
+{
+    return m_contentItem->width();
+}
+
+qreal ColumnView::contentX() const
+{
+    return -m_contentItem->x();
+}
+
+void ColumnView::setContentX(qreal x) const
+{
+    m_contentItem->setX(qRound(-x));
+}
+
+bool ColumnView::interactive() const
+{
+    return m_interactive;
+}
+
+void ColumnView::setInteractive(bool interactive)
+{
+    if (m_interactive == interactive) {
+        return;
+    }
+
+    m_interactive = interactive;
+
+    if (!m_interactive) {
+        if (m_dragging) {
+            m_dragging = false;
+            Q_EMIT draggingChanged();
+        }
+
+        m_contentItem->snapToItem();
+        setKeepMouseGrab(false);
+    }
+
+    Q_EMIT interactiveChanged();
+}
+
+bool ColumnView::acceptsMouse() const
+{
+    return m_acceptsMouse;
+}
+
+void ColumnView::setAcceptsMouse(bool accepts)
+{
+    if (m_acceptsMouse == accepts) {
+        return;
+    }
+
+    m_acceptsMouse = accepts;
+
+    if (!m_acceptsMouse) {
+        if (m_dragging) {
+            m_dragging = false;
+            Q_EMIT draggingChanged();
+        }
+
+        m_contentItem->snapToItem();
+        setKeepMouseGrab(false);
+    }
+
+    Q_EMIT acceptsMouseChanged();
+}
+
+void ColumnView::addItem(QQuickItem *item)
+{
+    insertItem(m_contentItem->m_items.length(), item);
+}
+
+void ColumnView::insertItem(int pos, QQuickItem *item)
+{
+    if (!item || m_contentItem->m_items.contains(item)) {
+        return;
+    }
+
+    m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
+
+    connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
+        removeItem(item);
+    });
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+    attached->setOriginalParent(item->parentItem());
+    attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
+    item->setParentItem(m_contentItem);
+
+    item->forceActiveFocus();
+
+    // Animate shift to new item.
+    m_contentItem->m_shouldAnimate = true;
+    m_contentItem->layoutItems();
+    Q_EMIT contentChildrenChanged();
+
+    // In order to keep the same current item we need to increase the current index if displaced
+    // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem
+    if (m_currentIndex >= pos) {
+        ++m_currentIndex;
+        Q_EMIT currentIndexChanged();
+    }
+
+    Q_EMIT itemInserted(pos, item);
+}
+
+void ColumnView::replaceItem(int pos, QQuickItem *item)
+{
+    if (pos < 0 || pos >= m_contentItem->m_items.length()) {
+        qCWarning(KirigamiLog) << "Position" << pos << "passed to ColumnView::replaceItem is out of range.";
+        return;
+    }
+
+    if (!item) {
+        qCWarning(KirigamiLog) << "Null item passed to ColumnView::replaceItem.";
+        return;
+    }
+
+    QQuickItem *oldItem = m_contentItem->m_items[pos];
+
+    // In order to keep the same current item we need to increase the current index if displaced
+    if (m_currentIndex >= pos) {
+        setCurrentIndex(m_currentIndex - 1);
+    }
+
+    m_contentItem->forgetItem(oldItem);
+    oldItem->setVisible(false);
+
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(oldItem, false));
+
+    if (attached && attached->shouldDeleteOnRemove()) {
+        oldItem->deleteLater();
+    } else {
+        oldItem->setParentItem(attached ? attached->originalParent() : nullptr);
+    }
+
+    Q_EMIT itemRemoved(oldItem);
+
+    if (!m_contentItem->m_items.contains(item)) {
+        m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item);
+
+        connect(item, &QObject::destroyed, m_contentItem, [this, item]() {
+            removeItem(item);
+        });
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+        attached->setOriginalParent(item->parentItem());
+        attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
+        item->setParentItem(m_contentItem);
+
+        if (m_currentIndex >= pos) {
+            ++m_currentIndex;
+            Q_EMIT currentIndexChanged();
+        }
+
+        Q_EMIT itemInserted(pos, item);
+    }
+
+    // Disable animation so replacement happens immediately.
+    m_contentItem->m_shouldAnimate = false;
+    m_contentItem->layoutItems();
+    Q_EMIT contentChildrenChanged();
+}
+
+void ColumnView::moveItem(int from, int to)
+{
+    if (m_contentItem->m_items.isEmpty() //
+        || from < 0 || from >= m_contentItem->m_items.length() //
+        || to < 0 || to >= m_contentItem->m_items.length()) {
+        return;
+    }
+
+    m_contentItem->m_items.move(from, to);
+    m_contentItem->m_shouldAnimate = true;
+
+    if (from == m_currentIndex) {
+        m_currentIndex = to;
+        Q_EMIT currentIndexChanged();
+    } else if (from < m_currentIndex && to > m_currentIndex) {
+        --m_currentIndex;
+        Q_EMIT currentIndexChanged();
+    } else if (from > m_currentIndex && to <= m_currentIndex) {
+        ++m_currentIndex;
+        Q_EMIT currentIndexChanged();
+    }
+
+    polish();
+}
+
+QQuickItem *ColumnView::removeItem(const QVariant &item)
+{
+    if (item.canConvert<QQuickItem *>()) {
+        return removeItem(item.value<QQuickItem *>());
+    } else if (item.canConvert<int>()) {
+        return removeItem(item.toInt());
+    } else {
+        return nullptr;
+    }
+}
+
+QQuickItem *ColumnView::removeItem(QQuickItem *item)
+{
+    if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) {
+        return nullptr;
+    }
+
+    const int index = m_contentItem->m_items.indexOf(item);
+
+    // In order to keep the same current item we need to increase the current index if displaced
+    if (m_currentIndex >= index) {
+        setCurrentIndex(m_currentIndex - 1);
+    }
+
+    m_contentItem->forgetItem(item);
+    item->setVisible(false);
+
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, false));
+
+    if (attached && attached->shouldDeleteOnRemove()) {
+        item->deleteLater();
+    } else {
+        item->setParentItem(attached ? attached->originalParent() : nullptr);
+    }
+
+    Q_EMIT contentChildrenChanged();
+    Q_EMIT itemRemoved(item);
+
+    return item;
+}
+
+QQuickItem *ColumnView::removeItem(int pos)
+{
+    if (m_contentItem->m_items.isEmpty() //
+        || pos < 0 || pos >= m_contentItem->m_items.length()) {
+        return nullptr;
+    }
+
+    return removeItem(m_contentItem->m_items[pos]);
+}
+
+QQuickItem *ColumnView::pop(QQuickItem *item)
+{
+    QQuickItem *removed = nullptr;
+
+    while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) {
+        removed = removeItem(m_contentItem->m_items.last());
+        // if no item has been passed, just pop one
+        if (!item) {
+            break;
+        }
+    }
+    return removed;
+}
+
+void ColumnView::clear()
+{
+    for (QQuickItem *item : std::as_const(m_contentItem->m_items)) {
+        removeItem(item);
+    }
+    m_contentItem->m_items.clear();
+    Q_EMIT contentChildrenChanged();
+}
+
+bool ColumnView::containsItem(QQuickItem *item)
+{
+    return m_contentItem->m_items.contains(item);
+}
+
+QQuickItem *ColumnView::itemAt(qreal x, qreal y)
+{
+    return m_contentItem->childAt(x, y);
+}
+
+ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object)
+{
+    return new ColumnViewAttached(object);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ColumnView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+#else
+void ColumnView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+#endif
+{
+    m_contentItem->setY(m_topPadding);
+    m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding);
+    m_contentItem->m_shouldAnimate = false;
+    polish();
+
+    m_contentItem->updateVisibleItems();
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QQuickItem::geometryChanged(newGeometry, oldGeometry);
+#else
+    QQuickItem::geometryChange(newGeometry, oldGeometry);
+#endif
+}
+
+bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event)
+{
+    if (!m_interactive || item == m_contentItem) {
+        return QQuickItem::childMouseEventFilter(item, event);
+    }
+
+    switch (event->type()) {
+    case QEvent::MouseButtonPress: {
+        QMouseEvent *me = static_cast<QMouseEvent *>(event);
+
+        if (me->button() != Qt::LeftButton) {
+            return false;
+        }
+
+        // On press, we set the current index of the view to the root item
+        QQuickItem *candidateItem = item;
+        while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
+            candidateItem = candidateItem->parentItem();
+        }
+        if (candidateItem->parentItem() == m_contentItem) {
+            setCurrentIndex(m_contentItem->m_items.indexOf(candidateItem));
+        }
+
+        // if !m_acceptsMouse we don't drag with mouse
+        if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
+            event->setAccepted(false);
+            return false;
+        }
+
+        m_contentItem->m_slideAnim->stop();
+        if (item->property("preventStealing").toBool()) {
+            m_contentItem->snapToItem();
+            return false;
+        }
+        m_oldMouseX = m_startMouseX = mapFromItem(item, me->localPos()).x();
+        m_oldMouseY = m_startMouseY = mapFromItem(item, me->localPos()).y();
+
+        m_mouseDown = true;
+        me->setAccepted(false);
+        setKeepMouseGrab(false);
+
+        break;
+    }
+    case QEvent::MouseMove: {
+        QMouseEvent *me = static_cast<QMouseEvent *>(event);
+
+        if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
+            return false;
+        }
+
+        if (!(me->buttons() & Qt::LeftButton)) {
+            return false;
+        }
+
+        const QPointF pos = mapFromItem(item, me->localPos());
+
+        bool verticalScrollIntercepted = false;
+
+        QQuickItem *candidateItem = item;
+        while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) {
+            candidateItem = candidateItem->parentItem();
+        }
+        if (candidateItem->parentItem() == m_contentItem) {
+            ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
+            if (attached->preventStealing()) {
+                return false;
+            }
+        }
+
+        {
+            ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(candidateItem, true));
+
+            ScrollIntentionEvent scrollIntentionEvent;
+            scrollIntentionEvent.delta = QPointF(pos.x() - m_oldMouseX, pos.y() - m_oldMouseY);
+
+            Q_EMIT attached->scrollIntention(&scrollIntentionEvent);
+
+            if (scrollIntentionEvent.accepted) {
+                verticalScrollIntercepted = true;
+                event->setAccepted(true);
+            }
+        }
+
+        if ((!keepMouseGrab() && item->keepMouseGrab()) || item->property("preventStealing").toBool()) {
+            m_contentItem->snapToItem();
+            m_oldMouseX = pos.x();
+            m_oldMouseY = pos.y();
+            return false;
+        }
+
+        const bool wasDragging = m_dragging;
+        // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves
+        m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->localPos()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3;
+
+        if (m_dragging != wasDragging) {
+            m_moving = true;
+            Q_EMIT movingChanged();
+            Q_EMIT draggingChanged();
+        }
+
+        if (m_dragging) {
+            m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX);
+        }
+
+        m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX;
+        m_oldMouseX = pos.x();
+        m_oldMouseY = pos.y();
+
+        setKeepMouseGrab(m_dragging);
+        me->setAccepted(m_dragging);
+
+        return m_dragging && !verticalScrollIntercepted;
+    }
+    case QEvent::MouseButtonRelease: {
+        QMouseEvent *me = static_cast<QMouseEvent *>(event);
+        if (item->property("preventStealing").toBool()) {
+            return false;
+        }
+
+        if (me->button() == Qt::BackButton && m_currentIndex > 0) {
+            setCurrentIndex(m_currentIndex - 1);
+            me->accept();
+            return true;
+        } else if (me->button() == Qt::ForwardButton) {
+            setCurrentIndex(m_currentIndex + 1);
+            me->accept();
+            return true;
+        }
+
+        if (!m_acceptsMouse && me->source() == Qt::MouseEventNotSynthesized) {
+            return false;
+        }
+
+        if (me->button() != Qt::LeftButton) {
+            return false;
+        }
+
+        m_mouseDown = false;
+
+        if (m_dragging) {
+            m_contentItem->snapToItem();
+            m_contentItem->m_lastDragDelta = 0;
+            m_dragging = false;
+            Q_EMIT draggingChanged();
+        }
+
+        event->accept();
+
+        // if a drag happened, don't pass the event
+        const bool block = keepMouseGrab();
+        setKeepMouseGrab(false);
+
+        me->setAccepted(block);
+        return block;
+    }
+    default:
+        break;
+    }
+
+    return QQuickItem::childMouseEventFilter(item, event);
+}
+
+void ColumnView::mousePressEvent(QMouseEvent *event)
+{
+    if (!m_acceptsMouse && event->source() == Qt::MouseEventNotSynthesized) {
+        event->setAccepted(false);
+        return;
+    }
+
+    if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) {
+        event->accept();
+        return;
+    }
+
+    if (!m_interactive) {
+        return;
+    }
+
+    m_contentItem->snapToItem();
+    m_oldMouseX = event->localPos().x();
+    m_startMouseX = event->localPos().x();
+    m_mouseDown = true;
+    setKeepMouseGrab(false);
+    event->accept();
+}
+
+void ColumnView::mouseMoveEvent(QMouseEvent *event)
+{
+    if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) {
+        event->accept();
+        return;
+    }
+
+    if (!m_interactive) {
+        return;
+    }
+
+    const bool wasDragging = m_dragging;
+    // Same startDragDistance * 2 as the event filter
+    m_dragging = keepMouseGrab() || qAbs(event->localPos().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2;
+    if (m_dragging != wasDragging) {
+        m_moving = true;
+        Q_EMIT movingChanged();
+        Q_EMIT draggingChanged();
+    }
+
+    setKeepMouseGrab(m_dragging);
+
+    if (m_dragging) {
+        m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX);
+    }
+
+    m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX;
+    m_oldMouseX = event->pos().x();
+    event->accept();
+}
+
+void ColumnView::mouseReleaseEvent(QMouseEvent *event)
+{
+    if (event->button() == Qt::BackButton && m_currentIndex > 0) {
+        setCurrentIndex(m_currentIndex - 1);
+        event->accept();
+        return;
+    } else if (event->button() == Qt::ForwardButton) {
+        setCurrentIndex(m_currentIndex + 1);
+        event->accept();
+        return;
+    }
+
+    m_mouseDown = false;
+
+    if (!m_interactive) {
+        return;
+    }
+
+    m_contentItem->snapToItem();
+    m_contentItem->m_lastDragDelta = 0;
+
+    if (m_dragging) {
+        m_dragging = false;
+        Q_EMIT draggingChanged();
+    }
+
+    setKeepMouseGrab(false);
+    event->accept();
+}
+
+void ColumnView::mouseUngrabEvent()
+{
+    m_mouseDown = false;
+
+    if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) {
+        m_contentItem->snapToItem();
+    }
+    m_contentItem->m_lastDragDelta = 0;
+
+    if (m_dragging) {
+        m_dragging = false;
+        Q_EMIT draggingChanged();
+    }
+
+    setKeepMouseGrab(false);
+}
+
+void ColumnView::classBegin()
+{
+    auto syncColumnWidth = [this]() {
+        m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->gridUnit() * 20;
+        Q_EMIT columnWidthChanged();
+    };
+
+    connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth);
+    syncColumnWidth();
+
+    auto syncDuration = [this]() {
+        m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->longDuration());
+        Q_EMIT scrollDurationChanged();
+    };
+
+    connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration);
+    syncDuration();
+
+    QQuickItem::classBegin();
+}
+
+void ColumnView::componentComplete()
+{
+    m_complete = true;
+    QQuickItem::componentComplete();
+}
+
+void ColumnView::updatePolish()
+{
+    m_contentItem->layoutItems();
+}
+
+void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
+{
+    switch (change) {
+    case QQuickItem::ItemChildAddedChange:
+        if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) {
+            addItem(value.item);
+        }
+        break;
+    default:
+        break;
+    }
+    QQuickItem::itemChange(change, value);
+}
+
+void ColumnView::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item)
+{
+    // This can only be called from QML
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return;
+    }
+
+    view->m_contentItem->m_items.append(item);
+    connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
+        view->removeItem(item);
+    });
+
+    ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+    attached->setOriginalParent(item->parentItem());
+    attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
+
+    item->setParentItem(view->m_contentItem);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+int ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
+#else
+qsizetype ColumnView::contentChildren_count(QQmlListProperty<QQuickItem> *prop)
+#endif
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return 0;
+    }
+
+    return view->m_contentItem->m_items.count();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, int index)
+#else
+QQuickItem *ColumnView::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index)
+#endif
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return nullptr;
+    }
+
+    if (index < 0 || index >= view->m_contentItem->m_items.count()) {
+        return nullptr;
+    }
+    return view->m_contentItem->m_items.value(index);
+}
+
+void ColumnView::contentChildren_clear(QQmlListProperty<QQuickItem> *prop)
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return;
+    }
+
+    return view->m_contentItem->m_items.clear();
+}
+
+QQmlListProperty<QQuickItem> ColumnView::contentChildren()
+{
+    return QQmlListProperty<QQuickItem>(this, //
+                                        nullptr,
+                                        contentChildren_append,
+                                        contentChildren_count,
+                                        contentChildren_at,
+                                        contentChildren_clear);
+}
+
+void ColumnView::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return;
+    }
+
+    view->m_contentData.append(object);
+    QQuickItem *item = qobject_cast<QQuickItem *>(object);
+    // exclude repeaters from layout
+    if (item && item->inherits("QQuickRepeater")) {
+        item->setParentItem(view);
+
+        connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel()));
+
+    } else if (item) {
+        view->m_contentItem->m_items.append(item);
+        connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() {
+            view->removeItem(item);
+        });
+
+        ColumnViewAttached *attached = qobject_cast<ColumnViewAttached *>(qmlAttachedPropertiesObject<ColumnView>(item, true));
+        attached->setOriginalParent(item->parentItem());
+        attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership);
+
+        item->setParentItem(view->m_contentItem);
+
+    } else {
+        object->setParent(view);
+    }
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+int ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
+#else
+qsizetype ColumnView::contentData_count(QQmlListProperty<QObject> *prop)
+#endif
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return 0;
+    }
+
+    return view->m_contentData.count();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, int index)
+#else
+QObject *ColumnView::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index)
+#endif
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return nullptr;
+    }
+
+    if (index < 0 || index >= view->m_contentData.count()) {
+        return nullptr;
+    }
+    return view->m_contentData.value(index);
+}
+
+void ColumnView::contentData_clear(QQmlListProperty<QObject> *prop)
+{
+    ColumnView *view = static_cast<ColumnView *>(prop->object);
+    if (!view) {
+        return;
+    }
+
+    return view->m_contentData.clear();
+}
+
+QQmlListProperty<QObject> ColumnView::contentData()
+{
+    return QQmlListProperty<QObject>(this, //
+                                     nullptr,
+                                     contentData_append,
+                                     contentData_count,
+                                     contentData_at,
+                                     contentData_clear);
+}
+
+#include "moc_columnview.cpp"
diff --git a/src/columnview.h b/src/columnview.h
new file mode 100644 (file)
index 0000000..2834ca0
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QPointer>
+#include <QQuickItem>
+#include <QVariant>
+
+class ContentItem;
+class ColumnView;
+
+class ScrollIntentionEvent : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QPointF delta MEMBER delta CONSTANT)
+    Q_PROPERTY(bool accepted MEMBER accepted)
+public:
+    ScrollIntentionEvent()
+    {
+    }
+    ~ScrollIntentionEvent() override
+    {
+    }
+
+    QPointF delta;
+    bool accepted = false;
+};
+
+/**
+ * This attached property is available to every child Item of the ColumnView,
+ * giving access to view and page information such as position and information for layouting.
+ * @since org.kde.kirigami 2.7
+ */
+class ColumnViewAttached : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief The index position of the column in the view, starting from 0.
+     */
+    Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
+
+    /**
+     * @brief This property sets whether the item will expand and take the whole viewport space minus the ::reservedSpace.
+     */
+    Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
+
+    /**
+     * @brief This property holds the reserved space in pixels
+     * applied to every item with ::fillWidth set to @c true.
+     *
+     * Every item that has ::fillWidth set to @c true will subtract this amount from the viewports width.
+     */
+    Q_PROPERTY(qreal reservedSpace READ reservedSpace WRITE setReservedSpace NOTIFY reservedSpaceChanged)
+
+    /**
+     * @brief This property sets whether the column view will manage input
+     * events from its children.
+     * 
+     * The ColumnView uses an
+     * <a href="https://doc.qt.io/qt-5/eventsandfilters.html#event-filters">event filter</a>
+     * to intercept information about its children like layouting information.
+     * This may conflict with its children input events in special cases.
+     *  
+     * If you want to guarantee that a child of the column view will not be
+     * intercepted by this event filter and that input events are managed by the
+     * child, set this to @c true.
+     * 
+     * This is desirable in special cases where a component has a single purpose
+     * and is managed solely via input events, such as a map or document viewer.
+     * 
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-mousearea.html#preventStealing-prop">MouseArea.preventStealing</a>
+     */
+    Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged)
+
+    /**
+     * @brief This property sets whether this page will always be visible.
+     *
+     * When set to @c true, the page will either stay at the left or right side.
+     */
+    Q_PROPERTY(bool pinned READ isPinned WRITE setPinned NOTIFY pinnedChanged)
+
+    /**
+     * @brief This is an attached property for Items to directly access the ColumnView.
+     */
+    Q_PROPERTY(ColumnView *view READ view NOTIFY viewChanged)
+
+    /**
+     * @brief This property holds whether this column is at least partly visible in ColumnView's viewport.
+     * @since KDE Frameworks 5.77
+     */
+    Q_PROPERTY(bool inViewport READ inViewport NOTIFY inViewportChanged)
+
+public:
+    ColumnViewAttached(QObject *parent = nullptr);
+    ~ColumnViewAttached() override;
+
+    void setIndex(int index);
+    int index() const;
+
+    void setFillWidth(bool fill);
+    bool fillWidth() const;
+
+    qreal reservedSpace() const;
+    void setReservedSpace(qreal space);
+
+    ColumnView *view();
+    void setView(ColumnView *view);
+
+    // Private API, not for QML use
+    QQuickItem *originalParent() const;
+    void setOriginalParent(QQuickItem *parent);
+
+    bool shouldDeleteOnRemove() const;
+    void setShouldDeleteOnRemove(bool del);
+
+    bool preventStealing() const;
+    void setPreventStealing(bool prevent);
+
+    bool isPinned() const;
+    void setPinned(bool pinned);
+
+    bool inViewport() const;
+    void setInViewport(bool inViewport);
+
+Q_SIGNALS:
+    void indexChanged();
+    void fillWidthChanged();
+    void reservedSpaceChanged();
+    void viewChanged();
+    void preventStealingChanged();
+    void pinnedChanged();
+    void scrollIntention(ScrollIntentionEvent *event);
+    void inViewportChanged();
+
+private:
+    int m_index = -1;
+    bool m_fillWidth = false;
+    qreal m_reservedSpace = 0;
+    QPointer<ColumnView> m_view;
+    QPointer<QQuickItem> m_originalParent;
+    bool m_customFillWidth = false;
+    bool m_customReservedSpace = false;
+    bool m_shouldDeleteOnRemove = true;
+    bool m_preventStealing = false;
+    bool m_pinned = false;
+    bool m_inViewport = false;
+};
+
+/**
+ * ColumnView is a container that lays out items horizontally in a row,
+ * when not all items fit in the ColumnView, it will behave like a QtQuick.Flickable
+ * and will be a scrollable view which shows only a determined number of columns.
+ * The columns can either all have the same fixed size (recommended),
+ * size themselves with QtQuick.QQuickItem.implicitWidth,
+ * or automatically expand to take all the available width: by default the last column will always be the expanding one.
+ * Items inside the ColumnView can access info of the view and set layouting hints via ColumnViewAttached.
+ *
+ * This is the base for the implementation of org::kde::kirigami::PageRow.
+ *
+ * @see ColumnViewAttached
+ * @since org.kde.kirigami 2.7
+ */
+class ColumnView : public QQuickItem
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the ColumnView's column resizing strategy.
+     */
+    Q_PROPERTY(ColumnResizeMode columnResizeMode READ columnResizeMode WRITE setColumnResizeMode NOTIFY columnResizeModeChanged)
+
+    /**
+     * @brief The width of all columns when ::columnResizeMode is set to ``ColumnResizeMode::FixedColumns``.
+     */
+    Q_PROPERTY(qreal columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged)
+
+    /**
+     * @brief This property holds the column count.
+     */
+    Q_PROPERTY(int count READ count NOTIFY countChanged)
+
+    /**
+     * @brief This property holds the index of currently focused item.
+    * @note The current item will have keyboard focus.
+     */
+    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+
+    /**
+     * @brief This property points to the currently focused item.
+     * @note The focused item will have keyboard focus.
+     */
+    Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged)
+
+    /**
+     * @brief This property points to the contentItem of the view,
+     * which is the parent of the column items.
+     */
+    Q_PROPERTY(QQuickItem *contentItem READ contentItem CONSTANT)
+
+    /**
+     * @brief This property holds the view's horizontal scroll value in pixels.
+     */
+    Q_PROPERTY(qreal contentX READ contentX WRITE setContentX NOTIFY contentXChanged)
+
+    /**
+     * @brief This property holds the compound width of all columns in the view.
+     */
+    Q_PROPERTY(qreal contentWidth READ contentWidth NOTIFY contentWidthChanged)
+
+    /**
+     * @brief This property holds the view's top padding.
+     */
+    Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged)
+
+    /**
+     * @brief This property holds the view's bottom padding.
+     */
+    Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding NOTIFY bottomPaddingChanged)
+
+    /**
+     * @brief This property holds the scrolling animation's duration.
+     */
+    Q_PROPERTY(int scrollDuration READ scrollDuration WRITE setScrollDuration NOTIFY scrollDurationChanged)
+
+    /**
+     * @brief This property sets whether columns should be visually separated by a line.
+     */
+    Q_PROPERTY(bool separatorVisible READ separatorVisible WRITE setSeparatorVisible NOTIFY separatorVisibleChanged)
+
+    /**
+     * @brief This property holds the list of all visible items that are currently at least partially visible.
+     */
+    Q_PROPERTY(QList<QObject *> visibleItems READ visibleItems NOTIFY visibleItemsChanged)
+
+    /**
+     * @brief This property points to the first currently visible item.
+     */
+    Q_PROPERTY(QQuickItem *firstVisibleItem READ firstVisibleItem NOTIFY firstVisibleItemChanged)
+
+    /**
+     * @brief This property points to the last currently visible item.
+     */
+    Q_PROPERTY(QQuickItem *lastVisibleItem READ lastVisibleItem NOTIFY lastVisibleItemChanged)
+
+    // Properties to make it similar to Flickable
+    /**
+     * @brief This property specifies whether the user is currently dragging the view's contents with a touch gesture.
+     */
+    Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
+
+    /**
+     * @brief This property specifies whether the user is currently dragging
+     * the view's contents with a touch gesture or if the view is animating.
+     *
+     */
+    Q_PROPERTY(bool moving READ moving NOTIFY movingChanged)
+
+    /**
+     * @brief This property sets whether the view supports moving the contents by
+     * dragging them with a touch gesture.
+     */
+    Q_PROPERTY(bool interactive READ interactive WRITE setInteractive NOTIFY interactiveChanged)
+
+    /**
+     * @brief This property sets whether the view supports moving the contents
+     * by dragging them with a mouse.
+     */
+    Q_PROPERTY(bool acceptsMouse READ acceptsMouse WRITE setAcceptsMouse NOTIFY acceptsMouseChanged)
+
+    // Default properties
+    /**
+     * @brief This property holds a list of every column visual item that the view currently contains.
+     */
+    Q_PROPERTY(QQmlListProperty<QQuickItem> contentChildren READ contentChildren NOTIFY contentChildrenChanged FINAL)
+
+    /**
+     * @brief This property holds a list of every column visual and non-visual item that the view currently contains.
+     */
+    Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData FINAL)
+    Q_CLASSINFO("DefaultProperty", "contentData")
+
+public:
+    enum ColumnResizeMode {
+        /**
+         * @brief Every column is fixed at the same width specified by columnWidth property.
+         */
+        FixedColumns = 0,
+
+        /**
+         * @brief Columns take their width from the implicitWidth property.
+         */
+        DynamicColumns,
+
+        /**
+         * @brief Only one column is shown, and as wide as the viewport allows it.
+         */
+        SingleColumn,
+    };
+    Q_ENUM(ColumnResizeMode)
+
+    ColumnView(QQuickItem *parent = nullptr);
+    ~ColumnView() override;
+
+    // QML property accessors
+    ColumnResizeMode columnResizeMode() const;
+    void setColumnResizeMode(ColumnResizeMode mode);
+
+    qreal columnWidth() const;
+    void setColumnWidth(qreal width);
+
+    int currentIndex() const;
+    void setCurrentIndex(int index);
+
+    int scrollDuration() const;
+    void setScrollDuration(int duration);
+
+    bool separatorVisible() const;
+    void setSeparatorVisible(bool visible);
+
+    int count() const;
+
+    qreal topPadding() const;
+    void setTopPadding(qreal padding);
+
+    qreal bottomPadding() const;
+    void setBottomPadding(qreal padding);
+
+    QQuickItem *currentItem();
+
+    // NOTE: It's a QList<QObject *> as QML can't correctly build an Array out of QList<QQuickItem*>
+    QList<QObject *> visibleItems() const;
+    QQuickItem *firstVisibleItem() const;
+    QQuickItem *lastVisibleItem() const;
+
+    QQuickItem *contentItem() const;
+
+    QQmlListProperty<QQuickItem> contentChildren();
+    QQmlListProperty<QObject> contentData();
+
+    bool dragging() const;
+    bool moving() const;
+    qreal contentWidth() const;
+
+    qreal contentX() const;
+    void setContentX(qreal x) const;
+
+    bool interactive() const;
+    void setInteractive(bool interactive);
+
+    bool acceptsMouse() const;
+    void setAcceptsMouse(bool accepts);
+
+    // Api not intended for QML use
+    // can't do overloads in QML
+    QQuickItem *removeItem(QQuickItem *item);
+    QQuickItem *removeItem(int item);
+
+    // QML attached property
+    static ColumnViewAttached *qmlAttachedProperties(QObject *object);
+
+public Q_SLOTS:
+    /**
+     * @brief This method pushes a new item to the end of the view.
+     * @param item the new item which will be reparented and managed
+     */
+    void addItem(QQuickItem *item);
+
+    /**
+     * @brief This method inserts a new item in the view at a given position.
+     *
+     * The ::currentItem will not be changed, ::currentIndex will be adjusted
+     * accordingly if needed to keep the same current item.
+     *
+     * @param pos the position we want the new item to be inserted in
+     * @param item the new item which will be reparented and managed
+     */
+    void insertItem(int pos, QQuickItem *item);
+
+    /**
+     * @brief This method replaces an item in the view at a given position with a new item.
+     *
+     * The ::currentItem and ::currentIndex will not be changed.
+     *
+     * @param pos the position we want the new item to be placed in
+     * @param item the new item which will be reparented and managed
+     */
+    void replaceItem(int pos, QQuickItem *item);
+
+    /**
+     * @brief This method swaps items at given positions.
+     *
+     * The ::currentIndex property may be changed in order to keep ::currentItem the same.
+     *
+     * @param from the old position
+     * @param to the new position
+     */
+    void moveItem(int from, int to);
+
+    /**
+     * @brief This method removes an item from the view.
+     *
+     * Items will be reparented to their old parent.
+     * If they have JavaScript ownership and they didn't have an old parent, they will be destroyed.
+     *
+     * The ::currentIndex property may be changed in order to keep the same ::currentItem.
+     *
+     * @param item it can either be a pointer of an item or an integer specifying the position to remove
+     * @returns the item that has just been removed
+     */
+    QQuickItem *removeItem(const QVariant &item);
+
+    /**
+     * @brief This method removes every item after the specified @p item from
+     * the column view.
+     *
+     * If no item is specified, only the last item will be removed.
+     *
+     * Items that are inserted in a column view are reparented to the column
+     * view while keeping information about the original parent, if available.
+     *
+     * After calling this method, the items removed from the column view will
+     * either be reparented back to their original parent if they had one, or be
+     * deleted if they did not (this is normally the case if the item was
+     * created from a JavaScript expression).
+     *
+     * @param item it can either be a pointer to an item or an integer
+     * specifying the position to remove.
+     *
+     * @returns the item that has just been removed.
+     */
+    QQuickItem *pop(QQuickItem *item);
+
+    /**
+     * @brief This method removes every item in the view.
+     *
+     * Items will be reparented to their old parent.
+     * If they have JavaScript ownership and they didn't have an old parent, they will be destroyed
+     */
+    void clear();
+
+    /**
+     * @brief This method checks whether an item is present in the view.
+     */
+    bool containsItem(QQuickItem *item);
+
+    /**
+     * @brief This method returns the item in the view at a given position.
+     *
+     * If there is no item at the point specified, or the item is not visible null is returned.
+     *
+     * @param x The horizontal position of the item
+     * @param y The vertical position of the item
+     */
+    QQuickItem *itemAt(qreal x, qreal y);
+
+protected:
+    void classBegin() override;
+    void componentComplete() override;
+    void updatePolish() override;
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#else
+    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#endif
+    bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
+    void mousePressEvent(QMouseEvent *event) override;
+    void mouseMoveEvent(QMouseEvent *event) override;
+    void mouseReleaseEvent(QMouseEvent *event) override;
+    void mouseUngrabEvent() override;
+
+Q_SIGNALS:
+    /**
+     * @brief This signal is emitted when an item has been inserted into the view.
+     * @param position where the page has been inserted
+     * @param item a pointer to the new item
+     */
+    void itemInserted(int position, QQuickItem *item);
+
+    /**
+     * @brief This signal is emitted when an item has been removed from the view.
+     * @param item a pointer to the item that has just been removed
+     */
+    void itemRemoved(QQuickItem *item);
+
+    // Property notifiers
+    void contentChildrenChanged();
+    void columnResizeModeChanged();
+    void columnWidthChanged();
+    void currentIndexChanged();
+    void currentItemChanged();
+    void visibleItemsChanged();
+    void countChanged();
+    void draggingChanged();
+    void movingChanged();
+    void contentXChanged();
+    void contentWidthChanged();
+    void interactiveChanged();
+    void acceptsMouseChanged();
+    void scrollDurationChanged();
+    void separatorVisibleChanged();
+    void firstVisibleItemChanged();
+    void lastVisibleItemChanged();
+    void topPaddingChanged();
+    void bottomPaddingChanged();
+
+private:
+    static void contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *object);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    static int contentChildren_count(QQmlListProperty<QQuickItem> *prop);
+    static QQuickItem *contentChildren_at(QQmlListProperty<QQuickItem> *prop, int index);
+#else
+    static qsizetype contentChildren_count(QQmlListProperty<QQuickItem> *prop);
+    static QQuickItem *contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index);
+#endif
+    static void contentChildren_clear(QQmlListProperty<QQuickItem> *prop);
+
+    static void contentData_append(QQmlListProperty<QObject> *prop, QObject *object);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    static int contentData_count(QQmlListProperty<QObject> *prop);
+    static QObject *contentData_at(QQmlListProperty<QObject> *prop, int index);
+#else
+    static qsizetype contentData_count(QQmlListProperty<QObject> *prop);
+    static QObject *contentData_at(QQmlListProperty<QObject> *prop, qsizetype index);
+#endif
+    static void contentData_clear(QQmlListProperty<QObject> *prop);
+
+    QList<QObject *> m_contentData;
+
+    ContentItem *m_contentItem;
+    QPointer<QQuickItem> m_currentItem;
+
+    qreal m_oldMouseX = -1.0;
+    qreal m_startMouseX = -1.0;
+    qreal m_oldMouseY = -1.0;
+    qreal m_startMouseY = -1.0;
+    int m_currentIndex = -1;
+    qreal m_topPadding = 0;
+    qreal m_bottomPadding = 0;
+
+    bool m_mouseDown = false;
+    bool m_interactive = true;
+    bool m_dragging = false;
+    bool m_moving = false;
+    bool m_separatorVisible = true;
+    bool m_complete = false;
+    bool m_acceptsMouse = false;
+};
+
+QML_DECLARE_TYPEINFO(ColumnView, QML_HAS_ATTACHED_PROPERTIES)
diff --git a/src/columnview_p.h b/src/columnview_p.h
new file mode 100644 (file)
index 0000000..13e7cc5
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "columnview.h"
+
+#include <QPointer>
+#include <QQuickItem>
+
+class QPropertyAnimation;
+class QQmlComponent;
+namespace Kirigami {
+class Units;
+}
+
+class QmlComponentsPool : public QObject
+{
+    Q_OBJECT
+
+public:
+    QmlComponentsPool(QQmlEngine *engine);
+    ~QmlComponentsPool() override;
+
+    QQmlComponent *m_leadingSeparatorComponent = nullptr;
+    QQmlComponent *m_trailingSeparatorComponent = nullptr;
+    Kirigami::Units *m_units = nullptr;
+
+Q_SIGNALS:
+    void gridUnitChanged();
+    void longDurationChanged();
+
+private:
+    QObject *m_instance = nullptr;
+};
+
+class ContentItem : public QQuickItem
+{
+    Q_OBJECT
+
+public:
+    ContentItem(ColumnView *parent = nullptr);
+    ~ContentItem() override;
+
+    void layoutItems();
+    void layoutPinnedItems();
+    qreal childWidth(QQuickItem *child);
+    void updateVisibleItems();
+    void forgetItem(QQuickItem *item);
+    QQuickItem *ensureLeadingSeparator(QQuickItem *item);
+    QQuickItem *ensureTrailingSeparator(QQuickItem *item);
+
+    void setBoundedX(qreal x);
+    void animateX(qreal x);
+    void snapToItem();
+
+    inline qreal viewportLeft() const;
+    inline qreal viewportRight() const;
+
+protected:
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#else
+    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#endif
+
+private Q_SLOTS:
+    void syncItemsOrder();
+    void updateRepeaterModel();
+
+private:
+    ColumnView *m_view;
+    QPropertyAnimation *m_slideAnim;
+    QList<QQuickItem *> m_items;
+    QList<QObject *> m_visibleItems;
+    QPointer<QQuickItem> m_viewAnchorItem;
+    QHash<QQuickItem *, QQuickItem *> m_leadingSeparators;
+    QHash<QQuickItem *, QQuickItem *> m_trailingSeparators;
+    QHash<QObject *, QObject *> m_models;
+
+    qreal m_leftPinnedSpace = 361;
+    qreal m_rightPinnedSpace = 0;
+
+    qreal m_columnWidth = 0;
+    qreal m_lastDragDelta = 0;
+    ColumnView::ColumnResizeMode m_columnResizeMode = ColumnView::FixedColumns;
+    bool m_shouldAnimate = false;
+    friend class ColumnView;
+};
diff --git a/src/controls/AboutItem.qml b/src/controls/AboutItem.qml
new file mode 100644 (file)
index 0000000..ea67f94
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.4 as QQC2
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.3
+import org.kde.kirigami 2.20 as Kirigami
+
+//TODO: Kf6: move somewhere else which can depend from KAboutData?
+/**
+ * @brief This component is an "About" item that displays data about the application.
+ *
+ * It allows showing the defind copyright notice of the application together
+ * with the contributors and some information of which platform it's running on.
+ *
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/advanced-add_about_page">About Page in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/aboutview">KDE Human Interface Guidelines on Application Information</a>
+ * @see kirigami::AboutPage
+ * @since KDE Frameworks 5.87
+ * @since org.kde.kirigami 2.19
+ */
+Item {
+    id: aboutItem
+    /**
+     * @brief This property holds information that this component displays.
+     *
+     * The stored JSON information object has the same structure as KAboutData.
+     *
+     * Example usage:
+     * @code{.json}
+     * aboutData: {
+     *    "displayName" : "KirigamiApp",
+     *    "productName" : "kirigami/app",
+     *    "componentName" : "kirigamiapp",
+     *    "shortDescription" : "A Kirigami example",
+     *    "homepage" : "",
+     *    "bugAddress" : "submit@bugs.kde.org",
+     *    "version" : "5.14.80",
+     *    "otherText" : "",
+     *    "authors" : [
+     *        {
+     *            "name" : "...",
+     *            "task" : "",
+     *            "emailAddress" : "somebody@kde.org",
+     *            "webAddress" : "",
+     *            "ocsUsername" : ""
+     *        }
+     *    ],
+     *    "credits" : [],
+     *    "translators" : [],
+     *    "licenses" : [
+     *        {
+     *            "name" : "GPL v2",
+     *            "text" : "long, boring, license text",
+     *            "spdx" : "GPL-2.0"
+     *        }
+     *    ],
+     *    "copyrightStatement" : "© 2010-2018 Plasma Development Team",
+     *    "desktopFileName" : "org.kde.kirigamiapp"
+     * }
+     * @endcode
+     *
+     * @see KAboutData
+     */
+    property var aboutData
+
+    /**
+     * @brief This property holds a link to a "Get Involved" page.
+     *
+     * default: `"https://community.kde.org/Get_Involved" when application id starts with "org.kde.", otherwise it is empty.`
+     */
+    property url getInvolvedUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://community.kde.org/Get_Involved" : ""
+
+    /**
+     * @brief This property holds a link to a "Donate" page.
+     *
+     * default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
+     */
+    property url donateUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://kde.org/community/donations" : ""
+
+    /** @internal */
+    property bool _usePageStack: false
+
+    /**
+     * @brief This property specifies whether about item is in wide mode.
+     * @see kirigami::FormLayout::wideMode
+     * @property bool wideMode
+     */
+    property alias wideMode: form.wideMode
+
+    /** @internal */
+    default property alias _content: form.data
+
+    implicitHeight: form.implicitHeight
+    implicitWidth: form.implicitWidth
+
+    Component {
+        id: personDelegate
+
+        RowLayout {
+            Layout.fillWidth: true
+            property bool hasRemoteAvatar: (typeof(modelData.ocsUsername) !== "undefined" && modelData.ocsUsername.length > 0)
+
+            spacing: Kirigami.Units.smallSpacing * 2
+
+            Kirigami.Icon {
+                id: avatarIcon
+
+                implicitWidth: Kirigami.Units.iconSizes.medium
+                implicitHeight: implicitWidth
+
+                fallback: "user"
+                source: hasRemoteAvatar && remoteAvatars.checked ? "https://store.kde.org/avatar/%1?s=%2".arg(modelData.ocsUsername).arg(width) : "user"
+                visible: status !== Kirigami.Icon.Loading
+            }
+
+            // So it's clear that something is happening while avatar images are loaded
+            QQC2.BusyIndicator {
+                implicitWidth: Kirigami.Units.iconSizes.medium
+                implicitHeight: implicitWidth
+
+                visible: avatarIcon.status === Kirigami.Icon.Loading
+                running: visible
+            }
+
+            QQC2.Label {
+                Layout.fillWidth: true
+                readonly property bool withTask: typeof(modelData.task) !== "undefined" && modelData.task.length > 0
+                text: withTask ? qsTr("%1 (%2)").arg(modelData.name).arg(modelData.task) : modelData.name
+                wrapMode: Text.WordWrap
+            }
+
+            QQC2.ToolButton {
+                visible: typeof(modelData.ocsUsername) !== "undefined" && modelData.ocsUsername.length > 0
+                icon.name: "get-hot-new-stuff"
+                QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+                QQC2.ToolTip.visible: hovered
+                QQC2.ToolTip.text: qsTr("Visit %1's KDE Store page").arg(modelData.name)
+                onClicked: Qt.openUrlExternally("https://store.kde.org/u/%1".arg(modelData.ocsUsername))
+            }
+
+            QQC2.ToolButton {
+                visible: typeof(modelData.emailAddress) !== "undefined" && modelData.emailAddress.length > 0
+                icon.name: "mail-sent"
+                QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+                QQC2.ToolTip.visible: hovered
+                QQC2.ToolTip.text: qsTr("Send an email to %1").arg(modelData.emailAddress)
+                onClicked: Qt.openUrlExternally("mailto:%1".arg(modelData.emailAddress))
+            }
+
+            QQC2.ToolButton {
+                visible: typeof(modelData.webAddress) !== "undefined" && modelData.webAddress.length > 0
+                icon.name: "globe"
+                QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+                QQC2.ToolTip.visible: hovered
+                QQC2.ToolTip.text: (typeof(modelData.webAddress) === "undefined" && modelData.webAddress.length > 0) ? "" : modelData.webAddress
+                onClicked: Qt.openUrlExternally(modelData.webAddress)
+            }
+        }
+    }
+
+    Kirigami.FormLayout {
+        id: form
+
+        anchors.fill: parent
+
+        GridLayout {
+            columns: 2
+            Layout.fillWidth: true
+
+            Kirigami.Icon {
+                Layout.rowSpan: 3
+                Layout.preferredHeight: Kirigami.Units.iconSizes.huge
+                Layout.preferredWidth: height
+                Layout.maximumWidth: aboutItem.width / 3;
+                Layout.rightMargin: Kirigami.Units.largeSpacing
+                source: Kirigami.Settings.applicationWindowIcon || aboutItem.aboutData.programLogo || aboutItem.aboutData.programIconName || aboutItem.aboutData.componentName
+            }
+
+            Kirigami.Heading {
+                Layout.fillWidth: true
+                text: aboutItem.aboutData.displayName + " " + aboutItem.aboutData.version
+                wrapMode: Text.WordWrap
+            }
+
+            Kirigami.Heading {
+                Layout.fillWidth: true
+                level: 2
+                wrapMode: Text.WordWrap
+                text: aboutItem.aboutData.shortDescription
+            }
+
+            RowLayout {
+                spacing: Kirigami.Units.largeSpacing * 2
+
+                UrlButton {
+                    text: qsTr("Get Involved")
+                    url: aboutItem.getInvolvedUrl
+                    visible: url !== ""
+                }
+
+                UrlButton {
+                    text: qsTr("Donate")
+                    url: aboutItem.donateUrl
+                    visible: url !== ""
+                }
+
+                UrlButton {
+                    readonly property string theUrl: {
+                        if (page.aboutData.bugAddress !== "submit@bugs.kde.org") {
+                            return page.aboutData.bugAddress
+                        }
+                        const elements = page.aboutData.productName.split('/');
+                        let url = `https://bugs.kde.org/enter_bug.cgi?format=guided&product=${elements[0]}&version=${page.aboutData.version}`;
+                        if (elements.length === 2) {
+                            url += "&component=" + elements[1];
+                        }
+                        return url;
+                    }
+                    text: qsTr("Report a Bug")
+                    url: theUrl
+                    visible: theUrl !== ""
+                }
+            }
+        }
+
+        Separator {
+            Layout.fillWidth: true
+        }
+
+        Kirigami.Heading {
+            Kirigami.FormData.isSection: true
+            text: qsTr("Copyright")
+        }
+
+        QQC2.Label {
+            Layout.leftMargin: Kirigami.Units.gridUnit
+            text: aboutData.otherText
+            visible: text.length > 0
+            wrapMode: Text.WordWrap
+            Layout.fillWidth: true
+        }
+
+        QQC2.Label {
+            Layout.leftMargin: Kirigami.Units.gridUnit
+            text: aboutData.copyrightStatement
+            visible: text.length > 0
+            wrapMode: Text.WordWrap
+            Layout.fillWidth: true
+        }
+
+        UrlButton {
+            Layout.leftMargin: Kirigami.Units.gridUnit
+            url: aboutData.homepage
+            visible: url.length > 0
+            wrapMode: Text.WordWrap
+            Layout.fillWidth: true
+        }
+
+        OverlaySheet {
+            id: licenseSheet
+            property alias text: bodyLabel.text
+
+            contentItem: SelectableLabel {
+                id: bodyLabel
+                text: licenseSheet.text
+                wrapMode: Text.Wrap
+            }
+        }
+
+        Component {
+            id: licenseLinkButton
+
+            RowLayout {
+                Layout.leftMargin: Kirigami.Units.smallSpacing
+
+                QQC2.Label { text: qsTr("License:") }
+
+                LinkButton {
+                    Layout.fillWidth: true
+                    wrapMode: Text.WordWrap
+                    text: modelData.name
+                    onClicked: mouse => {
+                        licenseSheet.text = modelData.text
+                        licenseSheet.title = modelData.name
+                        licenseSheet.open()
+                    }
+                }
+            }
+        }
+
+        Component {
+            id: licenseTextItem
+
+            QQC2.Label {
+                Layout.leftMargin: Kirigami.Units.smallSpacing
+                Layout.fillWidth: true
+                wrapMode: Text.WordWrap
+                text: qsTr("License: %1").arg(modelData.name)
+            }
+        }
+
+        Repeater {
+            model: aboutData.licenses
+            delegate: _usePageStack ? licenseLinkButton : licenseTextItem
+        }
+
+        Kirigami.Heading {
+            Kirigami.FormData.isSection: visible
+            text: qsTr("Libraries in use")
+            Layout.fillWidth: true
+            wrapMode: Text.WordWrap
+            visible: Kirigami.Settings.information
+        }
+
+        Repeater {
+            model: Kirigami.Settings.information
+            delegate: QQC2.Label {
+                Layout.leftMargin: Kirigami.Units.gridUnit
+                Layout.fillWidth: true
+                wrapMode: Text.WordWrap
+                id: libraries
+                text: modelData
+            }
+        }
+
+        Repeater {
+            model: aboutData.components
+            delegate: QQC2.Label {
+                Layout.fillWidth: true
+                wrapMode: Text.WordWrap
+                Layout.leftMargin: Kirigami.Units.gridUnit
+                text: modelData.name + (modelData.version === "" ? "" : " %1".arg(modelData.version))
+            }
+        }
+
+        Kirigami.Heading {
+            Layout.fillWidth: true
+            Kirigami.FormData.isSection: visible
+            text: qsTr("Authors")
+            wrapMode: Text.WordWrap
+            visible: aboutData.authors.length > 0
+        }
+
+        QQC2.CheckBox {
+            id: remoteAvatars
+            visible: authorsRepeater.hasAnyRemoteAvatars
+            checked: false
+            text: qsTr("Show author photos")
+
+            Timer {
+                id: remotesThrottle
+                repeat: false
+                interval: 1
+                onTriggered: {
+                    let hasAnyRemotes = false;
+                    for (let i = 0; i < authorsRepeater.count; ++i) {
+                        const itm = authorsRepeater.itemAt(i);
+                        if (itm.hasRemoteAvatar) {
+                            hasAnyRemotes = true;
+                            break;
+                        }
+                    }
+                    authorsRepeater.hasAnyRemoteAvatars = hasAnyRemotes;
+                }
+            }
+        }
+
+        Repeater {
+            id: authorsRepeater
+            model: aboutData.authors
+            property bool hasAnyRemoteAvatars
+            delegate: personDelegate
+            onCountChanged: remotesThrottle.start()
+        }
+
+        Kirigami.Heading {
+            height: visible ? implicitHeight : 0
+            Kirigami.FormData.isSection: visible
+            text: qsTr("Credits")
+            visible: repCredits.count > 0
+        }
+
+        Repeater {
+            id: repCredits
+            model: aboutData.credits
+            delegate: personDelegate
+        }
+
+        Kirigami.Heading {
+            height: visible ? implicitHeight : 0
+            Kirigami.FormData.isSection: visible
+            text: qsTr("Translators")
+            visible: repTranslators.count > 0
+        }
+
+        Repeater {
+            id: repTranslators
+            model: aboutData.translators
+            delegate: personDelegate
+        }
+    }
+}
diff --git a/src/controls/AboutPage.qml b/src/controls/AboutPage.qml
new file mode 100644 (file)
index 0000000..929ffea
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.4 as QQC2
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.3
+import org.kde.kirigami 2.19 as Kirigami
+
+//TODO KF6: move somewhere else? kirigami addons?
+/**
+ * @brief This component is an "About" page that displays data about the application.
+ *
+ * It allows showing the defined copyright notice of the application together
+ * with the contributors and some information of which platform it's running on.
+ *
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/advanced-add_about_page">About Page in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/aboutview">KDE Human Interface Guidelines on Application Information</a>
+ * @see kirigami::AboutItem
+ * @since KDE Frameworks 5.52
+ * @since org.kde.kirigami 2.6
+ * @inherit kirigami::ScrollablePage
+ */
+Kirigami.ScrollablePage {
+    id: page
+
+//BEGIN properties
+    /**
+     * @brief This property holds a JSON object with the structure of KAboutData
+     * that will be displayed by the AboutPage.
+     *
+     * @see KAboutData
+     *
+     * Note that ``displayName``, ``version``, ``description``, and ``authors``
+     * keys are mandatory, while the rest of the keys are optional. Make sure
+     * to fill out as many optional keys as you can to provide more accurate
+     * crediting information, especially ``copyrightStatement``, which
+     * facilitates the management of the licenses used in your program.
+     *
+     * Example usage:
+     * @code{.json}
+     * aboutData: {
+     *    "displayName" : "KirigamiApp",
+     *    "productName" : "kirigami/app",
+     *    "componentName" : "kirigamiapp",
+     *    "shortDescription" : "A Kirigami example",
+     *    "homepage" : "",
+     *    "bugAddress" : "submit@bugs.kde.org",
+     *    "version" : "5.14.80",
+     *    "otherText" : "",
+     *    "authors" : [
+     *        {
+     *            "name" : "...",
+     *            "task" : "",
+     *            "emailAddress" : "somebody@kde.org",
+     *            "webAddress" : "",
+     *            "ocsUsername" : ""
+     *        }
+     *    ],
+     *    "credits" : [],
+     *    "translators" : [],
+     *    "licenses" : [
+     *        {
+     *            "name" : "GPL v2",
+     *            "text" : "long, boring, license text",
+     *            "spdx" : "GPL-2.0"
+     *        }
+     *    ],
+     *    "copyrightStatement" : "© 2010-2018 Plasma Development Team",
+     *    "desktopFileName" : "org.kde.kirigamiapp"
+     * }
+     * @endcode
+     * @property KAboutData aboutData
+     */
+    property alias aboutData: aboutItem.aboutData
+
+    /**
+     * @brief This property holds a link to a "Get Involved" page.
+     *
+     * default: `"https://community.kde.org/Get_Involved" when your application id starts with "org.kde.", otherwise is empty`
+     *
+     * @property url getInvolvedUrl
+     */
+    property alias getInvolvedUrl: aboutItem.getInvolvedUrl
+
+    /**
+     * @brief This property holds a link to a "Donate" page.
+     * @since KDE Frameworks 5.101
+     *
+     * default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
+     */
+    property url donateUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://kde.org/community/donations" : ""
+
+    /** @internal */
+    default property alias _content: aboutItem._content
+//END properties
+
+    title: qsTr("About %1").arg(page.aboutData.displayName)
+
+    Kirigami.AboutItem {
+        id: aboutItem
+        wideMode: page.width >= aboutItem.implicitWidth
+
+        _usePageStack: applicationWindow().pageStack ? true : false
+    }
+}
diff --git a/src/controls/AbstractApplicationHeader.qml b/src/controls/AbstractApplicationHeader.qml
new file mode 100644 (file)
index 0000000..ca5521f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.5 as Kirigami
+import "private" as P
+import "templates" as T
+
+
+//TODO KF6: remove
+/**
+ * @brief An item that can be used as a title for the application.
+ *
+ * Scrolling the main page will make it taller or shorter (through the point of going away)
+ * It's a behavior similar to the typical mobile web browser addressbar
+ * the minimum, preferred and maximum heights of the item can be controlled with
+ * * ``minimumHeight``: default is 0, i.e. hidden
+ * * ``preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
+ * * ``maximumHeight``: default is Kirigami.Units.gridUnit * 3
+ *
+ * To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
+ *
+ * @deprecated This will be deleted in KF6.
+ * @inherit kirigami::templates::AbstractApplicationHeader
+ */
+T.AbstractApplicationHeader {
+    id: root
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: Kirigami.Theme.Header
+
+    background: Rectangle {
+        color: Kirigami.Theme.backgroundColor
+        P.EdgeShadow {
+            id: shadow
+            visible: root.separatorVisible
+            anchors {
+                right: parent.right
+                left: parent.left
+                top: parent.bottom
+            }
+            edge: Qt.TopEdge
+            opacity: (!root.page.header || root.page.header.toString().indexOf("ToolBar") === -1)
+            Behavior on opacity {
+                OpacityAnimator {
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+}
diff --git a/src/controls/AbstractApplicationItem.qml b/src/controls/AbstractApplicationItem.qml
new file mode 100644 (file)
index 0000000..d1dfb50
--- /dev/null
@@ -0,0 +1,483 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQml 2.14
+import QtQuick.Templates 2.12 as T
+import QtQuick.Window 2.12
+import org.kde.kirigami 2.14 as Kirigami
+import "templates/private" as TP
+
+/**
+ * @brief An item that provides the features of AbstractApplicationWindow without the window itself.
+ *
+ * This allows embedding into a larger application.
+ * Unless you need extra flexibility it is recommended to use ApplicationItem instead.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.AbstractApplicationItem {
+ *  [...]
+ *     globalDrawer: Kirigami.GlobalDrawer {
+ *         actions: [
+ *            Kirigami.Action {
+ *                text: "View"
+ *                icon.name: "view-list-icons"
+ *                Kirigami.Action {
+ *                        text: "action 1"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 2"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 3"
+ *                }
+ *            },
+ *            Kirigami.Action {
+ *                text: "Sync"
+ *                icon.name: "folder-sync"
+ *            }
+ *         ]
+ *     }
+ *
+ *     contextDrawer: Kirigami.ContextDrawer {
+ *         id: contextDrawer
+ *     }
+ *
+ *     pageStack: Kirigami.PageRow {
+ *         ...
+ *     }
+ *  [...]
+ * }
+ * @endcode
+ *
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds the stack used to allocate the pages and to manage the
+     * transitions between them.
+     *
+     * Put a container here, such as QtQuick.Controls.StackView or PageRow.
+     */
+    property Item pageStack
+
+    /**
+     * @brief This property exists for compatibility with Applicationwindow.
+     *
+     * default: ``Window.activeFocusItem``
+     */
+    readonly property Item activeFocusItem: Window.activeFocusItem
+
+    /**
+     * @brief This property holds the font for this item.
+     *
+     * default: ``Kirigami.Theme.defaultFont``
+     */
+    property font font: Kirigami.Theme.defaultFont
+
+    /**
+     * @brief This property holds the palette for this item.
+     *
+     * default: ``Kirigami.Theme.palette``
+     */
+    property var palette: Kirigami.Theme.palette
+
+    /**
+     * @brief This property holds the locale for this item.
+     */
+    property Locale locale
+
+    /**
+     * @brief This property holds an item that can be used as a menuBar for the application.
+     * @warning This will be restricted to QQC2.MenuBar in KF6.
+     */
+    property Item menuBar // TODO KF6: restrict type to QQC2.MenuBar
+
+    /**
+    * @brief This property holds an item that can be used as a title for the application.
+    *
+    * Scrolling the main page will make it taller or shorter (through the point of going away).
+    *
+    * It's a behavior similar to the typical mobile web browser addressbar.
+    *
+    * The minimum, preferred and maximum heights of the item can be controlled with
+    *
+    * * ``Layout.minimumHeight``: default is 0, i.e. hidden
+    * * ``Layout.preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
+    * * ``Layout.maximumHeight``: default is Kirigami.Units.gridUnit * 3
+    *
+    * To achieve a titlebar that stays completely fixed, just set the 3 sizes as the same.
+    *
+    * @warning This will be restricted to Kirigami.ApplicationHeader in KF6.
+    * @property kirigami::ApplicationHeader header
+    */
+    property Item header // TODO KF6 restrict the type to Kirigami.ApplicationHeader
+
+    /**
+     * @brief This property holds an item that can be used as a footer for the application.
+     */
+    property Item footer
+
+    /**
+     * @brief This property sets whether the standard chrome of the app is visible.
+     *
+     * These are the action button, the drawer handles and the application header.
+     *
+     * default: ``true``
+     */
+    property bool controlsVisible: true
+
+    /**
+     * @brief This property holds the drawer for global actions.
+     *
+     * This drawer can be opened by sliding from the left screen edge
+     * or by either pressing on the handle or sliding it to the right.
+     *
+     * @note It is recommended to use the GlobalDrawer here.
+     * @property kirigami::OverlayDrawer globalDrawer
+     */
+    property OverlayDrawer globalDrawer
+
+    /**
+     * @brief This property specifies whether the application is in "widescreen" mode.
+     *
+     * This is enabled on desktops or horizontal tablets.
+     *
+     * @note Different styles can have their own logic for deciding this.
+     */
+    property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
+
+    /**
+     * @brief This property holds the drawer for context-dependent actions.
+     *
+     * This drawer can be opened by sliding from the right screen edge
+     * or by either pressing on the handle or sliding it to the left.
+     *
+     * @note It is recommended to use the ContextDrawer class here.
+     *
+     * The context drawer will display the previously defined contextual
+     * actions of the page that is currently active in the pageStack.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationItem {
+     *  [...]
+     *     contextDrawer: Kirigami.ContextDrawer {
+     *         id: contextDrawer
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     *
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.Page {
+     *   [...]
+     *     // setting the contextual actions
+     *     actions.contextualActions: [
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         },
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         }
+     *     ]
+     *   [...]
+     * }
+     * @endcode
+     *
+     *
+     * @property kirigami::ContextDrawer contextDrawer
+     */
+    property OverlayDrawer contextDrawer
+
+    /**
+     * @brief This property specifies whether the application is in reachable mode,
+     * for single hand use.
+     *
+     * The whole content of the application is moved down the screen to be
+     * reachable with the thumb. If wideScreen is true, or reachableModeEnabled is false,
+     * this property has no effect.
+     *
+     * @note This property should be treated as readonly. Use ``reachableModeEnabled`` instead.
+     *
+     * default: ``false``
+     *
+     * @see ::reachableModeEnabled
+     */
+    property bool reachableMode: false
+
+    /**
+     * @brief This property sets whether reachable mode can be used.
+     *
+     * default: ``true``
+     */
+    property bool reachableModeEnabled: true
+
+    /**
+     * @brief This property holds the list of all children of this item.
+     * @internal
+     * @property list<Object> __data
+     */
+    default property alias __data: contentItemRoot.data
+
+    /**
+     * @brief This property holds the Item of the main part of the Application UI.
+     */
+    readonly property Item contentItem: Item {
+        id: contentItemRoot
+        parent: root
+        anchors {
+            fill: parent
+            topMargin: controlsVisible ? (root.header ? root.header.height : 0) + (root.menuBar ? root.menuBar.height : 0) : 0
+            bottomMargin: controlsVisible && root.footer ? root.footer.height : 0
+            leftMargin: root.globalDrawer && root.globalDrawer.modal === false ? root.globalDrawer.contentItem.width * root.globalDrawer.position : 0
+            rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.contentItem.width * root.contextDrawer.position : 0
+        }
+
+        transform: Translate {
+            Behavior on y {
+                NumberAnimation {
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+            y: root.reachableMode && root.reachableModeEnabled && !root.wideScreen ? root.height/2 : 0
+            x: root.globalDrawer && root.globalDrawer.modal === true && root.globalDrawer.toString().indexOf("SplitDrawer") === 0 ? root.globalDrawer.contentItem.width * root.globalDrawer.position : 0
+        }
+    }
+
+    /**
+     * @brief This property holds background's color.
+     *
+     * default: ``Kirigami.Theme.backgroundColor``
+     */
+    property color color: Kirigami.Theme.backgroundColor
+
+    /**
+     * @brief This property holds the background of the Application UI.
+     */
+    property Item background
+
+    property alias overlay: overlayRoot
+//END properties
+
+//BEGIN functions
+    /**
+     * @brief This function shows a passive notification at the bottom of the app window
+     * lasting for few seconds, with an optional action button.
+     *
+     * @param message The text message to be shown to the user.
+     * @param timeout How long to show the message:
+     *            possible values: "short", "long" or the number of milliseconds
+     * @param actionText Text in the action button, if any.
+     * @param callBack A JavaScript function that will be executed when the
+     *            user clicks the button.
+     */
+    function showPassiveNotification(message, timeout, actionText, callBack) {
+        notificationsObject.showNotification(message, timeout, actionText, callBack);
+    }
+
+    /**
+     * @brief This function hides the passive notification at specified index, if any is shown.
+     * @param index Index of the notification to hide. Default is 0 (oldest notification).
+    */
+    function hidePassiveNotification(index = 0) {
+        notificationsObject.hideNotification(index);
+    }
+
+    /**
+     * @brief This property returns a pointer to the main instance of
+     * AbstractApplicationItem.
+     *
+     * This is available to any children of this Item, including those
+     * instantiated from separate QML files, making interoperation with
+     * multiple files easier.
+     *
+     * Use this whenever you need access to properties that are available to
+     * the main AbstractApplicationItem, such as its pageStack, globalDrawer
+     * or header.
+     *
+     * @see AbstractApplicationWindow::applicationWindow()
+     *
+     * @returns a pointer to the instantiated AbstractApplicationItem.
+     */
+    function applicationWindow() {
+        return root;
+    }
+//END functions
+
+//BEGIN signals handlers
+    onMenuBarChanged: {
+        menuBar.parent = root.contentItem
+        if (menuBar.z === undefined) {
+            menuBar.z = 1;
+        }
+        if (menuBar instanceof T.ToolBar) {
+            menuBar.position = T.ToolBar.Footer
+        } else if (menuBar instanceof T.TabBar) {
+            menuBar.position = T.TabBar.Footer
+        } else if (menuBar instanceof T.DialogButtonBox) {
+            menuBar.position = T.DialogButtonBox.Footer
+        }
+        menuBar.width = Qt.binding(() => root.contentItem.width)
+        // FIXME: (root.header.height ?? 0) when we can depend from 5.15
+        menuBar.y = Qt.binding(() => -menuBar.height - (root.header.height ? root.header.height : 0))
+    }
+
+    onHeaderChanged: {
+        header.parent = root.contentItem
+        if (header.z === undefined) {
+            header.z = 1;
+        }
+        if (header instanceof T.ToolBar) {
+            header.position = T.ToolBar.Header
+        } else if (header instanceof T.TabBar) {
+            header.position = T.TabBar.Header
+        } else if (header instanceof T.DialogButtonBox) {
+            header.position = T.DialogButtonBox.Header
+        }
+        header.width = Qt.binding(() => root.contentItem.width)
+        header.y = Qt.binding(() => -header.height)
+    }
+
+    onFooterChanged: {
+        footer.parent = root.contentItem
+        if (footer.z === undefined) {
+            footer.z = 1;
+        }
+        if (footer instanceof T.ToolBar) {
+            footer.position = T.ToolBar.Footer
+        } else if (footer instanceof T.TabBar) {
+            footer.position = T.TabBar.Footer
+        } else if (footer instanceof T.DialogButtonBox) {
+            footer.position = T.DialogButtonBox.Footer
+        }
+        footer.width = Qt.binding(() => root.contentItem.width)
+        footer.y = Qt.binding(() => root.contentItem.height)
+    }
+
+    onBackgroundChanged: {
+        background.parent = root.contentItem
+        if (background.z === undefined) {
+            background.z = -1;
+        }
+        background.anchors.fill = background.parent
+    }
+
+    // NOTE: Don't want overscroll in landscape mode
+    onWidthChanged: {
+        if (width > height) {
+            root.reachableMode = false;
+        }
+    }
+
+    onPageStackChanged: pageStack.parent = root.contentItem;
+//END signals handlers
+
+    LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
+    LayoutMirroring.childrenInherit: true
+
+    TP.PassiveNotificationsManager {
+        id: notificationsObject
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+        z: 1
+    }
+
+    Item {
+        anchors.fill: parent
+        parent: root.parent || root
+        z: 999999
+        Rectangle {
+            z: -1
+            anchors.fill: parent
+            color: "black"
+            visible: contextDrawer && contextDrawer.modal
+            parent: contextDrawer ? contextDrawer.background.parent.parent : overlayRoot
+            opacity: contextDrawer ? contextDrawer.position * 0.6 : 0
+        }
+        Rectangle {
+            z: -1
+            anchors.fill: parent
+            color: "black"
+            visible: globalDrawer && globalDrawer.modal
+            parent: globalDrawer ? globalDrawer.background.parent.parent : overlayRoot
+            opacity: globalDrawer ? globalDrawer.position * 0.6 : 0
+        }
+        Item {
+            id: overlayRoot
+            z: -1
+            anchors.fill: parent
+        }
+        Window.onWindowChanged: {
+            if (globalDrawer) {
+                globalDrawer.visible = globalDrawer.drawerOpen;
+            }
+            if (contextDrawer) {
+                contextDrawer.visible = contextDrawer.drawerOpen;
+            }
+        }
+    }
+
+    MouseArea {
+        parent: root
+        z: -1
+        anchors.fill: parent
+        onClicked: mouse => {
+            root.reachableMode = false;
+        }
+        visible: root.reachableMode && root.reachableModeEnabled
+        Rectangle {
+            anchors.fill: parent
+            color: Qt.rgba(0, 0, 0, 0.3)
+            opacity: 0.15
+            Kirigami.Icon {
+                anchors.horizontalCenter: parent.horizontalCenter
+                y: x
+                width: Kirigami.Units.iconSizes.large
+                height: width
+                source: "go-up"
+            }
+        }
+    }
+
+    Binding {
+        when: globalDrawer !== undefined && root.visible
+        target: globalDrawer
+        property: "parent"
+        value: overlay
+        restoreMode: Binding.RestoreBinding
+    }
+    Binding {
+        when: contextDrawer !== undefined && root.visible
+        target: contextDrawer
+        property: "parent"
+        value: overlay
+        restoreMode: Binding.RestoreBinding
+    }
+
+    implicitWidth: Kirigami.Units.gridUnit * 30
+    implicitHeight: Kirigami.Units.gridUnit * 45
+    visible: true
+}
diff --git a/src/controls/AbstractApplicationWindow.qml b/src/controls/AbstractApplicationWindow.qml
new file mode 100644 (file)
index 0000000..3ad9c1b
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQml 2.15
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Window 2.5
+import org.kde.kirigami 2.4 as Kirigami
+import "templates/private" as TP
+/**
+ * @brief A window that provides some basic features needed for all apps.
+ *
+ * An abstract application window is a top-level component that provides
+ * several utilities for convenience such as:
+ * * ::applicationWindow()
+ * * ::globalDrawer
+ * * ::pageStack
+ * * ::wideScreen
+ *
+ * Use this class only if you need custom content for your application that is
+ * different from the PageRow behavior recommended by the HIG and provided
+ * by kirigami::AbstractApplicationWindow.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.ApplicationWindow {
+ *  [...]
+ *     globalDrawer: Kirigami.GlobalDrawer {
+ *         actions: [
+ *            Kirigami.Action {
+ *                text: "View"
+ *                icon.name: "view-list-icons"
+ *                Kirigami.Action {
+ *                        text: "action 1"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 2"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 3"
+ *                }
+ *            },
+ *            Kirigami.Action {
+ *                text: "Sync"
+ *                icon.name: "folder-sync"
+ *            }
+ *         ]
+ *     }
+ *
+ *     contextDrawer: Kirigami.ContextDrawer {
+ *         id: contextDrawer
+ *     }
+ *
+ *     pageStack.initialPage: Kirigami.Page {
+ *         actions.contextualActions: [
+ *             Kirigami.Action {
+ *                 text: "context action 1"
+ *             },
+ *             Kirigami.Action {
+ *                 text: "context action 2"
+ *             },
+ *             Kirigami.Action {
+ *                 text: "context action 3"
+ *             }
+ *         ]
+ *     }
+ *  [...]
+ * }
+ * @endcode
+ * @inherit QtQuick.Controls.ApplicationWindow
+ */
+QQC2.ApplicationWindow {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds the stack used to allocate the pages and to
+     * manage the transitions between them.
+     *
+     * Put a container here, such as QtQuick.Controls.StackView or PageRow.
+     */
+    property Item pageStack
+
+    /**
+     * @brief This property sets whether the standard chrome of the app is
+     * visible.
+     *
+     * These are the action button, the drawer handles, and the application
+     * header.
+     *
+     * default: ``true``
+     */
+    property bool controlsVisible: true
+
+    /**
+     * @brief This property holds the drawer for global actions.
+     *
+     * This drawer can be opened by sliding from the left screen edge
+     * or by either pressing on the handle or sliding it to the right.
+     *
+     * @note It is recommended to use the GlobalDrawer here.
+     * @property kirigami::OverlayDrawer globalDrawer
+     */
+    property OverlayDrawer globalDrawer
+
+    /**
+     * @brief This property specifies whether the application is in "widescreen" mode.
+     *
+     * This is enabled on desktops or horizontal tablets.
+     *
+     * @note Different styles can have their own logic for deciding this.
+     */
+    property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
+
+    /**
+     * @brief This property holds the drawer for context-dependent actions.
+     *
+     * The drawer that will be opened by sliding from the right screen edge
+     * or by dragging the ActionButton to the left.
+     *
+     * @note It is recommended to use the ContextDrawer class here.
+     *
+     * The context drawer will display the previously defined contextual
+     * actions of the page that is currently active in the pageStack.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationWindow {
+     *  [...]
+     *     contextDrawer: Kirigami.ContextDrawer {
+     *         id: contextDrawer
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     *
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.Page {
+     *   [...]
+     *     // setting the contextual actions
+     *     actions.contextualActions: [
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         },
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         }
+     *     ]
+     *   [...]
+     * }
+     * @endcode
+     *
+     * @property kirigami::ContextDrawer contextDrawer
+     */
+    property OverlayDrawer contextDrawer
+
+    /**
+     * @brief This property specifies whether the application is in reachable
+     * mode for single hand use.
+     *
+     * The whole content of the application is moved down the screen to be
+     * reachable with the thumb. If wideScreen is true, or reachableModeEnabled
+     * is false, this property has no effect.
+     *
+     * @warning This property should be treated as readonly. Use
+     * ``reachableModeEnabled`` instead.
+     *
+     * default: ``false``
+     */
+    property bool reachableMode: false
+
+    /**
+     * @brief This property sets whether reachable mode can be used.
+     */
+    property bool reachableModeEnabled: true
+
+    /**
+     * @brief This property holds a Kirigami action that will quit the
+     * application when triggered.
+     *
+     * @since KDE Frameworks 5.76
+     */
+    readonly property Kirigami.Action quitAction: Kirigami.Action {
+        text: qsTr("Quit")
+        icon.name: "application-exit";
+        shortcut: StandardKey.Quit
+        onTriggered: source => root.close();
+    }
+//END properties
+
+//BEGIN functions
+    /**
+     * @brief This function shows a passive notification at the bottom of the
+     * app window lasting for few seconds, with an optional action button.
+     *
+     * @param message Notification's text message that is shown to the user.
+     * @param timeout Notification's visibility duration. Possible values are:
+     * "short", "long" or the number of milliseconds. Default is ``7000``.
+     * @param actionText Notification's action button's text.
+     * @param callBack A JavaScript function that will be executed when the user
+     * clicks the button.
+     */
+    function showPassiveNotification(message, timeout, actionText, callBack) {
+        notificationsObject.showNotification(message, timeout, actionText, callBack);
+    }
+
+   /**
+    * @brief This function hides the passive notification at specified index,
+    * if any is shown.
+    *
+    * @param index Index of the notification to hide. Default is 0
+    * (oldest notification).
+    */
+    function hidePassiveNotification(index = 0) {
+        notificationsObject.hideNotification(index);
+    }
+
+    /**
+     * @brief This property returns a pointer
+     * to the main instance of AbstractApplicationWindow.
+     * 
+     * This is available to any children of this window,
+     * including those instantiated from separate QML files,
+     * making interoperation with multiple files easier.
+     * 
+     * Use this whenever you need access to properties
+     * that are available to the main AbstractApplicationWindow,
+     * such as its ::pageStack or ::globalDrawer.
+     * 
+     * @see AbstractApplicationItem::applicationWindow()
+     * @returns a pointer to the instantiated AbstractApplicationWindow.
+     */
+    function applicationWindow() {
+        return root;
+    }
+//END functions
+
+    LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
+    LayoutMirroring.childrenInherit: true
+
+    color: Kirigami.Theme.backgroundColor
+
+    TP.PassiveNotificationsManager {
+        id: notificationsObject
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+        z: 1
+    }
+
+    MouseArea {
+        parent: contentItem.parent
+        z: 0
+        anchors.fill: parent
+        onClicked: mouse => {
+            root.reachableMode = false;
+        }
+        visible: root.reachableMode && root.reachableModeEnabled
+        Rectangle {
+            anchors.fill: parent
+            color: Qt.rgba(0, 0, 0, 0.3)
+            opacity: 0.15
+            Kirigami.Icon {
+                anchors.horizontalCenter: parent.horizontalCenter
+                y: x
+                width: Kirigami.Units.iconSizes.large
+                height: width
+                source: "go-up"
+            }
+        }
+    }
+
+    contentItem.z: 1
+    contentItem.anchors.left: contentItem.parent.left
+    contentItem.anchors.right: contentItem.parent.right
+    contentItem.anchors.topMargin: root.wideScreen && header && controlsVisible ? header.height : 0
+    contentItem.anchors.leftMargin: root.globalDrawer && root.globalDrawer.modal === false && (!root.pageStack || root.pageStack.leftSidebar !== root.globalDrawer) ? root.globalDrawer.width * root.globalDrawer.position : 0
+    contentItem.anchors.rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.width * root.contextDrawer.position : 0
+
+    Binding {
+        when: menuBar !== undefined
+        target: menuBar
+        property: "x"
+        value: -contentItem.x
+        restoreMode: Binding.RestoreBinding
+    }
+    Binding {
+        when: header !== undefined
+        target: header
+        property: "x"
+        value: -contentItem.x
+        restoreMode: Binding.RestoreBinding
+    }
+    Binding {
+        when: footer !== undefined
+        target: footer
+        property: "x"
+        value: -contentItem.x
+        restoreMode: Binding.RestoreBinding
+    }
+
+    contentItem.transform: Translate {
+        Behavior on y {
+            NumberAnimation {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+        y: root.reachableMode && root.reachableModeEnabled && !root.wideScreen ? root.height/2 : 0
+        x: root.globalDrawer && root.globalDrawer.modal === true && root.globalDrawer.toString().indexOf("SplitDrawer") === 0 ? root.globalDrawer.contentItem.width * root.globalDrawer.position : 0
+    }
+    //Don't want overscroll in landscape mode
+    onWidthChanged: {
+        if (width > height) {
+            root.reachableMode = false;
+        }
+    }
+    Binding {
+        when: globalDrawer !== undefined && root.visible
+        target: globalDrawer
+        property: "parent"
+        value: overlay
+        restoreMode: Binding.RestoreBinding
+    }
+    Binding {
+        when: contextDrawer !== undefined && root.visible
+        target: contextDrawer
+        property: "parent"
+        value: overlay
+        restoreMode: Binding.RestoreBinding
+    }
+    onPageStackChanged: pageStack.parent = contentItem;
+
+    width: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 55
+    height: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 40
+    visible: true
+
+    Component.onCompleted: {
+        // Explicitly break the binding as we need this to be set only at startup.
+        // if the bindings are active, after this the window is resized by the
+        // compositor and then the bindings are reevaluated, then the window
+        // size would reset ignoring what the compositor asked.
+        // see BUG 433849
+        root.width = root.width;
+        root.height = root.height;
+    }
+
+    // This is needed because discover in mobile mode does not
+    // close with the global drawer open.
+    Shortcut {
+        sequence: root.quitAction.shortcut
+        context: Qt.ApplicationShortcut
+        onActivated: root.close();
+    }
+}
diff --git a/src/controls/AbstractCard.qml b/src/controls/AbstractCard.qml
new file mode 100644 (file)
index 0000000..11f3ebe
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "templates" as T
+import "private" as P
+
+/**
+ * @brief AbstractCard is the base for cards.
+ *
+ * A Card is a visual object that serves as an entry point for more detailed information.
+ * An abstractCard is empty, providing just the look and the base properties and signals
+ * for a QtQuick.Controls.ItemDelegate.
+ * It can be filled with any custom layout of items, its content is organized in
+ * 3 properties: ::header, contentItem and ::footer.
+ * Use this only when you need particular custom contents, for a standard layout
+ * for cards, use the Card component.
+ *
+ * @see kirigami::Card
+ * @see <a href="https://develop.kde.org/hig/components/editing/card">KDE Human Interface Guidelines on Cards</a>
+ * @since org.kde.kirigami 2.4
+ * @inherit kirigami::templates::AbstractCard
+ */
+T.AbstractCard {
+    id: root
+
+    background: P.DefaultCardBackground {
+        clickFeedback: root.showClickFeedback
+        hoverFeedback: root.hoverEnabled
+    }
+}
diff --git a/src/controls/AbstractChip.qml b/src/controls/AbstractChip.qml
new file mode 100644 (file)
index 0000000..1030451
--- /dev/null
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import "templates" as T
+import "private" as P
+
+/**
+ * AbstractChip is a visual object based on QtQuick.Controls.AbstractButton
+ * that provides a way to display predetermined elements
+ * with the visual styling of "tags" or "tokens". It provides
+ * the look, the base properties, and signals for an AbstractButton.
+ *
+ * You can control its visual feedback with the following properties:
+ * * `down: bool`: whether the chip component has been presssed (click feedback)
+ * * `hoverEnabled: bool`: whether the chip supports hover event (hover feedback)
+ * * `checked: bool`: whether the chip is in a checked state (check feedback)
+ *
+ * @since org.kde.kirigami 2.19
+ * @inherit kirigami::templates::AbstractChip
+ */
+T.AbstractChip {
+    id: root
+
+    background: P.DefaultChipBackground {}
+}
diff --git a/src/controls/AbstractItemViewHeader.qml b/src/controls/AbstractItemViewHeader.qml
new file mode 100644 (file)
index 0000000..f28321b
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Templates 2.0 as T2
+import org.kde.kirigami 2.4 as Kirigami
+
+// TODO KF6: remove this
+/**
+ * @brief An item that can be used as an header for a ListView.
+ *
+ * It will play nice with the margin policies of ScrollablePage and can
+ * automatically shrink when the list is scrolled, like the behavior
+ * of list headers in many mobile applications.
+ * @since org.kde.kirigami 2.1
+ * @inherit QtQuick.Controls.Control
+ * @deprecated This will be removed in KF6.
+ */
+T2.Control {
+    /**
+     * @brief This property holds the minimum height of the AbstractItemViewHeader.
+     */
+    property int minimumHeight: Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing * 2
+
+    /**
+     * @brief This property holds the maximum height of the AbstractItemViewHeader.
+     */
+    property int maximumHeight: Kirigami.Units.gridUnit * 6
+
+    /**
+     * @brief This property holds the ListView for which this item is the header.
+     *
+     * default: ``ListView.view``
+     */
+    property ListView view: ListView.view
+
+    width: view.width
+
+    implicitHeight: topPadding + bottomPadding + (view.headerPositioning === ListView.InlineHeader
+                                                    ? maximumHeight
+                                                    : Math.min(maximumHeight, Math.max(minimumHeight, maximumHeight - Math.max(0, view.contentY))))
+
+
+    z: 9
+    topPadding: applicationWindow() && !applicationWindow().wideScreen && applicationWindow().header ? applicationWindow().header.paintedHeight : 0
+    rightPadding: Kirigami.Units.gridUnit
+
+}
diff --git a/src/controls/AbstractListItem.qml b/src/controls/AbstractListItem.qml
new file mode 100644 (file)
index 0000000..ce80c2a
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "private" as P
+import "templates" as T
+
+//TODO KF6: not have list items at all (except perhaps swipelistitem which is an unuque ui) but rather have a set of layouts for lists items to be put inside standard QQC2 Delegate
+/**
+ * @brief An item delegate for the primitive ListView component.
+ *
+ * It's intended to make all listviews look coherent.
+ *
+ * @see <a href="https://develop.kde.org/hig/components/editing/list">KDE Human Interface Guidelines on List Views and List Items</a>
+ * @inherit kirigami::templates::AbstractListItem
+ */
+T.AbstractListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {}
+}
diff --git a/src/controls/Action.qml b/src/controls/Action.qml
new file mode 100644 (file)
index 0000000..447237e
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 2.4 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+
+/**
+ * @brief An item that represents an abstract Action
+ * @inherit QtQuick.Controls.Action
+ */
+QQC2.Action {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds whether the graphic representation of the action
+     * is supposed to be visible.
+     *
+     * It's up to the action representation to honor this property.
+     *
+     * default: ``true``
+     */
+    property bool visible: true
+
+    /**
+     * @brief This property holds the icon name for the action. This will pick the icon with the given name from the current theme.
+     * @deprecated Use the icon.name property instead.
+     * @property string iconName
+     */
+    property alias iconName: root.icon.name
+
+    /**
+     * @brief This property holds an url to an icon file or resource url for the action.
+     * @note Use this if you want a specific file rather than an icon from the theme.
+     * @deprecated Use icon.source property instead.
+     * @property url iconSource
+     */
+    property alias iconSource: root.icon.source
+
+    /**
+     * @brief This property holds the tooltip text that is shown when the cursor is hovering over the control.
+     *
+     * Leaving this undefined or setting it to an empty string means that no tooltip will be shown when
+     * the cursor is hovering over the control that triggers the tooltip.
+     *
+     * @warning Tooltips may not be supported on all platforms.
+     */
+    property string tooltip
+
+    /**
+     * @brief This property sets whether this action is a separator action.
+     *
+     * default: ``false``
+     */
+    property bool separator: false
+
+    /**
+     * @brief This property sets whether this action  becomes a title displaying
+     * its child actions as sub-items in GlobalDrawers and ContextDrawers.
+     *
+     * default: ``false``
+     *
+     * @since org.kde.kirigami 2.6
+     */
+    property bool expandible: false
+
+    /**
+     * @brief This property holds the parent action.
+     */
+    property QQC2.Action parent
+
+    /**
+     * @brief This property sets this action's display type.
+     *
+     * These are provided to implementations to indicate a preference for certain display
+     * styles.
+     *
+     * default: ``Kirigami.DisplayHint.NoPreference``
+     *
+     * @note This property contains only preferences, implementations may choose to disregard them.
+     * @see DisplayHint
+     * @since org.kde.kirigami 2.12
+     * @property Kirigami.DisplayHint displayHint
+     */
+    property int displayHint: Kirigami.DisplayHint.NoPreference
+
+    /**
+     * @brief This is a helper function to check if a certain display hint has been set.
+     *
+     * This function is mostly convenience to enforce the mutual exclusivity of KeepVisible and AlwaysHide.
+     *
+     * @param hint The display hint to check if it is set.
+     * @return @c true if the hint was set for this action, @c false if not.
+     * @deprecated Since 2.14, Use DisplayHint.displayHintSet(action, hint) instead.
+     * @since org.kde.kirigami 2.12
+     */
+    function displayHintSet(hint) {
+        print("Action::displayHintSet is deprecated, use Kirigami.DisplayHint.displayHintSet(action, hint)")
+        return Kirigami.DisplayHint.displayHintSet(root, hint);
+    }
+
+    /**
+     * @brief This property holds the component that should be used for displaying this action.
+     * @note This can be used to display custom components in the toolbar.
+     * @since KDE Frameworks 5.65
+     * @since org.kde.kirigami 2.12
+     */
+    property Component displayComponent: null
+
+    /**
+     * @brief This property holds a list of child actions.
+     *
+     * This is useful for tree-like menus, such as the GlobalDrawer.
+     *
+     * Example usage:
+     * @code{.qml}
+     * Action {
+     *    text: "Tools"
+     *    Action {
+     *        text: "Action1"
+     *    }
+     *    Action {
+     *        text: "Action2"
+     *    }
+     * }
+     * @endcode
+     * @property list<Action> children
+     */
+    default property list<QtObject> children
+//END properties
+
+    onChildrenChanged: {
+        let child;
+        for (const i in children) {
+            child = children[i];
+            if (child.hasOwnProperty("parent")) {
+                child.parent = root
+            }
+        }
+    }
+
+    /**
+     * @brief This property holds the action's visible child actions.
+     * @property list<Action> visibleChildren
+     */
+    readonly property var visibleChildren: {
+        const visible = [];
+        for (const i in children) {
+            const child = children[i];
+            if (!child.hasOwnProperty("visible") || child.visible) {
+                visible.push(child);
+            }
+        }
+        return visible;
+    }
+    /**
+     * @brief Hints for implementations using Actions indicating preferences about how to display the action.
+     * @deprecated Since 2.14, use Kirigami.DisplayHint instead.
+     */
+    enum DisplayHint {
+        NoPreference = 0,
+        IconOnly = 1,
+        KeepVisible = 2,
+        AlwaysHide = 4,
+        HideChildIndicator = 8
+    }
+}
diff --git a/src/controls/ActionTextField.qml b/src/controls/ActionTextField.qml
new file mode 100644 (file)
index 0000000..bc40e8f
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * This is advanced textfield. It is recommended to use this class when there
+ * is a need to create a create a textfield with action buttons (e.g a clear
+ * action).
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.20 as Kirigami
+ *
+ * Kirigami.ActionTextField {
+ *     id: searchField
+ *
+ *     placeholderText: i18n("Search...")
+ *
+ *     focusSequence: StandardKey.Find
+ *
+ *     rightActions: Kirigami.Action {
+ *         icon.name: "edit-clear"
+ *         visible: searchField.text !== ""
+ *         onTriggered: {
+ *             searchField.clear();
+ *             searchField.accepted();
+ *         }
+ *     }
+ *
+ *     onAccepted: console.log("Search text is " + searchField.text);
+ * }
+ * @endcode
+ * @since KDE Frameworks 5.56
+ * @inherit QtQuick.Controls.TextField
+ */
+QQC2.TextField {
+    id: root
+
+    /**
+     * @brief This property holds a shortcut sequence that will focus the text field.
+     * @since KDE Frameworks 5.56
+     */
+    property alias focusSequence: focusShortcut.sequence
+
+    /**
+     * @brief This property holds a list of actions that will be displayed on the left side of the text field.
+     *
+     * By default, this list is empty.
+     *
+     * @since KDE Frameworks 5.56
+     */
+    property list<QtObject> leftActions
+
+    /**
+     * @brief This property holds a list of actions that will be displayed on the right side of the text field.
+     *
+     * By default, this list is empty.
+     *
+     * @since KDE Frameworks 5.56
+     */
+    property list<QtObject> rightActions
+
+    /** @internal */
+    property alias _leftActionsRow: leftActionsRow
+
+    /** @internal */
+    property alias _rightActionsRow: rightActionsRow
+
+    hoverEnabled: true
+
+    // Manually setting this fixes alignment in RTL layouts
+    horizontalAlignment: TextInput.AlignLeft
+
+    leftPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? rightActionsRow : leftActionsRow).width
+    rightPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? leftActionsRow : rightActionsRow).width
+
+    Behavior on leftPadding {
+        NumberAnimation {
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutQuad
+        }
+    }
+
+    Behavior on rightPadding {
+        NumberAnimation {
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutQuad
+        }
+    }
+
+    Shortcut {
+        id: focusShortcut
+        enabled: root.visible && root.enabled
+        onActivated: {
+            root.forceActiveFocus(Qt.ShortcutFocusReason)
+            root.selectAll()
+        }
+    }
+
+    QQC2.ToolTip {
+        visible: focusShortcut.nativeText.length > 0 && root.text.length === 0 && !rightActionsRow.hovered && !leftActionsRow.hovered && hovered
+        text: focusShortcut.nativeText
+    }
+
+    component ActionIconMouseArea: MouseArea {
+        anchors.fill: parent
+        activeFocusOnTab: true
+        cursorShape: Qt.PointingHandCursor
+        hoverEnabled: true
+        Accessible.role: Accessible.Button
+        Keys.onPressed: event => {
+            switch (event.key) {
+            case Qt.Key_Space:
+            case Qt.Key_Enter:
+            case Qt.Key_Return:
+            case Qt.Key_Select:
+                clicked(null);
+                event.accepted = true;
+                break;
+            }
+        }
+    }
+
+    Row {
+        id: leftActionsRow
+        padding: Kirigami.Units.smallSpacing
+        spacing: Kirigami.Units.smallSpacing
+        layoutDirection: Qt.LeftToRight
+        LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
+        anchors.left: parent.left
+        anchors.leftMargin: Kirigami.Units.smallSpacing
+        anchors.top: parent.top
+        anchors.topMargin: parent.topPadding
+        anchors.bottom: parent.bottom
+        anchors.bottomMargin: parent.bottomPadding
+        Repeater {
+            model: root.leftActions
+            Kirigami.Icon {
+                implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
+                implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
+
+                anchors.verticalCenter: parent.verticalCenter
+
+                source: modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source
+                active: leftActionArea.containsPress || leftActionArea.activeFocus
+                visible: !(modelData instanceof Kirigami.Action) || modelData.visible
+                enabled: modelData.enabled
+
+                ActionIconMouseArea {
+                    id: leftActionArea
+                    Accessible.name: modelData.text
+                    onClicked: mouse => modelData.trigger()
+                }
+
+                QQC2.ToolTip {
+                    visible: (rightActionArea.containsMouse || rightActionArea.activeFocus) && (modelData.text.length > 0)
+                    text: modelData.text
+                }
+            }
+        }
+    }
+
+    Row {
+        id: rightActionsRow
+        padding: Kirigami.Units.smallSpacing
+        spacing: Kirigami.Units.smallSpacing
+        layoutDirection: Qt.RightToLeft
+        LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
+        anchors.right: parent.right
+        anchors.rightMargin: Kirigami.Units.smallSpacing
+        anchors.top: parent.top
+        anchors.topMargin: parent.topPadding
+        anchors.bottom: parent.bottom
+        anchors.bottomMargin: parent.bottomPadding
+        Repeater {
+            model: root.rightActions
+            Kirigami.Icon {
+                implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
+                implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
+
+                anchors.verticalCenter: parent.verticalCenter
+
+                source: modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source
+                active: rightActionArea.containsPress || rightActionArea.activeFocus
+                visible: !(modelData instanceof Kirigami.Action) || modelData.visible
+                enabled: modelData.enabled
+
+                ActionIconMouseArea {
+                    id: rightActionArea
+                    Accessible.name: modelData.text
+                    onClicked: mouse => modelData.trigger()
+                }
+
+                QQC2.ToolTip {
+                    visible: (rightActionArea.containsMouse || rightActionArea.activeFocus) && (modelData.text.length > 0)
+                    text: modelData.text
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/ActionToolBar.qml b/src/controls/ActionToolBar.qml
new file mode 100644 (file)
index 0000000..4623e3b
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQml 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.4 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+import "private" as P
+
+/**
+ * @brief A toolbar built out of a list of actions.
+ *
+ * The default representation for visible actions is a QtQuick.Controls.ToolButton,
+ * but it can be changed by setting Action::displayComponent to a different component.
+ * 
+ * The ActionToolBar component will try to display as many
+ * action delegates as possible but it will place those that do not fit into an
+ * overflow menu.
+ * 
+ * How actions are displayed can be changed by setting the `displayHint`
+ * in an action. For example, when setting it to ``DisplayHint.KeepVisible``,
+ * it will try to keep that action in view as long as possible, using an icon-only
+ * button if the full-sized delegate does not fit.
+ *
+ * @see <a href="https://develop.kde.org/hig/components/navigation/toolbar">KDE Human Interface Guidelines on Toolbars</a>
+ * @see ToolBarLayout
+ * @since org.kde.kirigami 2.5
+ * @inherit QtQuick.Controls.Control
+ */
+QQC2.Control {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds a list of visible actions.
+     *
+     * The ActionToolBar will try to display as many actions as possible.
+     * Those that do not fit go into a overflow menu.
+     *
+     * @property list<Action> actions
+     */
+    property alias actions: layout.actions
+
+    /**
+     * @brief This property holds a list of hidden actions.
+     *
+     * These actions will always be displayed in the overflow menu, even if there is enough space.
+     *
+     * @deprecated Since 2.14, set ``displayHint`` to ``DisplayHint.AlwaysHide`` in actions instead.
+     * @since org.kde.kirigami 2.6
+     * @property list<Action> hiddenActions
+     */
+    property list<QtObject> hiddenActions
+    onHiddenActionsChanged: print("ActionToolBar::hiddenActions is deprecated, use the AlwaysHide hint on your actions instead")
+
+    /**
+     * @brief This property holds whether the buttons will have a flat appearance.
+     *
+     * default: ``true``
+     */
+    property bool flat: true
+
+    /**
+     * @brief This property determines how the icon and text are displayed within the button.
+     *
+     * The following values are allowed:
+     * * ``Button.IconOnly``
+     * * ``Button.TextOnly``
+     * * ``Button.TextBesideIcon``
+     * * ``Button.TextUnderIcon``
+     *
+     * default: ``Controls.Button.TextBesideIcon``
+     *
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-abstractbutton.html#display-prop">AbstractButton.display</a>
+     */
+    property int display: QQC2.Button.TextBesideIcon
+
+    /**
+     * @brief This property holds the alignment of the buttons.
+     *
+     * When there is more space available than required by the visible delegates,
+     * we need to determine how to place the delegates.
+     *
+     * When there is more space available than required by the visible action delegates,
+     * we need to determine where to position them.
+     *
+     * default: ``Qt.AlignRight``
+     *
+     * @see <a href="https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum">Qt::Alignment</a>
+     * @property Qt::Alignment alignment
+     */
+    property alias alignment: layout.alignment
+
+    /**
+     * @brief This property holds the position of the toolbar.
+     *
+     * If this ActionToolBar is the contentItem of a QQC2 Toolbar, the position is bound to the ToolBar's position
+     *
+     * The following values are allowed:
+     * * ``Controls.ToolBar.Header``: The toolbar is at the top, as a window or page header.
+     * * ``Controls.ToolBar.Footer``: The toolbar is at the bottom, as a window or page footer.
+     *
+     * @property int position
+     */
+    property int position: parent && parent.hasOwnProperty("position")
+            ? parent.position
+            : QQC2.ToolBar.Header
+
+    /**
+     * @brief This property holds the maximum width of the content of this ToolBar.
+     *
+     * If the toolbar's width is larger than this value, empty space will
+     * be added on the sides, according to the Alignment property.
+     *
+     * The value of this property is derived from the ToolBar's actions and their properties.
+     *
+     * @property int maximumContentWidth
+     */
+    readonly property alias maximumContentWidth: layout.implicitWidth
+
+    /**
+     * @brief This property holds the Freedesktop standard icon name to use for the overflow menu button.
+     *
+     * default: ``"overflow-menu"``
+     *
+     * @since KDE Frameworks 5.65
+     * @since org.kde.kirigami 2.12
+     */
+    property string overflowIconName: "overflow-menu"
+
+    /**
+     * @brief This property holds the combined width of all visible delegates.
+     * @property int visibleWidth
+     */
+    property alias visibleWidth: layout.visibleWidth
+
+    /**
+     * @brief This property sets the handling method for items that do not match the toolbar's height.
+     *
+     * When toolbar items do not match the height of the toolbar, there are
+     * several ways we can deal with this. This property sets the preferred way.
+     *
+     * The following values are allowed:
+     * * ``HeightMode.AlwaysCenter``
+     * * ``HeightMode.AlwaysFill``
+     * * ``HeightMode.ConstrainIfLarger``
+     *
+     * default: ``HeightMode::ConstrainIfLarger``
+     *
+     * @see ToolBarLayout::heightMode
+     * @see ToolBarLayout::HeightMode
+     * @property ToolBarLayout::HeightMode heightMode
+     */
+    property alias heightMode: layout.heightMode
+//END properties
+
+    implicitHeight: layout.implicitHeight
+    implicitWidth: layout.implicitWidth
+
+    Layout.minimumWidth: layout.minimumWidth
+    Layout.preferredWidth: 0
+    Layout.fillWidth: true
+
+    leftPadding: 0
+    rightPadding: 0
+    topPadding: 0
+    bottomPadding: 0
+
+    contentItem: Kirigami.ToolBarLayout {
+        id: layout
+        spacing: Kirigami.Units.smallSpacing
+        layoutDirection: root.LayoutMirroring.enabled ? Qt.RightToLeft : Qt.LeftToRight
+
+        fullDelegate: P.PrivateActionToolButton {
+            flat: root.flat
+            display: root.display
+            action: Kirigami.ToolBarLayout.action
+        }
+
+        iconDelegate: P.PrivateActionToolButton {
+            flat: root.flat
+            display: QQC2.Button.IconOnly
+            action: Kirigami.ToolBarLayout.action
+
+            showMenuArrow: false
+
+            menuActions: {
+                if (action.displayComponent) {
+                    return [action]
+                }
+
+                if (action.hasOwnProperty("children") && action.children.length > 0) {
+                    return Array.prototype.map.call(action.children, i => i)
+                }
+
+                return []
+            }
+        }
+
+        moreButton: P.PrivateActionToolButton {
+            flat: root.flat
+
+            action: Kirigami.Action {
+                tooltip: qsTr("More Actions")
+                icon.name: root.overflowIconName
+                displayHint: Kirigami.DisplayHint.IconOnly | Kirigami.DisplayHint.HideChildIndicator
+            }
+
+            menuActions: {
+                if (root.hiddenActions.length === 0) {
+                    return root.actions
+                } else {
+                    const result = []
+                    result.concat(Array.prototype.map.call(root.actions, i => i))
+                    result.concat(Array.prototype.map.call(hiddenActions, i => i))
+                    return result
+                }
+            }
+
+            menuComponent: P.ActionsMenu {
+                submenuComponent: P.ActionsMenu {
+                    Binding {
+                        target: parentItem
+                        property: "visible"
+                        value: layout.hiddenActions.includes(parentAction)
+                               && (parentAction.visible === undefined || parentAction.visible)
+                        restoreMode: Binding.RestoreBinding
+                    }
+                }
+
+                itemDelegate: P.ActionMenuItem {
+                    visible: layout.hiddenActions.includes(action)
+                             && (action.visible === undefined || action.visible)
+                }
+
+                loaderDelegate: Loader {
+                    property var action
+                    height: visible ? implicitHeight : 0
+                    visible: layout.hiddenActions.includes(action)
+                             && (action.visible === undefined || action.visible)
+                }
+
+                separatorDelegate: QQC2.MenuSeparator {
+                    property var action
+                    visible: layout.hiddenActions.includes(action)
+                             && (action.visible === undefined || action.visible)
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/ApplicationHeader.qml b/src/controls/ApplicationHeader.qml
new file mode 100644 (file)
index 0000000..0344b9a
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "templates" as T
+
+//TODO KF6: remove
+/**
+ * @brief An item that can be used as a title for the application.
+ *
+ * Scrolling the main page will make it taller or shorter (through the point of going away)
+ * It's a behavior similar to the typical mobile web browser addressbar
+ * the minimum, preferred and maximum heights of the item can be controlled with
+ * * ``minimumHeight``: Default is 0, i.e. hidden
+ * * ``preferredHeight``: Default is Kirigami.Units.gridUnit * 1.6
+ * * ``maximumHeight``: Default is Kirigami.Units.gridUnit * 3
+ *
+ * To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
+ *
+ * @deprecated This will be removed in KF6.
+ * @inherit kirigami::templates::ApplicationHeader
+ */
+T.ApplicationHeader {
+    id: header
+}
diff --git a/src/controls/ApplicationItem.qml b/src/controls/ApplicationItem.qml
new file mode 100644 (file)
index 0000000..6f76680
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * @brief An item that provides the features of ApplicationWindow without the window itself.
+ *
+ * This allows embedding into a larger application.
+ * It's based around the PageRow component that allows adding/removing of pages.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.ApplicationItem {
+ *     globalDrawer: Kirigami.GlobalDrawer {
+ *         actions: [
+ *            Kirigami.Action {
+ *                text: "View"
+ *                icon.name: "view-list-icons"
+ *                Kirigami.Action {
+ *                        text: "action 1"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 2"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 3"
+ *                }
+ *            },
+ *            Kirigami.Action {
+ *                text: "Sync"
+ *                icon.name: "folder-sync"
+ *            }
+ *         ]
+ *     }
+ *
+ *     contextDrawer: Kirigami.ContextDrawer {
+ *         id: contextDrawer
+ *     }
+ *
+ *     pageStack.initialPage: Kirigami.Page {
+ *         actions {
+ *             main: Kirigami.Action {
+ *                 icon.name: "edit"
+ *                 onTriggered: {
+ *                     // do stuff
+ *                 }
+ *             }
+ *             contextualActions: [
+ *                 Kirigami.Action {
+ *                     icon.name: "edit"
+ *                     text: "Action text"
+ *                     onTriggered: {
+ *                         // do stuff
+ *                     }
+ *                 },
+ *                 Kirigami.Action {
+ *                     icon.name: "edit"
+ *                     text: "Action text"
+ *                     onTriggered: {
+ *                         // do stuff
+ *                     }
+ *                 }
+ *             ]
+ *           [...]
+ *         }
+ *     }
+ * }
+ * @endcode
+*/
+Kirigami.AbstractApplicationItem {
+    id: root
+
+    /**
+     * @brief This property holds the PageRow that is used to allocate
+     * the pages and manage the transitions between them.
+     *
+     * @see kirigami::PageRow
+     * @warning This property is not currently readonly, but it should be treated like it is readonly.
+     * @property Kirigami.PageRow pageStack
+     */
+    property alias pageStack: __pageStack // TODO KF6 make readonly
+
+    // Redefines here as here we can know a pointer to PageRow
+    wideScreen: width >= applicationWindow().pageStack.defaultColumnWidth * 2
+
+    Component.onCompleted: {
+        if (pageStack.currentItem) {
+            pageStack.currentItem.forceActiveFocus();
+        }
+    }
+
+    Kirigami.PageRow {
+        id: __pageStack
+        anchors {
+            fill: parent
+            // HACK: workaround a bug in android iOS keyboard management
+            bottomMargin: ((Qt.platform.os === "android" || Qt.platform.os === "ios") || !Qt.inputMethod.visible) ? 0 : Qt.inputMethod.keyboardRectangle.height
+            onBottomMarginChanged: {
+                if (bottomMargin > 0) {
+                    root.reachableMode = false;
+                }
+            }
+        }
+        // FIXME
+        onCurrentIndexChanged: root.reachableMode = false;
+
+        function goBack() {
+            // NOTE: drawers are handling the back button by themselves
+            const backEvent = {accepted: false}
+            if (root.pageStack.currentIndex >= 1) {
+                root.pageStack.currentItem.backRequested(backEvent);
+                if (!backEvent.accepted) {
+                    root.pageStack.flickBack();
+                    backEvent.accepted = true;
+                }
+            }
+
+            if (Kirigami.Settings.isMobile && !backEvent.accepted && Qt.platform.os !== "ios") {
+                Qt.quit();
+            }
+        }
+        function goForward() {
+            root.pageStack.currentIndex = Math.min(root.pageStack.depth - 1, root.pageStack.currentIndex + 1);
+        }
+        Keys.onBackPressed: event => {
+            goBack();
+            event.accepted = true;
+        }
+        Shortcut {
+            sequences: [StandardKey.Forward]
+            onActivated: __pageStack.goForward();
+        }
+        Shortcut {
+            sequences: [StandardKey.Back]
+            onActivated: __pageStack.goBack();
+        }
+
+        background: Rectangle {
+            color: root.color
+        }
+
+        focus: true
+    }
+}
diff --git a/src/controls/ApplicationWindow.qml b/src/controls/ApplicationWindow.qml
new file mode 100644 (file)
index 0000000..5f9882e
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * @brief A window that provides some basic features needed for all apps.
+ *
+ * An application window is a top-level component that provides
+ * several utilities for convenience, such as:
+ * * kirigami::AbstractApplicationWindow::applicationWindow()
+ * * kirigami::AbstractApplicationWindow::globalDrawer
+ * * kirigami::AbstractApplicationWindow::pageStack
+ * * kirigami::AbstractApplicationWindow::wideScreen
+ * 
+ * @see kirigami::AbstractApplicationWindow
+ * 
+ * Use this class only if you need custom content for your application that is
+ * different from the PageRow behavior recommended by the HIG and provided
+ * by kirigami::AbstractApplicationWindow.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.ApplicationWindow {
+ *   [...]
+ *     globalDrawer: Kirigami.GlobalDrawer {
+ *         actions: [
+ *            Kirigami.Action {
+ *                text: "View"
+ *                iconName: "view-list-icons"
+ *                Kirigami.Action {
+ *                        text: "action 1"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 2"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 3"
+ *                }
+ *            },
+ *            Kirigami.Action {
+ *                text: "Sync"
+ *                iconName: "folder-sync"
+ *            }
+ *         ]
+ *     }
+ *
+ *     contextDrawer: Kirigami.ContextDrawer {
+ *         id: contextDrawer
+ *     }
+ *
+ *     pageStack.initialPage: Kirigami.Page {
+ *         actions {
+ *             main: Kirigami.Action {
+ *                 iconName: "edit"
+ *                 onTriggered: {
+ *                     // do stuff
+ *                 }
+ *             }
+ *             contextualActions: [
+ *                 Kirigami.Action {
+ *                     iconName: "edit"
+ *                     text: "Action text"
+ *                     onTriggered: {
+ *                         // do stuff
+ *                     }
+ *                 },
+ *                 Kirigami.Action {
+ *                     iconName: "edit"
+ *                     text: "Action text"
+ *                     onTriggered: {
+ *                         // do stuff
+ *                     }
+ *                 }
+ *             ]
+ *         }
+ *     }
+ *   [...]
+ * }
+ * @endcode
+*/
+Kirigami.AbstractApplicationWindow {
+    id: root
+
+    /**
+     * @brief This property holds the PageRow that is used to allocate the pages
+     * and manage the transitions between them.
+     *
+     * It implements useful features to control then shown pages such as:
+     * * kirigami::PageRow::initialPage
+     * * kirigami::PageRow::push()
+     * * kirigami::PageRow::pop()
+     *
+     * @see kirigami::PageRow
+     * @warning This property is not currently readonly, but it should be treated like it is readonly.
+     * @property Kirgiami.PageRow pageStack
+     */
+    property alias pageStack: __pageStack  // TODO KF6 make readonly
+
+    // Redefined here as here we can know a pointer to PageRow.
+    // We negate the canBeEnabled check because we don't want to factor in the automatic drawer provided by Kirigami for page actions for our calculations
+    wideScreen: width >= (root.pageStack.defaultColumnWidth) + ((contextDrawer && !(contextDrawer instanceof Kirigami.ContextDrawer)) ? contextDrawer.width : 0) + (globalDrawer ? globalDrawer.width : 0)
+
+    Component.onCompleted: {
+        if (pageStack.currentItem) {
+            pageStack.currentItem.forceActiveFocus()
+        }
+    }
+
+    PageRow {
+        id: __pageStack
+        globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto
+        anchors {
+            fill: parent
+            // HACK: workaround a bug in android iOS keyboard management
+            bottomMargin: ((Qt.platform.os === "android" || Qt.platform.os === "ios") || !Qt.inputMethod.visible) ? 0 : Qt.inputMethod.keyboardRectangle.height
+            onBottomMarginChanged: {
+                if (__pageStack.anchors.bottomMargin > 0) {
+                    root.reachableMode = false;
+                }
+            }
+        }
+        // FIXME
+        onCurrentIndexChanged: root.reachableMode = false;
+
+        focus: true
+    }
+}
diff --git a/src/controls/Avatar.qml b/src/controls/Avatar.qml
new file mode 100644 (file)
index 0000000..bb53184
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.13
+import QtQuick.Window 2.15
+import QtQuick.Controls 2.13 as QQC2
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.14 as Kirigami
+import org.kde.kirigami.private 2.14 as KP
+import "templates/private" as TP
+
+//TODO KF6: generic enough or belongs to a different framework?
+/**
+ * @brief An element that represents a user, either with initials, an icon, or a profile image.
+ * @inherit QtQuick.Controls.Control
+ */
+QQC2.Control {
+    id: avatarRoot
+
+    enum ImageMode {
+        AlwaysShowImage,
+        AdaptiveImageOrInitals,
+        AlwaysShowInitials
+    }
+    enum InitialsMode {
+        UseInitials,
+        UseIcon
+    }
+
+//BEGIN properties
+    /**
+     * @brief This property holds the given name of a user.
+     *
+     * The user's name will be used for generating initials and to provide the
+     * accessible name for assistive technology.
+     */
+    property string name
+
+    /**
+     * @brief This property holds the source of the user's profile picture; an image.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-image.html#source-prop">Image.source</a>
+     * @property url source
+     */
+    property alias source: avatarImage.source
+
+    /**
+     * @brief This property holds the avatar's icon source.
+     *
+     * The icon acts as a fallback in case the profile image set via `source`
+     * takes too long to load or is undefined, or in case no `name` or `source`
+     * has been set.
+     *
+     * To force display the icon when both ``name`` and ``source`` are set, you
+     * may set ``imageMode`` to ``Kirigami.Avatar.ImageMode.AlwaysShowInitials``
+     * to make it fallback to using initials, and then change ``initialsMode``
+     * to ``Kirigami.Avatar.InitialsMode.UseIcon`` to display the icon.
+     *
+     * @see Icon::source
+     * @property var iconSource
+     */
+    property alias iconSource: avatarIcon.source
+
+    /**
+     * @brief This property holds how the button should represent the user when no user-set image is available.
+     *
+     * The following values are allowed:
+     * * ``Avatar.InitialsMode.UseInitials``: Show the user's initials.
+     * * ``Avatar.InitialsMode.UseIcon``: Show a generic icon.
+     *
+     * @see ::InitialsMode
+     */
+    property int initialsMode: Kirigami.Avatar.InitialsMode.UseInitials
+
+    /**
+     * @brief This property holds how the avatar should be shown.
+     *
+     * This property holds whether the button should always show the image; show the image if one is
+     * available and show initials when it is not; or always show initials.
+     *
+     * The following values are allowed:
+     * * ``Avatar.ImageMode.AlwaysShowImage``: Always try to show the image; even if it hasn't loaded yet or is undefined.
+     * * ``Avatar.ImageMode.AdaptiveImageOrInitals``: Show the image if it is valid; or show initials if it is not
+     * * ``Avatar.ImageMode.AlwaysShowInitials``: Always show initials
+     *
+     * @see ::ImageMode
+     */
+    property int imageMode: Kirigami.Avatar.ImageMode.AdaptiveImageOrInitals
+
+    /**
+     * @brief This property sets whether the provided image should be cached.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-image.html#cache-prop">Image.cache</a>
+     * @property bool cache
+     */
+    property alias cache: avatarImage.cache
+
+    /**
+     * @brief This property holds the source size of the user's profile picture.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-image.html#sourceSize-prop">Image.sourceSize</a>
+     * @property int sourceSize
+     */
+    property alias sourceSize: avatarImage.sourceSize
+
+    /**
+     * @brief This property holds whether the provided image should be smoothed.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-image.html#smooth-prop">Image.smooth</a>
+     * @property bool smooth
+     */
+    property alias smooth: avatarImage.smooth
+
+    /**
+     * @brief This property holds the color to use for this avatar.
+     *
+     * If not explicitly set, this defaults to generating a color from the name.
+     *
+     * @property color color
+     */
+    property var color: Kirigami.NameUtils.colorsFromString(name)
+    // We use a var instead of a color here to allow setting the colour
+    // as undefined, which will result in a generated colour being used.
+
+    /**
+     * @brief This property holds the main and secondary actions associated with this avatar.
+     * @code{.qml}
+     * Kirigami.Avatar {
+     *     actions.main: Kirigami.Action {}
+     *     actions.secondary: Kirigami.Action {}
+     * }
+     * @endcode
+     *
+     * Actions associated with this avatar.
+     *
+     * @note The secondary action should only be used for shortcuts of actions
+     * elsewhere in your application's UI, and cannot be accessed on mobile platforms.
+     */
+    property KP.AvatarGroup actions: KP.AvatarGroup {}
+
+    /**
+     * @brief This property holds the border properties group.
+     *
+     * Example usage:
+     * @code{.qml}
+     * Kirigami.Avatar {
+     *     border.width: 10
+     *     border.color: "red"
+     * }
+     * @endcode
+     */
+    property TP.BorderPropertiesGroup border: TP.BorderPropertiesGroup {
+        width: 0
+        color: Qt.rgba(0,0,0,0.2)
+    }
+//END properties
+
+    padding: 0
+    horizontalPadding: padding
+    verticalPadding: padding
+    leftPadding: horizontalPadding
+    rightPadding: horizontalPadding
+    topPadding: verticalPadding
+    bottomPadding: verticalPadding
+
+    implicitWidth: Kirigami.Units.iconSizes.large
+    implicitHeight: Kirigami.Units.iconSizes.large
+
+    activeFocusOnTab: !!actions.main
+
+    Accessible.role: !!actions.main ? Accessible.Button : Accessible.Graphic
+    Accessible.name: !!actions.main ? qsTr("%1 — %2").arg(name).arg(actions.main.text) : name
+    Accessible.focusable: !!actions.main
+    Accessible.onPressAction: __triggerMainAction()
+    Keys.onReturnPressed: event => __triggerMainAction()
+    Keys.onEnterPressed: event => __triggerMainAction()
+    Keys.onSpacePressed: event => __triggerMainAction()
+
+    function __triggerMainAction() {
+        if (actions.main) {
+            actions.main.trigger();
+        }
+    }
+
+    background: Rectangle {
+        radius: parent.width / 2
+
+        color: __private.showImage ? Kirigami.Theme.backgroundColor : avatarRoot.color
+
+        Rectangle {
+            anchors.fill: parent
+            anchors.margins: -border.width
+
+            radius: width / 2
+
+            color: "transparent"
+            border.width: Kirigami.Units.smallSpacing
+            border.color: Kirigami.Theme.focusColor
+            visible: avatarRoot.focus
+        }
+
+        MouseArea {
+            id: primaryMouse
+
+            anchors.fill: parent
+            hoverEnabled: true
+            property bool mouseInCircle: {
+                const x = avatarRoot.width / 2, y = avatarRoot.height / 2
+                const xPrime = mouseX, yPrime = mouseY
+
+                const distance = (x - xPrime) ** 2 + (y - yPrime) ** 2
+                const radiusSquared = (Math.min(avatarRoot.width, avatarRoot.height) / 2) ** 2
+
+                return distance < radiusSquared
+            }
+
+            onClicked: mouse =>{
+                if (mouseY > avatarRoot.height - secondaryRect.height && !!avatarRoot.actions.secondary) {
+                    avatarRoot.actions.secondary.trigger()
+                    return
+                }
+                avatarRoot.__triggerMainAction()
+            }
+
+            enabled: !!avatarRoot.actions.main || !!avatarRoot.actions.secondary
+            cursorShape: containsMouse && mouseInCircle && enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+
+            QQC2.ToolTip {
+                text: avatarRoot.actions.main && avatarRoot.actions.main.tooltip ? avatarRoot.actions.main.tooltip : ''
+                visible: primaryMouse.containsMouse && text
+            }
+
+            states: [
+                State {
+                    name: "secondaryRevealed"
+                    when: (!Kirigami.Settings.isMobile) && (!!avatarRoot.actions.secondary) && (primaryMouse.containsMouse && primaryMouse.mouseInCircle)
+                    PropertyChanges {
+                        target: secondaryRect
+                        visible: true
+                    }
+                }
+            ]
+        }
+    }
+
+    QtObject {
+        id: __private
+        property color textColor: Kirigami.ColorUtils.brightnessForColor(avatarRoot.color) === Kirigami.ColorUtils.Light
+                                ? "black"
+                                : "white"
+        property bool showImage: {
+            return (avatarRoot.imageMode === Kirigami.Avatar.ImageMode.AlwaysShowImage) ||
+                   (avatarImage.status === Image.Ready && avatarRoot.imageMode === Kirigami.Avatar.ImageMode.AdaptiveImageOrInitals)
+        }
+    }
+
+    contentItem: Item {
+        Text {
+            id: avatarText
+            fontSizeMode: Text.Fit
+            visible: avatarRoot.initialsMode === Kirigami.Avatar.InitialsMode.UseInitials &&
+                    !__private.showImage &&
+                    !Kirigami.NameUtils.isStringUnsuitableForInitials(avatarRoot.name) &&
+                    avatarRoot.width > Kirigami.Units.gridUnit
+
+            text: Kirigami.NameUtils.initialsFromString(name)
+            color: __private.textColor
+
+            anchors.fill: parent
+            font {
+                // this ensures we don't get a both point and pixel size are set warning
+                pointSize: -1
+                pixelSize: (avatarRoot.height - Kirigami.Units.largeSpacing) / 2
+            }
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+        }
+        Kirigami.Icon {
+            id: avatarIcon
+            visible: (avatarRoot.initialsMode === Kirigami.Avatar.InitialsMode.UseIcon && !__private.showImage) ||
+                    (Kirigami.NameUtils.isStringUnsuitableForInitials(avatarRoot.name) && !__private.showImage)
+
+            source: "user"
+
+            anchors.fill: parent
+            anchors.margins: Kirigami.Units.largeSpacing
+
+            color: __private.textColor
+        }
+        Image {
+            id: avatarImage
+            visible: __private.showImage
+
+            mipmap: true
+            smooth: true
+            sourceSize {
+                width: avatarRoot.width * Screen.devicePixelRatio
+                height: avatarRoot.height * Screen.devicePixelRatio
+            }
+
+            fillMode: Image.PreserveAspectCrop
+            anchors.fill: parent
+        }
+
+        Rectangle {
+            color: "transparent"
+
+            radius: width / 2
+            anchors.fill: parent
+
+            border {
+                width: avatarRoot.border.width
+                color: avatarRoot.border.color
+            }
+        }
+
+        Rectangle {
+            id: secondaryRect
+            visible: false
+
+            anchors {
+                bottom: parent.bottom
+                left: parent.left
+                right: parent.right
+            }
+
+            height: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing*2
+
+            color: Qt.rgba(0, 0, 0, 0.6)
+
+            Kirigami.Icon {
+                Kirigami.Theme.textColor: "white"
+                source: (avatarRoot.actions.secondary || {iconName: ""}).iconName
+
+                width: Kirigami.Units.iconSizes.small
+                height: Kirigami.Units.iconSizes.small
+
+                x: Math.round((parent.width/2)-(this.width/2))
+                y: Math.round((parent.height/2)-(this.height/2))
+            }
+        }
+
+        layer.enabled: true
+        layer.effect: GE.OpacityMask {
+            maskSource: Rectangle {
+                height: avatarRoot.height
+                width: avatarRoot.width
+                radius: height / 2
+                color: "black"
+                visible: false
+            }
+        }
+    }
+}
diff --git a/src/controls/BasicListItem.qml b/src/controls/BasicListItem.qml
new file mode 100644 (file)
index 0000000..25bf0ee
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
+ *  SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+
+//TODO KF6: this needs to become a layout inside a Delegate rather than its own listItem
+/**
+ * @brief A BasicListItem provides a simple list item design that can handle the
+ * most common list item usecases.
+ *
+ * @image html BasicListItemTypes.svg "The styles of the BasicListItem. From left to right top to bottom: light icon + title + subtitle, dark icon + title + subtitle, light icon + label, dark icon + label, light label, dark label." width=50%
+ * @warning This will be reworked into a layout to be used inside a delegate.
+ * @see <a href="https://develop.kde.org/hig/components/editing/list">KDE Human Interface Guidelines on List Views and List Items</a>
+ */
+Kirigami.AbstractListItem {
+    id: listItem
+
+//BEGIN properties
+    /**
+     * @brief This property holds the text of this list item's label.
+     *
+     * If a subtitle is provided, the label will behave as a title and will be styled
+     * accordingly. Every list item should have a label.
+     *
+     * @property string label
+     */
+    property alias label: listItem.text
+
+    /**
+     * @brief This property holds an optional subtitle that can appear under the label.
+     * @since KDE Frameworks 5.70
+     * @since org.kde.kirigami 2.12
+     */
+    property alias subtitle: subtitleItem.text
+
+    /**
+     * @brief This property holds an item that will be displayed before the title and subtitle.
+     * @note The leading item is allowed to expand infinitely horizontally, and should be bounded by the user.
+     * @since org.kde.kirigami 2.15
+     */
+    property Item leading
+
+    /**
+     * @brief This property holds the padding after the leading item.
+     * @since org.kde.kirigami 2.15
+     */
+    property real leadingPadding: Kirigami.Units.largeSpacing
+
+    // TODO KF6: remove this property and instead implement leading and trailing
+    // item positioning in such a way that they fill vertically, but a fixed
+    // height can be manually specified without needing to wrap it in an Item
+    /**
+     * @brief This property sets whether or not to stretch the leading item to fit all available vertical space.
+     *
+     * If false, you will be responsible for setting a height for the
+     * item or ensuring that its default height works.
+     *
+     * default: ``true``
+     *
+     * @warning This property will likely be removed in KF6
+     * @since KDE Frameworks 5.83
+     * @since org.kde.kirigami 2.15
+     */
+    property bool leadingFillVertically: true
+
+    /**
+     * @brief This property holds an item that will be displayed after the title and subtitle
+     * @note The trailing item is allowed to expand infinitely horizontally, and should be bounded by the user.
+     * @since org.kde.kirigami 2.15
+     */
+    property Item trailing
+
+    /**
+     * @brief This property holds the padding before the trailing item.
+     * @since org.kde.kirigami 2.15
+     */
+    property real trailingPadding: Kirigami.Units.largeSpacing
+
+    // TODO KF6: remove this property and instead implement leading and trailing
+    // item positioning in such a way that they fill vertically, but a fixed
+    // height can be manually specified without needing to wrap it in an Item
+    /**
+     * @brief This propery sets whether or not to stretch the trailing item to fit all available vertical space.
+     *
+     * If false, you will be responsible for setting a height for the
+     * item or ensuring that its default height works.
+     *
+     * default: ``true``
+     *
+     * @warning This property will likely be removed in KF6
+     * @since KDE Frameworks 5.83
+     * @since org.kde.kirigami 2.15
+     */
+    property bool trailingFillVertically: true
+
+    /**
+     * @brief This property sets whether list item's text should render in bold.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.71
+     * @since org.kde.kirigami 2.13
+     */
+    property bool bold: false
+
+    /**
+     * @code ts
+     * interface IconGroup {
+     *     name:   string,
+     *     source: string,
+     *     width:  int,
+     *     height: int,
+     *     color:  color,
+     * }
+     *
+     * type Icon = string | IconGroup | URL
+     * @endcode
+     *
+     * The icon that will render on this list item.
+     *
+     * This can either be an icon name, a URL, or an object with the following properties:
+     *
+     * If the type of the icon is a string containing an icon name, the icon will be looked up from the
+     * platform icon theme.
+     *
+     * Using an icon object allows you to specify more granular attributes of the icon,
+     * such as its color and dimensions.
+     *
+     * If the icon is a URL, the icon will be attempted to be loaded from the
+     * given URL.
+     */
+    property var icon
+
+    /**
+     * @brief This property sets the size at which the icon will render.
+     *
+     * This will not affect icon lookup, unlike the icon group's width and height properties, which will.
+     *
+     * @since org.kde.kirigami 2.5
+     * @property int iconSize
+     */
+    property alias iconSize: iconItem.size
+
+    /**
+     * @brief This property holds the color of the icon.
+     *
+     * If the icon's original colors should be left intact, set this to the default value, "transparent".
+     * Note that this colour will only be applied if the icon can be recoloured, (e.g. you can use Kirigami.Theme.foregroundColor to change the icon's colour.)
+     *
+     * @since org.kde.kirigami 2.7
+     * @property color iconColor
+     */
+    property alias iconColor: iconItem.color
+
+    /**
+     * @brief This property sets whether or not the icon has a "selected" appearance.
+     *
+     * Can be used to override the icon coloration if the list item's background and
+     * text are also being overridden, to ensure that the icon never becomes invisible.
+     *
+     * @since KDE Frameworks 5.91
+     * @since org.kde.kirigami 2.19
+     * @property bool iconSelected
+     */
+    property alias iconSelected: iconItem.selected
+
+    /**
+     * @brief This property sets whether or not to reserve space for the icon, even if there is no icon.
+     * @image html BasicListItemReserve.svg "Left: reserveSpaceForIcon: false. Right: reserveSpaceForIcon: true" width=50%
+     * @property bool reserveSpaceForIcon
+     */
+    property alias reserveSpaceForIcon: iconItem.visible
+
+    /**
+     * @brief This property sets whether or not the label of the list item should fill width.
+     *
+     * Setting this to @c false is useful if you have other items in the list item
+     * that should fill width instead of the label.
+     *
+     * @property bool reserveSpaceForLabel
+     */
+    property alias reserveSpaceForLabel: labelItem.visible
+
+    /**
+     * @brief This property holds whether the list item's height should account for
+     * the presence of a subtitle.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.77
+     * @since org.kde.kirigami 2.15
+     */
+    property bool reserveSpaceForSubtitle: false
+
+    /**
+     * @brief This property holds the spacing between the label row and subtitle row.
+     * @since KDE Frameworks 5.83
+     * @since org.kde.kirigami 2.15
+     * @property real textSpacing
+     */
+    property alias textSpacing: labelColumn.spacing
+
+    /**
+     * @brief This property holds sets whether to make the icon and labels have a disabled look.
+     *
+     * This can be used to tweak whether the content elements are visually active
+     * while preserving an active appearance for any leading or trailing items.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.83
+     * @since org.kde.kirigami 2.15
+     */
+    property bool fadeContent: false
+
+    /**
+     * @brief This property holds the label item, for accessing the usual QtQuick.Text properties.
+     * @since KDE Frameworks 5.84
+     * @since org.kde.kirigami 2.16
+     * @property QtQuick.Controls.Label labelItem
+     */
+    property alias labelItem: labelItem
+
+    /**
+     * @brief This property holds the subtitle item, for accessing the usual QtQuick.Text properties.
+     * @since KDE Frameworks 5.84
+     * @since org.kde.kirigami 2.16
+     * @property QtQuick.Controls.Label subtitleItem
+     */
+    property alias subtitleItem: subtitleItem
+
+    /**
+     * @brief This property holds whether the tooltip of the subtitle should be visible
+     * @since 5.107
+     */
+    property bool toolTipVisible: false
+
+    /** @internal */
+    default property alias _basicDefault: layout.data
+//END properties
+
+//BEGIN signal handlers
+    onLeadingChanged: {
+        const item = leading;
+        if (!!item) {
+            item.parent = contItem
+            item.anchors.left = item.parent.left
+            item.anchors.top = leadingFillVertically ? item.parent.top : undefined
+            item.anchors.bottom = leadingFillVertically ? item.parent.bottom : undefined
+            item.anchors.verticalCenter = leadingFillVertically ? undefined : item.parent.verticalCenter
+            layout.anchors.left = item.right
+            layout.anchors.leftMargin = Qt.binding(() => leadingPadding)
+        } else {
+            layout.anchors.left = contentItem.left
+            layout.anchors.leftMargin = 0
+        }
+    }
+
+    onTrailingChanged: {
+        const item = trailing;
+        if (!!item) {
+            item.parent = contItem
+            item.anchors.right = item.parent.right
+            item.anchors.top = trailingFillVertically ? item.parent.top : undefined
+            item.anchors.bottom = trailingFillVertically ? item.parent.bottom : undefined
+            item.anchors.verticalCenter = trailingFillVertically ? undefined : item.parent.verticalCenter
+            layout.anchors.right = item.left
+            layout.anchors.rightMargin = Qt.binding(() => trailingPadding)
+        } else {
+            layout.anchors.right = contentItem.right
+            layout.anchors.rightMargin = 0
+        }
+    }
+
+    Keys.onEnterPressed: event => action ? action.trigger() : clicked()
+    Keys.onReturnPressed: event => action ? action.trigger() : clicked()
+//END signal handlers
+
+    icon: action ? action.icon.name || action.icon.source : undefined
+
+    contentItem: Item {
+        id: contItem
+
+        implicitWidth: layout.implicitWidth
+            + (listItem.leading !== null ? listItem.leading.implicitWidth : 0)
+            + (listItem.trailing !== null ? listItem.trailing.implicitWidth : 0)
+
+        Binding on implicitHeight {
+            value: Math.max(iconItem.size, (!subtitleItem.visible && listItem.reserveSpaceForSubtitle ? (labelItem.implicitHeight + labelColumn.spacing + subtitleItem.implicitHeight): labelColumn.implicitHeight) )
+            delayed: true
+        }
+
+        RowLayout {
+            id: layout
+            spacing: LayoutMirroring.enabled ? listItem.rightPadding : listItem.leftPadding
+            anchors.left: contItem.left
+            anchors.leftMargin: listItem.leading ? listItem.leadingPadding : 0
+            anchors.right: contItem.right
+            anchors.rightMargin: listItem.trailing ? listItem.trailingPadding : 0
+            anchors.verticalCenter: parent.verticalCenter
+
+            Kirigami.Icon {
+                id: iconItem
+                source: {
+                    if (!listItem.icon) {
+                        return undefined
+                    }
+                    if (listItem.icon.hasOwnProperty) {
+                        if (listItem.icon.hasOwnProperty("name") && listItem.icon.name !== "")
+                            return listItem.icon.name;
+                        if (listItem.icon.hasOwnProperty("source"))
+                            return listItem.icon.source;
+                    }
+                    return listItem.icon;
+                }
+                property int size: subtitleItem.visible || reserveSpaceForSubtitle ? Kirigami.Units.iconSizes.medium : Kirigami.Units.iconSizes.smallMedium
+                Layout.minimumHeight: size
+                Layout.maximumHeight: size
+                Layout.minimumWidth: size
+                Layout.maximumWidth: size
+                selected: (listItem.highlighted || listItem.checked || listItem.down)
+                opacity: listItem.fadeContent ? 0.6 : 1.0
+                visible: source !== undefined
+            }
+            ColumnLayout {
+                id: labelColumn
+                spacing: 0
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignVCenter
+                QQC2.Label {
+                    id: labelItem
+                    text: listItem.text
+                    Layout.fillWidth: true
+                    Layout.alignment: subtitleItem.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter
+                    color: (listItem.highlighted || listItem.checked || listItem.down) ? listItem.activeTextColor : listItem.textColor
+                    elide: Text.ElideRight
+                    font.weight: listItem.bold ? Font.Bold : Font.Normal
+                    opacity: listItem.fadeContent ? 0.6 : 1.0
+                }
+                QQC2.Label {
+                    id: subtitleItem
+                    Layout.fillWidth: true
+                    Layout.alignment: subtitleItem.visible ? Qt.AlignLeft | Qt.AlignTop : Qt.AlignLeft | Qt.AlignVCenter
+                    color: (listItem.highlighted || listItem.checked || listItem.down) ? listItem.activeTextColor : listItem.textColor
+                    elide: Text.ElideRight
+                    font: Kirigami.Theme.smallFont
+                    opacity: listItem.fadeContent ? 0.6 : (listItem.bold ? 0.9 : 0.7)
+                    visible: text.length > 0
+                }
+                QQC2.ToolTip.text: {
+                    let txt = "";
+                    if (labelItem.truncated) {
+                        txt += labelItem.text;
+                    }
+                    if (subtitleItem.truncated) {
+                        if (txt.length > 0) {
+                            txt += "<br/><br/>";
+                        }
+                        txt += subtitleItem.text;
+                    }
+                    return txt;
+                }
+                QQC2.ToolTip.visible: toolTipVisible && QQC2.ToolTip.text.length > 0 && (Kirigami.Settings.tabletMode ? listItem.down : listItem.hovered)
+                QQC2.ToolTip.delay: Kirigami.Settings.tabletMode ? Qt.styleHints.mousePressAndHoldInterval : Kirigami.Units.toolTipDelay
+            }
+        }
+    }
+}
diff --git a/src/controls/Card.qml b/src/controls/Card.qml
new file mode 100644 (file)
index 0000000..8f6fd42
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.0 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import "private" as P
+
+/**
+ * @brief This component implements a standard layout of a card.
+ *
+ * It is recommended to use this class when the concept of Cards is needed
+ * in the application.
+ *
+ * This Card has default items as AbstractCard::header and AbstractCard::footer. The header is an
+ * image that can contain an optional title and icon, accessible via the
+ * ::banner grouped property.
+ *
+ * The footer will show a series of toolbuttons (and eventual overflow menu)
+ * representing the actions list accessible with the list property actions.
+ * It is possible even tough is discouraged to override the footer:
+ * in this case the actions property shouldn't be used.
+ *
+ * @see <a href="https://develop.kde.org/hig/components/editing/card">KDE Human Interface Guidelines on Cards</a>
+ * @since org.kde.kirigami 2.4
+ * @inherit kirigami::AbstractCard
+ */
+Kirigami.AbstractCard {
+    id: root
+
+    /**
+     * @brief This property holds visible actions that will be available in the footer
+     * of the card.
+     *
+     * The actions will be represented by a list of ToolButtons with an optional overflow
+     * menu, when not all of them will fit in the available Card width.
+     *
+     * @property list<kirigami::Action> Card::actions
+     */
+    property list<QtObject> actions
+
+    /**
+     * @brief This property holds hidden actions that will be available in the footer.
+     *
+     * These actions will only be shown in the overflow menu, even when there is enough space.
+     *
+     * @deprecated Use actions with a ``Kirigami.DisplayHint.AlwaysHide`` as displayHint.
+     * @see kirigami::DisplayHint
+     * @since org.kde.kirigami 2.6
+     * @property list<kirigami::Action> hiddenActions
+     */
+    property alias hiddenActions: actionsToolBar.hiddenActions
+
+    /**
+     * @brief This grouped property controls the banner image present in the header.
+     *
+     * This grouped property has the following sub-properties:
+     * * ``source: url``: The source for the image. It understands any URL valid for an Image component.
+     * * ``titleIcon: string``: The optional icon to put in the banner, either a freedesktop-compatible
+     * icon name (recommended) or any URL supported by QtQuick.Image.
+     * * ``title: string``: The title for the banner, shown as contrasting text over the image.
+     * * ``titleAlignment: Qt::Alignment``: The alignment of the title inside the image.
+     * default: ``Qt.AlignTop | Qt.AlignLeft``
+     * * ``titleLevel: int``: The Kirigami.Heading level for the title, which controls the font size.
+     * default: ``1``, which is the largest size.
+     * * ``titleWrapMode: QtQuick.Text.wrapMode``: Whether the header text should be able to wrap.
+     * default: ``Text.NoWrap``
+     *
+     * It also has the full set of properties that QtQuick.Image has, such as sourceSize and fillMode.
+     *
+     * @see kirigami::private::BannerImage
+     * @property Image banner
+     */
+    readonly property alias banner: bannerImage
+
+
+    header: P.BannerImage {
+        id: bannerImage
+        anchors.leftMargin: -root.leftPadding + root.background.border.width
+        anchors.topMargin: -root.topPadding + root.background.border.width
+        anchors.rightMargin: root.headerOrientation === Qt.Vertical ? -root.rightPadding + root.background.border.width : 0
+        anchors.bottomMargin: root.headerOrientation === Qt.Horizontal ? -root.bottomPadding + root.background.border.width : 0
+        //height: Layout.preferredHeight
+        implicitWidth: root.headerOrientation === Qt.Horizontal ? sourceSize.width : Layout.preferredWidth
+        Layout.preferredHeight: (source.toString() !== "" ? width / (sourceSize.width / sourceSize.height) : Layout.minimumHeight) + anchors.topMargin + anchors.bottomMargin
+
+        readonly property real widthWithBorder: width + root.background.border.width * 2
+        readonly property real heightWithBorder: height + root.background.border.width * 2
+        readonly property real radiusFromBackground: root.background.radius - root.background.border.width
+
+        corners.topLeftRadius: radiusFromBackground
+        corners.topRightRadius: (root.headerOrientation === Qt.Horizontal && widthWithBorder < root.width) ? 0 : radiusFromBackground
+        corners.bottomLeftRadius: (root.headerOrientation !== Qt.Horizontal && heightWithBorder < root.height) ? 0 : radiusFromBackground
+        corners.bottomRightRadius: heightWithBorder < root.height ? 0 : radiusFromBackground
+    }
+
+    onHeaderChanged: {
+        if (!header) {
+            return;
+        }
+
+        header.anchors.leftMargin = Qt.binding(() => -root.leftPadding);
+        header.anchors.topMargin = Qt.binding(() =>  -root.topPadding);
+        header.anchors.rightMargin = Qt.binding(() => root.headerOrientation === Qt.Vertical ? -root.rightPadding : 0);
+        header.anchors.bottomMargin = Qt.binding(() => root.headerOrientation === Qt.Horizontal ? -root.bottomPadding : 0);
+    }
+
+    footer: Kirigami.ActionToolBar {
+        id: actionsToolBar
+        actions: root.actions
+        position: QQC2.ToolBar.Footer
+        visible: root.footer === actionsToolBar
+    }
+}
diff --git a/src/controls/CardsGridView.qml b/src/controls/CardsGridView.qml
new file mode 100644 (file)
index 0000000..d88bb91
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.10
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+import "private" as P
+
+
+//TODO KF6: remove the whole class?
+/**
+ * @brief CardsGridView is used to display a grid of Cards generated from any model.
+ *
+ * The behavior is same as CardsLayout, and it allows cards to be put in one or two
+ * columns depending on the available width.
+ *
+ * GridView has the limitation that every Card must have the same exact height,
+ * so cellHeight must be manually set to a value in which the content fits
+ * for every item.
+ *
+ * If possible use cards only when you don't need to instantiate a lot
+ * and use CardsLayout instead.
+ *
+ * @see kirigami::CardsLayout
+ * @see kirigami::CardsListView
+ * @see <a href="https://develop.kde.org/hig/components/editing/grid">KDE Human Interface Guidelines on Grids</a>
+ * @since org.kde.kirigami 2.4
+ * @inherit QtQuick.GridView
+ */
+P.CardsGridViewPrivate {
+    id: root
+
+    /**
+     * @brief This property sets whether the view should fill the first row with columns
+     * even when there is not enough space.
+     *
+     * Set this to @c true if you want to stop the view from filling the first row with columns,
+     * even when delegates can't even fill the first row.
+     *
+     * default: ``true``
+     */
+    property bool extraColumns: true
+
+    /**
+     * @brief This property holds the number of columns the gridview has.
+     * @since org.kde.kirigami 2.5
+     */
+    readonly property int columns: {
+        const minFromWidth = Math.floor(width / minimumColumnWidth)
+        const maxFromWidth = Math.ceil(width / maximumColumnWidth)
+        const maxColumns = extraColumns ? maximumColumns : count
+        return Math.max(1,Math.min(minFromWidth,maxFromWidth,maxColumns))
+    }
+
+    /**
+     * @brief This property holds the maximum number of columns the gridview may have.
+     *
+     * default: ``Kirigami.Units.maximumInteger()``
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property int maximumColumns: Kirigami.Units.maximumInteger
+
+    /**
+     * @brief This property holds the maximum width that the columns may have.
+     *
+     * The cards will never become wider than this size; when the GridView is wider
+     * than maximumColumnWidth, it will switch from one to two columns.
+     *
+     * If the default needs to be overridden for some reason,
+     * it is advised to express this unit as a multiple
+     * of Kirigami.Units.gridUnit.
+     *
+     * default: ``20 * Kirigami.Units.gridUnit``
+     */
+    property int maximumColumnWidth: Kirigami.Units.gridUnit * 20
+
+    /**
+     * @brief This property holds the minimum width that the columns may have.
+     *
+     * The cards will never become thinner than this.
+     *
+     * If the default needs to be overridden for some reason,
+     * it is advised to express this unit as a multiple
+     * of Kirigami.Units.gridUnit.
+     *
+     * default: ``12 * Kirigami.Units.gridUnit``
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property int minimumColumnWidth: Kirigami.Units.gridUnit * 12
+
+    cellWidth: Math.floor(width/columns)
+    cellHeight: Math.max(Kirigami.Units.gridUnit * 15, Math.min(cellWidth, maximumColumnWidth) / 1.2)
+
+    /**
+     * @brief This property holds the delegate of the CardsGridView.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-gridview.html#delegate-prop">GridView.delegate</a>
+     */
+    default property alias delegate: root._delegateComponent
+
+    topMargin: Kirigami.Units.largeSpacing * 2
+
+    Keys.onPressed: event => {
+        if (event.key === Qt.Key_Home) {
+            positionViewAtBeginning();
+            currentIndex = 0;
+            event.accepted = true;
+        }
+        else if (event.key === Qt.Key_End) {
+            positionViewAtEnd();
+            currentIndex = count - 1;
+            event.accepted = true;
+        }
+    }
+}
diff --git a/src/controls/CardsLayout.qml b/src/controls/CardsLayout.qml
new file mode 100644 (file)
index 0000000..ce85a14
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * @brief A QtQuick.Layouts.GridLayout optimized for showing a couple of columns
+ * of cards, depending on the available space.
+ *
+ * This should be used when the cards to be displayed, are not instantiated by
+ * a model or are instantiated by a model that always has very few items
+ * (in the case of a big model, use CardsListView or CardsGridView instead).
+ *
+ * The cards are presented in a grid of at least one column, which will remain
+ * centered. Note that the layout will automatically add and remove columns
+ * depending on the size available.
+ *
+ * @note A CardsLayout should always be contained within a
+ * QtQuick.Layouts.ColumnLayout.
+ *
+ * @since org.kde.kirigami 2.4
+ * @inherit QtQuick.Layouts.GridLayout
+ */
+GridLayout {
+    /**
+     * @brief This property holds the maximum number of columns.
+     *
+     * default: ``2``
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property int maximumColumns: 2
+
+    /**
+     * @brief This property holds the maximum width the columns may have.
+     *
+     * If the default needs to be overridden for some reason,
+     * it is advised to express this unit as a multiple
+     * of Kirigami.Units.gridUnit.
+     *
+     * default: ``20 * Kirigami.Units.gridUnit``
+     */
+    property int maximumColumnWidth: Kirigami.Units.gridUnit * 20
+
+    /**
+     * @brief This property holds the minimum width the columns may have.
+     *
+     * default: ``12 * Kirigami.Units.gridUnit``
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property int minimumColumnWidth: Kirigami.Units.gridUnit * 12
+
+    columns: Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
+                                  Math.floor(width/minimumColumnWidth),
+                                  Math.ceil(width/maximumColumnWidth)));
+
+    rowSpacing: Kirigami.Units.largeSpacing * columns
+    columnSpacing: Kirigami.Units.largeSpacing * columns
+
+
+    // NOTE: this default width which defaults to 2 columns is just to remove a binding loop on columns
+    width: maximumColumnWidth*2 + Kirigami.Units.largeSpacing
+    // same computation of columns, but on the parent size
+    Layout.preferredWidth: maximumColumnWidth * Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
+                                  Math.floor(parent.width/minimumColumnWidth),
+                                  Math.ceil(parent.width/maximumColumnWidth))) + Kirigami.Units.largeSpacing * (columns - 1)
+
+    Layout.maximumWidth: Layout.preferredWidth
+    Layout.alignment: Qt.AlignHCenter
+
+    Component.onCompleted: childrenChanged()
+    onChildrenChanged: {
+        for (let i = 0; i < children.length; ++i) {
+            children[i].Layout.fillHeight = true;
+        }
+    }
+}
diff --git a/src/controls/CardsListView.qml b/src/controls/CardsListView.qml
new file mode 100644 (file)
index 0000000..0706309
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+/**
+ * CardsListView is a ListView intended to be used with
+ * AbstractCard component as its delegate.
+ *
+ * It will automatically assign the proper spacings and margins around the
+ * cards adhering to the design guidelines.
+ *
+ * CardsListView should be used only with cards which can look good at any
+ * horizontal size, so it is recommended to directly use AbstractCard with an
+ * appropriate layout inside, because they are stretching for the whole list width.
+ *
+ * Therefore, it is discouraged to use it with the Card type, unless it has
+ * AbstractCard::headerOrientation set to ``Qt.Horizontal``.
+ *
+ * The choice between using this view with AbstractCard or a normal QtQuick.ListView
+ * with AbstractListItem / BasicListItem is purely a choice based on aesthetics alone.
+ *
+ * It is recommended to use default values.
+ *
+ * @since org.kde.kirigami 2.4
+ * @inherit QtQuick.ListView
+ */
+ListView {
+    id: root
+    spacing: Kirigami.Units.largeSpacing * 2
+    topMargin: headerPositioning !== ListView.InlineHeader ? spacing : 0
+    rightMargin: Kirigami.Units.largeSpacing * 2
+    leftMargin: Kirigami.Units.largeSpacing * 2
+    reuseItems: true
+
+    headerPositioning: ListView.OverlayHeader
+
+    Keys.onPressed: event => {
+        if (event.key === Qt.Key_Home) {
+            positionViewAtBeginning();
+            currentIndex = 0;
+            event.accepted = true;
+        }
+        else if (event.key === Qt.Key_End) {
+            positionViewAtEnd();
+            currentIndex = count - 1;
+            event.accepted = true;
+        }
+    }
+}
diff --git a/src/controls/CheckableListItem.qml b/src/controls/CheckableListItem.qml
new file mode 100644 (file)
index 0000000..6dd2c3d
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Nate Graham <nate@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick.Controls 2.0 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+
+/**
+ * A simple subclass of BasicListItem that adds a checkbox on the left side of
+ * the layout. The list item's own
+ * <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-abstractbutton.html#checked-prop">checked</a>
+ * property controls the check state of the checkbox.
+ *
+ * When the list item or its checkbox is clicked, the QtQuick.Controls.Action
+ * specified in the list item's ``actions:`` property will be triggered.
+ *
+ * @note Due to the way BasicListItem works, the QtQuick.Controls.Action MUST contain the
+ * line "checked = !checked" as the first line within its
+ * @link QtQuick.Controls.Action.triggered QtQuick.Controls.Action.onTriggered @endlink handler.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.14 as Kirigami
+ *
+ * ListView {
+ *     id: listView
+ *     model: [...]
+ *     delegate: Kirigami.CheckableListItem {
+ *         label: model.display
+ *
+ *         checked: model.checked
+ *
+ *         action: Action {
+ *             onTriggered: {
+ *                 checked = !checked
+ *                 [ do something amazing ]
+ *             }
+ *         }
+ *     }
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/hig/components/editing/list">KDE Human Interface Guidelines on List Views and List Items</a>
+ * @see <a href="https://develop.kde.org/hig/components/editing/checkbox">KDE Human Interface Guidelines on Checkboxes</a>
+ * @since org.kde.kirigami 2.14
+ * @inherit kirigami::BasicListItem
+ */
+Kirigami.BasicListItem {
+    id: checkableListItem
+
+    checkable: true
+    activeBackgroundColor: "transparent"
+    activeTextColor: Kirigami.Theme.textColor
+    iconSelected: false
+
+    leading: QQC2.CheckBox {
+        checked: checkableListItem.checked
+        onToggled: {
+            checkableListItem.checked = !checkableListItem.checked
+
+            // TODO(Qt6): rephrase as `checkableListItem.action?.trigger();`
+            if (checkableListItem.action) {
+                checkableListItem.action.trigger();
+            }
+        }
+    }
+}
diff --git a/src/controls/Chip.qml b/src/controls/Chip.qml
new file mode 100644 (file)
index 0000000..3502a08
--- /dev/null
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * @brief A compact element that represents an attribute, action, or filter.
+ *
+ * Should be used in a group of multiple elements. e.g when displaying tags in a image viewer.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.19 as Kirigami
+ *
+ * Flow {
+ *     Repeater {
+ *         model: chipsModel
+ *
+ *         Kirigami.Chip {
+ *             text: model.text
+ *             icon.name: "tag-symbolic"
+ *             closable: model.closable
+ *             onClicked: {
+ *                 [...]
+ *             }
+ *             onRemoved: {
+ *                 [...]
+ *             }
+ *         }
+ *     }
+ * }
+ * @endcode
+ *
+ * @since org.kde.kirigami 2.19
+ * @inherit kirigami::AbstractChip
+ */
+Kirigami.AbstractChip {
+    id: chip
+
+    implicitWidth: layout.implicitWidth
+    implicitHeight: toolButton.implicitHeight
+
+    checkable: !closable
+
+    /**
+     * @brief This property holds the label item; used for accessing the usual QtQuick.Text properties.
+     * @property QtQuick.Controls.Label labelItem
+     */
+    property alias labelItem: label
+
+    contentItem: RowLayout {
+        id: layout
+        spacing: 0
+
+        Kirigami.Icon {
+            id: icon
+            visible: icon.valid
+            Layout.preferredWidth: Kirigami.Units.iconSizes.small
+            Layout.preferredHeight: Kirigami.Units.iconSizes.small
+            Layout.leftMargin: Kirigami.Units.smallSpacing
+            color: chip.icon.color
+            source: chip.icon.name || chip.icon.source
+        }
+        QQC2.Label {
+            id: label
+            Layout.fillWidth: true
+            Layout.minimumWidth: Kirigami.Units.gridUnit * 1.5
+            Layout.leftMargin: icon.visible ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
+            Layout.rightMargin: chip.closable ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+            text: chip.text
+            color: Kirigami.Theme.textColor
+            elide: Text.ElideRight
+        }
+        QQC2.ToolButton {
+            id: toolButton
+            visible: chip.closable
+            text: qsTr("Remove Tag")
+            icon.name: "edit-delete-remove"
+            icon.width: Kirigami.Units.iconSizes.sizeForLabels
+            icon.height: Kirigami.Units.iconSizes.sizeForLabels
+            display: QQC2.AbstractButton.IconOnly
+            onClicked: chip.removed()
+        }
+    }
+}
diff --git a/src/controls/ContextDrawer.qml b/src/controls/ContextDrawer.qml
new file mode 100644 (file)
index 0000000..4cde24c
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.2 as QQC2
+import org.kde.kirigami 2.4 as Kirigami
+import "private" as P
+
+/**
+ * A specialized type of drawer that will show a list of actions
+ * relevant to the application's current page.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.ApplicationWindow {
+ *  [...]
+ *     contextDrawer: Kirigami.ContextDrawer {
+ *         id: contextDrawer
+ *     }
+ *  [...]
+ * }
+ * @endcode
+ *
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.Page {
+ *   [...]
+ *     actions.contextualActions: [
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         },
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         }
+ *     ]
+ *   [...]
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/hig/components/navigation/contextdrawer">Human Interface Guidelines on Context Drawers</a>
+ * @see <a href="https://develop.kde.org/hig/patterns-command/drawer/#context-drawer">KDE Human Interface Guidelines' Short Introduction of Context Drawers</a>
+ * @inherit OverlayDrawer
+ */
+Kirigami.OverlayDrawer {
+    id: root
+    handleClosedIcon.source: null
+    handleOpenIcon.source: null
+
+    /**
+     * @brief A title for the action list that will be shown to the user when opens the drawer
+     *
+     * default: ``qsTr("Actions")``
+     */
+    property string title: qsTr("Actions")
+
+    /**
+     * This can be any type of object that a ListView can accept as model.
+     * It expects items compatible with either QtQuick.Controls.Action or
+     * Kirigami.Action.
+     *
+     * @see QtQuick.Controls.Action
+     * @see kirigami::Action
+     * @property list<Action> actions
+     */
+    property var actions: page ? page.contextualActions : []
+
+    /**
+     * @brief Arbitrary content to show above the list view.
+     *
+     * default: `an Item containing a Kirigami.Heading that displays a title whose text is
+     * controlled by the title property.`
+     *
+     * @property Component header
+     * @since org.kde.kirigami 2.7
+     */
+    property alias header: menu.header
+
+    /**
+     * @brief Arbitrary content to show below the list view.
+     * @property Component footer
+     * @since org.kde.kirigami 2.7
+     */
+    property alias footer: menu.footer
+
+    property Page page: {
+        if (applicationWindow().pageStack.layers && applicationWindow().pageStack.layers.depth > 1 && applicationWindow().pageStack.layers.currentItem.hasOwnProperty("contextualActions")) {
+            return applicationWindow().pageStack.layers.currentItem;
+        }
+        else if ((applicationWindow().pageStack.currentItem || {}).hasOwnProperty("contextualActions")) {
+            return applicationWindow().pageStack.currentItem;
+        }
+        else {
+            return applicationWindow().pageStack.lastVisibleItem;
+        }
+    }
+
+    // Disable for empty menus or when we have a global toolbar
+    enabled: menu.count > 0 &&
+            (typeof applicationWindow() === "undefined" || !applicationWindow().pageStack.globalToolBar ||
+            (applicationWindow().pageStack.lastVisibleItem && applicationWindow().pageStack.lastVisibleItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar) ||
+            (applicationWindow().pageStack.layers && applicationWindow().pageStack.layers.depth > 1 && applicationWindow().pageStack.layers.currentItem && applicationWindow().pageStack.layers.currentItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar))
+    edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
+    drawerOpen: false
+
+    // list items go to edges, have their own padding
+    topPadding: 0
+    leftPadding: 0
+    rightPadding: 0
+    bottomPadding: 0
+
+    handleVisible: applicationWindow === undefined ? false : applicationWindow().controlsVisible
+
+    onPeekingChanged: {
+        if (page) {
+            page.contextualActionsAboutToShow();
+        }
+    }
+    contentItem: QQC2.ScrollView {
+        // this just to create the attached property
+        Kirigami.Theme.inherit: true
+        implicitWidth: Kirigami.Units.gridUnit * 20
+        ListView {
+            id: menu
+            interactive: contentHeight > height
+            model: {
+                if (typeof root.actions === "undefined") {
+                    return null;
+                }
+                if (root.actions.length === 0) {
+                    return null;
+                } else {
+
+                    // Check if at least one action is visible
+                    let somethingVisible = false;
+                    for (let i = 0; i < root.actions.length; i++) {
+                        if (root.actions[i].visible) {
+                            somethingVisible = true;
+                            break;
+                        }
+                    }
+
+                    if (!somethingVisible) {
+                        return null;
+                    }
+
+                    return root.actions[0].text !== undefined &&
+                        root.actions[0].trigger !== undefined ?
+                            root.actions :
+                            root.actions[0];
+                }
+            }
+            topMargin: root.handle.y > 0 ? menu.height - menu.contentHeight : 0
+            header: Item {
+                height: heading.height
+                width: menu.width
+                Kirigami.Heading {
+                    id: heading
+                    anchors {
+                        left: parent.left
+                        right: parent.right
+                        margins: Kirigami.Units.largeSpacing
+                    }
+                    elide: Text.ElideRight
+                    level: 2
+                    text: root.title
+                }
+            }
+            delegate: Column {
+                width: parent.width
+                P.ContextDrawerActionItem {
+                    width: parent.width
+                }
+                Repeater {
+                    model: modelData.hasOwnProperty("expandible") && modelData.expandible ? modelData.children : null
+                    delegate: P.ContextDrawerActionItem {
+                        width: parent.width
+                        leftPadding: Kirigami.Units.largeSpacing * 2
+                        opacity: !root.collapsed
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/Dialog.qml b/src/controls/Dialog.qml
new file mode 100644 (file)
index 0000000..c05989b
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+    SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
+    SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+    SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Templates 2.15 as T
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import QtGraphicalEffects 1.12 as GE
+
+/**
+ * @brief Popup dialog that is used for short tasks and user interaction.
+ *
+ * Dialog consists of three components:
+ * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#header-prop">header</a>,
+ * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#contentItem-prop">contentItem</a>,
+ * and the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#footer-prop">footer</a>.
+ *
+ * By default, the header is a heading with text specified by the
+ * <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#title-prop">title</a> property.
+ *
+ * By default, the footer consists of a row of buttons specified by
+ * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#standardButtons-prop">standardButtons</a>
+ * and ::customFooterActions properties.
+ *
+ * The `implicitHeight` and `implicitWidth` of the dialog contentItem is
+ * the primary hint used for the dialog size. The dialog will be the
+ * minimum size required for the header, footer and content unless
+ * it is larger than `maximumHeight` and `maximumWidth`. Use
+ * `preferredHeight` and `preferredWidth` in order to manually specify
+ * a size for the dialog.
+ *
+ * If the content height exceeds the maximum height of the dialog, the
+ * dialog's contents will become scrollable.
+ *
+ * If the contentItem is a QtQuick.ListView,
+ * the dialog will take care of the necessary scrollbars and scrolling behaviour.
+ * Do @b not attempt
+ * to nest ListViews (it must be the top level item), as the scrolling
+ * behaviour will not be handled. Use ListView's `header` and `footer` instead.
+ *
+ * Example for a selection dialog:
+ * @code{.qml}
+ * import QtQuick 2.15
+ * import QtQuick.Layouts 1.15
+ * import QtQuick.Controls 2.15 as Controls
+ * import org.kde.kirigami 2.19 as Kirigami
+ *
+ * Kirigami.Dialog {
+ *     title: i18n("Dialog")
+ *     padding: 0
+ *     preferredWidth: Kirigami.Units.gridUnit * 16
+ *
+ *     standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
+ *
+ *     onAccepted: console.log("OK button pressed")
+ *     onRejected: console.log("Rejected")
+ *
+ *     ColumnLayout {
+ *         spacing: 0
+ *         Repeater {
+ *             model: 5
+ *             delegate: Controls.CheckDelegate {
+ *                 topPadding: Kirigami.Units.smallSpacing * 2
+ *                 bottomPadding: Kirigami.Units.smallSpacing * 2
+ *                 Layout.fillWidth: true
+ *                 text: modelData
+ *             }
+ *         }
+ *     }
+ * }
+ * @endcode
+ *
+ * Example with scrolling (ListView scrolling behaviour is handled by the Dialog):
+ * @code{.qml}
+ * Kirigami.Dialog {
+ *     id: scrollableDialog
+ *     title: i18n("Select Number")
+ *
+ *     ListView {
+ *         id: listView
+ *         // hints for the dialog dimensions
+ *         implicitWidth: Kirigami.Units.gridUnit * 16
+ *         implicitHeight: Kirigami.Units.gridUnit * 16
+ *
+ *         model: 100
+ *         delegate: Controls.RadioDelegate {
+ *             topPadding: Kirigami.Units.smallSpacing * 2
+ *             bottomPadding: Kirigami.Units.smallSpacing * 2
+ *             implicitWidth: listView.width
+ *             text: modelData
+ *         }
+ *     }
+ * }
+ * @endcode
+ *
+ * There are also sub-components of the Dialog that target specific usecases,
+ * and can reduce boilerplate code if used:
+ *
+ * @see kirigami::PromptDialog
+ * @see kirigami::MenuDialog
+ * @see <a href="https://develop.kde.org/hig/components/navigation/dialog">Human Interface Guidelines on Dialogs</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/message">Human Interface Guidelines on Modal Message Dialogs</a>
+ * @inherit QtQuick.QtObject
+ */
+T.Dialog {
+    id: root
+
+    // TODO KF6: remove this property
+    /**
+     * @deprecated This property will be removed in the next major frameworks release (KF6)
+     */
+    property Item mainItem: contentControl.contentChildren.length > 0 ? contentControl.contentChildren[0] : null
+
+    /**
+     * @brief This property holds the dialog's contents; includes Items and QtObjects.
+     * @property list<QtObject> dialogData
+     */
+    default property alias dialogData: contentControl.contentData
+
+    /**
+     * @brief This property holds the content items of the dialog.
+     *
+     * The initial height and width of the dialog is calculated from the
+     * `implicitWidth` and `implicitHeight` of the content.
+     *
+     * @property list<Item> dialogChildren
+     */
+    property alias dialogChildren: contentControl.contentChildren
+
+    /**
+     * @brief This property sets the absolute maximum height the dialog can have.
+     *
+     * The height restriction is solely applied on the content, so if the
+     * maximum height given is not larger than the height of the header and
+     * footer, it will be ignored.
+     *
+     * This is the window height, subtracted by Kirigami.Units.largeSpacing on both the top
+     * and bottom.
+     * 
+     * @see maximumHeight
+     */
+    readonly property real absoluteMaximumHeight: parent.height - Kirigami.Units.largeSpacing * 2
+
+    /**
+     * @brief This property holds the absolute maximum width the dialog can have.
+     *
+     * By default, it is the window width, subtracted by Kirigami.Units.largeSpacing on both
+     * the top and bottom.
+     * 
+     * @see maximumWidth
+     */
+    readonly property real absoluteMaximumWidth: parent.width - Kirigami.Units.largeSpacing * 2
+
+    /**
+     * @brief This property holds the maximum height the dialog can have
+     * (including the header and footer).
+     *
+     * The height restriction is solely enforced on the content, so if the
+     * maximum height given is not larger than the height of the header and
+     * footer, it will be ignored.
+     *
+     * By default, this is ::absoluteMaximumHeight.
+     */
+    property real maximumHeight: absoluteMaximumHeight
+
+    /**
+     * @brief This property holds the maximum width the dialog can have.
+     *
+     * By default, this is ::absoluteMaximumWidth.
+     */
+    property real maximumWidth: absoluteMaximumWidth
+
+    /**
+     * @brief This property holds the preferred height of the dialog.
+     *
+     * The content will receive a hint for how tall it should be to have
+     * the dialog to be this height.
+     *
+     * If the content, header or footer require more space, then the height
+     * of the dialog will expand to the necessary amount of space.
+     */
+    property real preferredHeight: -1
+
+    /**
+     * @brief This property holds the preferred width of the dialog.
+     *
+     * The content will receive a hint for how wide it should be to have
+     * the dialog be this wide.
+     *
+     * If the content, header or footer require more space, then the width
+     * of the dialog will expand to the necessary amount of space.
+     */
+    property real preferredWidth: -1
+
+
+    /**
+     * @brief This property holds the component to the left of the footer buttons.
+     */
+    property Component footerLeadingComponent
+
+    /**
+     * @brief his property holds the component to the right of the footer buttons.
+     */
+    property Component footerTrailingComponent
+
+    /**
+     * @brief This property sets whether to show the close button in the header.
+     */
+    property bool showCloseButton: true
+
+    /**
+     * @brief This property sets whether the footer button style should be flat.
+     */
+    property bool flatFooterButtons: false
+
+    /**
+     * @brief This property holds the custom actions displayed in the footer.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import QtQuick 2.15
+     * import QtQuick.Controls 2.15 as Controls
+     * import org.kde.kirigami 2.18 as Kirigami
+     *
+     * Kirigami.PromptDialog {
+     *     id: dialog
+     *     title: i18n("Confirm Playback")
+     *     subtitle: i18n("Are you sure you want to play this song? It's really loud!")
+     *
+     *     standardButtons: Kirigami.Dialog.Cancel
+     *     customFooterActions: [
+     *         Kirigami.Action {
+     *             text: i18n("Play")
+     *             iconName: "media-playback-start"
+     *             onTriggered: {
+     *                 //...
+     *                 dialog.close();
+     *             }
+     *         }
+     *     ]
+     * }
+     * @endcode
+     * @see kirigami::Action
+     */
+    property list<Kirigami.Action> customFooterActions
+
+    // default standard button
+    standardButtons: QQC2.Dialog.Close
+
+    function standardButton(button): T.AbstractButton {
+        // in case a footer is redefined
+        if (footer instanceof T.DialogButtonBox) {
+            return footer.standardButton(button);
+        } else if (footer === footerToolBar) {
+            return dialogButtonBox.standardButton(button);
+        } else {
+            return null;
+        }
+    }
+
+    // calculate dimensions
+    implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding // maximum width enforced from our content (one source of truth) to avoid binding loops
+    implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
+                    + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+                    + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0);
+
+    // misc. dialog settings
+    closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnReleaseOutside
+    modal: true
+    clip: false
+    padding: 0
+
+    // determine parent so that popup knows which window to popup in
+    // we want to open the dialog in the center of the window, if possible
+    Component.onCompleted: {
+        if (typeof applicationWindow !== "undefined") {
+            parent = applicationWindow().overlay;
+        }
+    }
+
+    // center dialog
+    x: Math.round((parent.width - width) / 2)
+    y: Math.round((parent.height - height) / 2) + Kirigami.Units.gridUnit * 2 * (1 - opacity) // move animation
+
+    // dialog enter and exit transitions
+    enter: Transition {
+        NumberAnimation { property: "opacity"; from: 0; to: 1; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
+    }
+    exit: Transition {
+        NumberAnimation { property: "opacity"; from: 1; to: 0; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
+    }
+
+    // black background, fades in and out
+    QQC2.Overlay.modal: Rectangle {
+        color: Qt.rgba(0, 0, 0, 0.3)
+
+        // the opacity of the item is changed internally by QQuickPopup on open/close
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+
+    // dialog view background
+    background: Item {
+        GE.RectangularGlow {
+            anchors.fill: rect
+            anchors.topMargin: 1
+            cornerRadius: rect.radius * 2
+            glowRadius: 2
+            spread: 0.2
+            color: Qt.rgba(0, 0, 0, 0.3)
+        }
+
+        Rectangle {
+            id: rect
+            anchors.fill: parent
+            Kirigami.Theme.colorSet: Kirigami.Theme.View
+            Kirigami.Theme.inherit: false
+            color: Kirigami.Theme.backgroundColor
+            radius: Kirigami.Units.smallSpacing
+        }
+    }
+
+    // dialog content
+    contentItem: ColumnLayout {
+        QQC2.ScrollView {
+            id: contentControl
+
+            // ensure view colour scheme, and background color
+            Kirigami.Theme.inherit: false
+            Kirigami.Theme.colorSet: Kirigami.Theme.View
+
+            QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
+
+            // height of everything else in the dialog other than the content
+            property real otherHeights: root.header.height + root.footer.height + root.topPadding + root.bottomPadding;
+
+            property real calculatedMaximumWidth: Math.min(root.absoluteMaximumWidth, root.maximumWidth) - root.leftPadding - root.rightPadding
+            property real calculatedMaximumHeight: Math.min(root.absoluteMaximumHeight, root.maximumHeight) - root.topPadding - root.bottomPadding
+            property real calculatedImplicitWidth: (contentChildren.length === 1 && contentChildren[0].implicitWidth > 0
+                ? contentChildren[0].implicitWidth
+                : (contentItem.implicitWidth > 0 ? contentItem.implicitWidth : contentItem.width)) + leftPadding + rightPadding
+            property real calculatedImplicitHeight: (contentChildren.length === 1 && contentChildren[0].implicitHeight > 0
+                ? contentChildren[0].implicitHeight
+                : (contentItem.implicitHeight > 0 ? contentItem.implicitHeight : contentItem.height)) + topPadding + bottomPadding
+
+            // how do we deal with the scrollbar width?
+            // - case 1: the dialog itself has the preferredWidth set
+            //   -> we hint a width to the content so it shrinks to give space to the scrollbar
+            // - case 2: preferredWidth not set, so we are using the content's implicit width
+            //   -> we expand the dialog's width to accommodate the scrollbar width (to respect the content's desired width)
+
+            // don't enforce preferred width and height if not set
+            Layout.preferredWidth: (root.preferredWidth >= 0 ? root.preferredWidth : calculatedImplicitWidth)
+            Layout.preferredHeight: root.preferredHeight >= 0 ? root.preferredHeight - otherHeights : calculatedImplicitHeight
+
+            Layout.fillWidth: true
+            Layout.maximumWidth: calculatedMaximumWidth
+            Layout.maximumHeight: calculatedMaximumHeight - otherHeights // we enforce maximum height solely from the content
+
+            // give an implied width and height to the contentItem so that features like word wrapping/eliding work
+            // cannot placed directly in contentControl as a child, so we must use a property
+            property var widthHint: Binding {
+                target: contentControl.contentChildren[0] || null
+                property: "width"
+
+                // we want to avoid horizontal scrolling, so we apply maximumWidth as a hint if necessary
+                property real preferredWidthHint: contentControl.contentItem.width
+                property real maximumWidthHint: contentControl.calculatedMaximumWidth - contentControl.leftPadding - contentControl.rightPadding
+
+                value: Math.min(maximumWidthHint, preferredWidthHint)
+
+                restoreMode: Binding.RestoreBinding
+            }
+        }
+    }
+
+    header: T.Control {
+        implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+                                implicitContentWidth + leftPadding + rightPadding)
+        implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+                                implicitContentHeight + topPadding + bottomPadding)
+
+        padding: Kirigami.Units.largeSpacing
+        bottomPadding: verticalPadding + headerSeparator.implicitHeight // add space for bottom separator
+
+        contentItem: RowLayout {
+            Kirigami.Heading {
+                id: heading
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignVCenter
+                level: 2
+                text: root.title === "" ? " " : root.title // always have text to ensure header height
+                elide: Text.ElideRight
+
+                // use tooltip for long text that is elided
+                QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
+                QQC2.ToolTip.text: root.title
+                HoverHandler { id: titleHoverHandler }
+            }
+            Kirigami.Icon {
+                id: closeIcon
+                visible: root.showCloseButton
+
+                // We want to position the close button in the top-right
+                // corner if the header is very tall, but we want to
+                // vertically center it in a short header
+                readonly property bool tallHeader: parent.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
+                Layout.alignment: tallHeader ? Qt.AlignRight | Qt.AlignTop : Qt.AlignRight | Qt.AlignVCenter
+                Layout.topMargin: tallHeader ? Kirigami.Units.largeSpacing : 0
+                implicitHeight: Kirigami.Units.iconSizes.smallMedium
+                implicitWidth: implicitHeight
+
+                source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
+                active: closeMouseArea.containsMouse
+                MouseArea {
+                    id: closeMouseArea
+                    hoverEnabled: Qt.styleHints.useHoverEffects
+                    anchors.fill: parent
+                    onClicked: mouse => root.reject()
+                }
+            }
+        }
+
+        // header background
+        background: Kirigami.ShadowedRectangle {
+            corners.topLeftRadius: Kirigami.Units.smallSpacing
+            corners.topRightRadius: Kirigami.Units.smallSpacing
+            Kirigami.Theme.colorSet: Kirigami.Theme.Header
+            Kirigami.Theme.inherit: false
+            color: Kirigami.Theme.backgroundColor
+            Kirigami.Separator {
+                id: headerSeparator
+                width: parent.width
+                anchors.bottom: parent.bottom
+            }
+        }
+    }
+
+    // use top level control rather than toolbar, since toolbar causes button rendering glitches
+    footer: T.Control {
+        id: footerToolBar
+
+        // if there is nothing in the footer, still maintain a height so that we can create a rounded bottom buffer for the dialog
+        property bool bufferMode: contentItem.implicitHeight === 0
+        implicitHeight: bufferMode ? Kirigami.Units.smallSpacing : contentItem.implicitHeight
+
+        leftPadding: 0; rightPadding: 0; bottomPadding: 0
+        topPadding: bufferMode ? 0 : footerSeparator.implicitHeight // add space for the separator above the footer
+
+        contentItem: RowLayout {
+            spacing: parent.spacing
+            // Don't let user interact with footer during transitions
+            enabled: root.opened
+
+            Loader {
+                id: leadingLoader
+                sourceComponent: root.footerLeadingComponent
+            }
+
+            // footer buttons
+            QQC2.DialogButtonBox {
+                // we don't explicitly set padding, to let the style choose the padding
+                id: dialogButtonBox
+                standardButtons: root.standardButtons
+                visible: count > 0
+
+                Layout.fillWidth: true
+                Layout.alignment: dialogButtonBox.alignment
+
+                position: QQC2.DialogButtonBox.Footer
+
+                // ensure themes don't add a background, since it can lead to visual inconsistencies
+                // with the rest of the dialog
+                background: null
+
+                // we need to hook all of the buttonbox events to the dialog events
+                onAccepted: root.accept()
+                onRejected: root.reject()
+                onApplied: root.applied()
+                onDiscarded: root.discarded()
+                onHelpRequested: root.helpRequested()
+                onReset: root.reset()
+
+                // add custom footer buttons
+                Repeater {
+                    model: root.customFooterActions
+                    // we have to use Button instead of ToolButton, because ToolButton has no visual distinction when disabled
+                    delegate: QQC2.Button {
+                        flat: flatFooterButtons
+                        action: modelData
+                        visible: modelData.visible
+                    }
+                }
+            }
+
+            Loader {
+                id: trailingLoader
+                sourceComponent: root.footerTrailingComponent
+            }
+        }
+
+        background: Kirigami.ShadowedRectangle {
+            // curved footer bottom corners
+            corners.bottomLeftRadius: Kirigami.Units.smallSpacing
+            corners.bottomRightRadius: Kirigami.Units.smallSpacing
+
+            // we act as a content buffer if nothing is in the footer
+            Kirigami.Theme.colorSet: footerToolBar.bufferMode ? Kirigami.Theme.View : Kirigami.Theme.Window
+            Kirigami.Theme.inherit: false
+            color: Kirigami.Theme.backgroundColor
+
+            // separator above footer
+            Kirigami.Separator {
+                id: footerSeparator
+                visible: !footerToolBar.bufferMode
+                width: parent.width
+                anchors.top: parent.top
+            }
+        }
+    }
+}
diff --git a/src/controls/FlexColumn.qml b/src/controls/FlexColumn.qml
new file mode 100644 (file)
index 0000000..20eef3e
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import org.kde.kirigami 2.13 as Kirigami
+
+//TODO KF6: how much is this used? can be removed?
+/**
+ * @brief FlexColumn is a column that grows in width to a fixed cap.
+ * @warning This might be removed in KF6 due to not being used.
+ * @inherit QtQuick.Layouts.ColumnLayout
+ */
+ColumnLayout {
+    id: __outer
+
+    default property alias columnChildren: __inner.children
+
+    /**
+     * @brief This property holds the column's offset from the cross axis.
+     *
+     * Note that padding is applied on both sides
+     * when the column is aligned to a centered cross axis.
+     *
+     * default: ``Kirigami.Units.largeSpacing``
+     */
+    property real padding: Kirigami.Units.largeSpacing
+
+    /**
+     * @brief This property holds maximum column width.
+     *
+     * default: ``Kirigami.Units.gridUnit * 50``
+     */
+    property real maximumWidth: Kirigami.Units.gridUnit * 50
+
+    /**
+     * @brief This property sets column's alignment when it hits its maximum width.
+     *
+     * default: ``Qt.AlignHCenter | Qt.AlignTop``
+     *
+     * @property Qt::Alignment alignment
+     */
+    property int alignment: Qt.AlignHCenter | Qt.AlignTop
+
+    Layout.fillWidth: true
+    Layout.fillHeight: true
+
+    enum CrossAxis {
+        Left,
+        Center,
+        Right
+    }
+
+    ColumnLayout {
+        id: __inner
+        Layout.maximumWidth: __outer.maximumWidth
+        Layout.leftMargin: __outer.alignment & Qt.AlignLeft || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
+        Layout.rightMargin: __outer.alignment & Qt.AlignRight || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
+        Layout.alignment: __outer.alignment
+    }
+}
diff --git a/src/controls/FormLayout.qml b/src/controls/FormLayout.qml
new file mode 100644 (file)
index 0000000..4519134
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.18 as Kirigami
+
+/**
+ * This is the base class for Form layouts conforming to the
+ * Kirigami Human Interface Guidelines. The layout consists
+ * of two columns: the left column contains only right-aligned
+ * labels provided by a kirigami::FormData attached property,
+ * the right column contains left-aligned child types.
+ *
+ * Child types can be sectioned using an QtQuick.Item
+ * or kirigami::Separator with a kirigami::FormData
+ * attached property, see FormLayoutAttached::isSection for details.
+ *
+ * Example usage:
+ * @include formlayout.qml
+ *
+ * @see FormLayoutAttached
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/components-formlayouts">Form Layouts in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/patterns-content/form">KDE Human Interface Guidelines on Forms</a>
+ * @since org.kde.kirigami 2.3
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: root
+
+    /**
+     * @brief This property specifies whether the form layout is in wide mode.
+     *
+     * If true, the layout will be optimized for a wide screen, such as
+     * a desktop machine (the labels will be on a left column,
+     * the fields on a right column beside it), if @c false (such as on a phone)
+     * everything is laid out in a single column.
+     *
+     * By default, this property automatically adjusts the layout
+     * if there is enough screen space.
+     *
+     * Set this to @c true for a convergent design,
+     * set this to @c false for a mobile-only design.
+     */
+    property bool wideMode: width >= lay.wideImplicitWidth
+
+    /**
+     * If for some implementation reason multiple FormLayouts have to appear
+     * on the same page, they can have each other in twinFormLayouts,
+     * so they will vertically align with each other perfectly
+     *
+     * @since KDE Frameworks 5.53
+     */
+    property list<Item> twinFormLayouts  // should be list<FormLayout> but we can't have a recursive declaration
+
+    onTwinFormLayoutsChanged: {
+        for (const i in twinFormLayouts) {
+            if (!(root in twinFormLayouts[i].children[0].reverseTwins)) {
+                twinFormLayouts[i].children[0].reverseTwins.push(root)
+                Qt.callLater(() => twinFormLayouts[i].children[0].reverseTwinsChanged());
+            }
+        }
+    }
+
+    Component.onCompleted: {
+        relayoutTimer.triggered();
+    }
+
+    Component.onDestruction: {
+        for (const i in twinFormLayouts) {
+            const twin = twinFormLayouts[i];
+            const child = twin.children[0];
+            child.reverseTwins = child.reverseTwins.filter(value => value !== root);
+        }
+    }
+
+    implicitWidth: lay.wideImplicitWidth
+    implicitHeight: lay.implicitHeight
+    Layout.preferredHeight: lay.implicitHeight
+    Layout.fillWidth: true
+    Accessible.role: Accessible.Form
+
+    GridLayout {
+        id: lay
+        property int wideImplicitWidth
+        columns: root.wideMode ? 2 : 1
+        rowSpacing: Kirigami.Units.smallSpacing
+        columnSpacing: Kirigami.Units.smallSpacing
+        width: root.wideMode ? undefined : root.width
+        anchors {
+            horizontalCenter: root.wideMode ? root.horizontalCenter : undefined
+            left: root.wideMode ? undefined : root.left
+        }
+
+        property var reverseTwins: []
+        property var knownItems: []
+        property var buddies: []
+        property int knownItemsImplicitWidth: {
+            let hint = 0;
+            for (const i in knownItems) {
+                const item = knownItems[i];
+                if (typeof item.Layout === "undefined") {
+                    // Items may have been dynamically destroyed. Even
+                    // printing such zombie wrappers results in a
+                    // meaningless "TypeError: Type error". Normally they
+                    // should be cleaned up from the array, but it would
+                    // trigger a binding loop if done here.
+                    //
+                    // This is, so far, the only way to detect them.
+                    continue;
+                }
+                const actualWidth = item.Layout.preferredWidth > 0
+                    ? item.Layout.preferredWidth
+                    : item.implicitWidth;
+
+                hint = Math.max(hint, item.Layout.minimumWidth, Math.min(actualWidth, item.Layout.maximumWidth));
+            }
+            return hint;
+        }
+        property int buddiesImplicitWidth: {
+            let hint = 0;
+
+            for (const i in buddies) {
+                if (buddies[i].visible && buddies[i].item !== null && !buddies[i].item.Kirigami.FormData.isSection) {
+                    hint = Math.max(hint, buddies[i].implicitWidth);
+                }
+            }
+            return hint;
+        }
+        readonly property var actualTwinFormLayouts: {
+            // We need to copy that array by value
+            const list = lay.reverseTwins.slice();
+            for (const i in twinFormLayouts) {
+                const parentLay = twinFormLayouts[i];
+                if (!parentLay || !parentLay.hasOwnProperty("children")) {
+                    continue;
+                }
+                list.push(parentLay);
+                for (const j in parentLay.children[0].reverseTwins) {
+                    const childLay = parentLay.children[0].reverseTwins[j];
+                    if (childLay && !(childLay in list)) {
+                        list.push(childLay);
+                    }
+                }
+            }
+            return list;
+        }
+
+        Timer {
+            id: hintCompression
+            interval: 0
+            onTriggered: {
+                if (root.wideMode) {
+                    lay.wideImplicitWidth = lay.implicitWidth;
+                }
+            }
+        }
+        onImplicitWidthChanged: hintCompression.restart();
+        //This invisible row is used to sync alignment between multiple layouts
+
+        Item {
+            Layout.preferredWidth: {
+                let hint = lay.buddiesImplicitWidth;
+                for (const i in lay.actualTwinFormLayouts) {
+                    if (lay.actualTwinFormLayouts[i] && lay.actualTwinFormLayouts[i].hasOwnProperty("children")) {
+                        hint = Math.max(hint, lay.actualTwinFormLayouts[i].children[0].buddiesImplicitWidth);
+                    }
+                }
+                return hint;
+            }
+            Layout.preferredHeight: 2
+        }
+        Item {
+            Layout.preferredWidth: {
+                let hint = Math.min(root.width, lay.knownItemsImplicitWidth);
+                for (const i in lay.actualTwinFormLayouts) {
+                    if (lay.actualTwinFormLayouts[i] && lay.actualTwinFormLayouts[i].hasOwnProperty("children")) {
+                        hint = Math.max(hint, lay.actualTwinFormLayouts[i].children[0].knownItemsImplicitWidth);
+                    }
+                }
+                return hint;
+            }
+            Layout.preferredHeight: 2
+        }
+    }
+
+    Item {
+        id: temp
+
+        /**
+         * The following two functions are used in the label buddy items.
+         *
+         * They're in this mostly unused item to keep them private to the FormLayout
+         * without creating another QObject.
+         *
+         * Normally, such complex things in bindings are kinda bad for performance
+         * but this is a fairly static property. If for some reason an application
+         * decides to obsessively change its alignment, V8's JIT hotspot optimisations
+         * will kick in.
+         */
+
+        /**
+         * @param {Item} item
+         * @returns {Qt::Alignment}
+         */
+        function effectiveLayout(item) {
+            if (!item) {
+                return 0;
+            }
+            const verticalAlignment =
+                item.Kirigami.FormData.labelAlignment !== 0
+                ? item.Kirigami.FormData.labelAlignment
+                : Qt.AlignTop;
+
+            if (item.Kirigami.FormData.isSection) {
+                return Qt.AlignHCenter;
+            }
+            if (root.wideMode) {
+                return Qt.AlignRight | verticalAlignment;
+            }
+            return Qt.AlignLeft | Qt.AlignBottom;
+        }
+
+        /**
+         * @param {Item} item
+         * @returns vertical alignment of the item passed as an argument.
+         */
+        function effectiveTextLayout(item) {
+            if (!item) {
+                return 0;
+            }
+            if (root.wideMode) {
+                return item.Kirigami.FormData.labelAlignment !== 0 ? item.Kirigami.FormData.labelAlignment : Text.AlignVCenter;
+            }
+            return Text.AlignBottom;
+        }
+    }
+
+    Timer {
+        id: relayoutTimer
+        interval: 0
+        onTriggered: {
+            const __items = root.children;
+            // exclude the layout and temp
+            for (let i = 2; i < __items.length; ++i) {
+                const item = __items[i];
+
+                // skip items that are already there
+                if (lay.knownItems.indexOf(item) !== -1 || item instanceof Repeater) {
+                    continue;
+                }
+                lay.knownItems.push(item);
+
+                const itemContainer = itemComponent.createObject(temp, { item });
+
+                // if it's a labeled section header, add extra spacing before it
+                if (item.Kirigami.FormData.label.length > 0 && item.Kirigami.FormData.isSection) {
+                    placeHolderComponent.createObject(lay, { item });
+                }
+
+                const buddy = item.Kirigami.FormData.checkable
+                    ? checkableBuddyComponent.createObject(lay, { item })
+                    : buddyComponent.createObject(lay, { item, index: i - 2 });
+
+                itemContainer.parent = lay;
+                lay.buddies.push(buddy);
+            }
+            lay.knownItemsChanged();
+            lay.buddiesChanged();
+            hintCompression.triggered();
+        }
+    }
+
+    onChildrenChanged: relayoutTimer.restart();
+
+    Component {
+        id: itemComponent
+        Item {
+            id: container
+
+            property Item item
+
+            enabled: item !== null && item.enabled
+            visible: item !== null && item.visible
+
+            // NOTE: work around a  GridLayout quirk which doesn't lay out items with null size hints causing things to be laid out incorrectly in some cases
+            implicitWidth: item !== null ? Math.max(item.implicitWidth, 1) : 0
+            implicitHeight: item !== null ? Math.max(item.implicitHeight, 1) : 0
+            Layout.preferredWidth: item !== null ? Math.max(1, item.Layout.preferredWidth > 0 ? item.Layout.preferredWidth : Math.ceil(item.implicitWidth)) : 0
+            Layout.preferredHeight: item !== null ? Math.max(1, item.Layout.preferredHeight > 0 ? item.Layout.preferredHeight : Math.ceil(item.implicitHeight)) : 0
+
+            Layout.minimumWidth: item !== null ? item.Layout.minimumWidth : 0
+            Layout.minimumHeight: item !== null ? item.Layout.minimumHeight : 0
+
+            Layout.maximumWidth: item !== null ? item.Layout.maximumWidth : 0
+            Layout.maximumHeight: item !== null ? item.Layout.maximumHeight : 0
+
+            Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+            Layout.fillWidth: item !== null && (item instanceof TextInput || item.Layout.fillWidth || item.Kirigami.FormData.isSection)
+            Layout.columnSpan: item !== null && item.Kirigami.FormData.isSection ? lay.columns : 1
+            onItemChanged: {
+                if (!item) {
+                    container.destroy();
+                }
+            }
+            onXChanged: if (item !== null) { item.x = x + lay.x; }
+            // Assume lay.y is always 0
+            onYChanged: if (item !== null) { item.y = y + lay.y; }
+            onWidthChanged: if (item !== null) { item.width = width; }
+            Component.onCompleted: item.x = x + lay.x;
+            Connections {
+                target: lay
+                function onXChanged() {
+                    if (item !== null) {
+                        item.x = x + lay.x;
+                    }
+                }
+            }
+        }
+    }
+    Component {
+        id: placeHolderComponent
+        Item {
+            property Item item
+
+            enabled: item !== null && item.enabled
+            visible: item !== null && item.visible
+
+            width: Kirigami.Units.smallSpacing
+            height: Kirigami.Units.smallSpacing
+            Layout.topMargin: item !== null && item.height > 0 ? Kirigami.Units.smallSpacing : 0
+            onItemChanged: {
+                if (!item) {
+                    labelItem.destroy();
+                }
+            }
+        }
+    }
+    Component {
+        id: buddyComponent
+        Kirigami.Heading {
+            id: labelItem
+
+            property Item item
+            property int index
+
+            enabled: item !== null && item.enabled && item.Kirigami.FormData.enabled
+            visible: item !== null && item.visible && (root.wideMode || text.length > 0)
+            Kirigami.MnemonicData.enabled: item !== null && item.Kirigami.FormData.buddyFor && item.Kirigami.FormData.buddyFor.activeFocusOnTab
+            Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.FormLabel
+            Kirigami.MnemonicData.label: item !== null ? item.Kirigami.FormData.label : ""
+            text: Kirigami.MnemonicData.richTextLabel
+            type: item !== null && item.Kirigami.FormData.isSection ? Kirigami.Heading.Type.Primary : Kirigami.Heading.Type.Normal
+
+            level: item !== null && item.Kirigami.FormData.isSection ? 3 : 5
+
+            Layout.columnSpan: item !== null && item.Kirigami.FormData.isSection ? lay.columns : 1
+            Layout.preferredHeight: {
+                if (!item) {
+                    return 0;
+                }
+                if (item.Kirigami.FormData.label.length > 0) {
+                    if (root.wideMode && !(item.Kirigami.FormData.buddyFor instanceof QQC2.TextArea)) {
+                        return Math.max(implicitHeight, item.Kirigami.FormData.buddyFor.height)
+                    }
+                    return implicitHeight;
+                }
+                return Kirigami.Units.smallSpacing;
+            }
+
+            Layout.alignment: temp.effectiveLayout(item)
+            verticalAlignment: temp.effectiveTextLayout(item)
+
+            Layout.fillWidth: !root.wideMode
+            wrapMode: Text.Wrap
+
+            Layout.topMargin: {
+                if (!item) {
+                    return 0;
+                }
+                if (root.wideMode && item.Kirigami.FormData.buddyFor.parent !== root) {
+                    return item.Kirigami.FormData.buddyFor.y;
+                }
+                if (index === 0 || root.wideMode) {
+                    return 0;
+                }
+                return Kirigami.Units.largeSpacing * 2;
+            }
+            onItemChanged: {
+                if (!item) {
+                    labelItem.destroy();
+                }
+            }
+            Shortcut {
+                sequence: labelItem.Kirigami.MnemonicData.sequence
+                onActivated: labelItem.item.Kirigami.FormData.buddyFor.forceActiveFocus()
+            }
+        }
+    }
+    Component {
+        id: checkableBuddyComponent
+        QQC2.CheckBox {
+            id: labelItem
+
+            property Item item
+
+            visible: item !== null && item.visible
+            Kirigami.MnemonicData.enabled: item !== null && item.Kirigami.FormData.buddyFor && item.Kirigami.FormData.buddyFor.activeFocusOnTab
+            Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.FormLabel
+            Kirigami.MnemonicData.label: item !== null ? item.Kirigami.FormData.label : ""
+
+            Layout.columnSpan: item !== null && item.Kirigami.FormData.isSection ? lay.columns : 1
+            Layout.preferredHeight: {
+                if (!item) {
+                    return 0;
+                }
+                if (item.Kirigami.FormData.label.length > 0) {
+                    if (root.wideMode && !(item.Kirigami.FormData.buddyFor instanceof QQC2.TextArea)) {
+                        return Math.max(implicitHeight, item.Kirigami.FormData.buddyFor.height);
+                    }
+                    return implicitHeight;
+                }
+                return Kirigami.Units.smallSpacing;
+            }
+
+            Layout.alignment: temp.effectiveLayout(this)
+            Layout.topMargin: item !== null && item.Kirigami.FormData.buddyFor.height > implicitHeight * 2 ? Kirigami.Units.smallSpacing/2 : 0
+
+            activeFocusOnTab: indicator.visible && indicator.enabled
+            // HACK: desktop style checkboxes have also the text in the background item
+            // text: Kirigami.MnemonicData.richTextLabel
+            enabled: item !== null && item.Kirigami.FormData.enabled
+            checked: item !== null && item.Kirigami.FormData.checked
+
+            onItemChanged: {
+                if (!item) {
+                    labelItem.destroy();
+                }
+            }
+            Shortcut {
+                sequence: labelItem.Kirigami.MnemonicData.sequence
+                onActivated: {
+                    checked = !checked;
+                    item.Kirigami.FormData.buddyFor.forceActiveFocus();
+                }
+            }
+            onCheckedChanged: {
+                item.Kirigami.FormData.checked = checked;
+            }
+            contentItem: Kirigami.Heading {
+                id: labelItemHeading
+                level: labelItem.item !== null && labelItem.item.Kirigami.FormData.isSection ? 3 : 5
+                text: labelItem.Kirigami.MnemonicData.richTextLabel
+                type: labelItem.item !== null && labelItem.item.Kirigami.FormData.isSection ? Kirigami.Heading.Type.Primary : Kirigami.Heading.Type.Normal
+                verticalAlignment: temp.effectiveTextLayout(labelItem.item)
+                enabled: labelItem.item !== null && labelItem.item.Kirigami.FormData.enabled
+                leftPadding: height  // parent.indicator.width
+            }
+            Rectangle {
+                enabled: labelItem.indicator.enabled
+                anchors.left: labelItemHeading.left
+                anchors.right: labelItemHeading.right
+                anchors.top: labelItemHeading.bottom
+                anchors.leftMargin: labelItemHeading.leftPadding
+                height: 1
+                color: Kirigami.Theme.highlightColor
+                visible: labelItem.activeFocus && labelItem.indicator.visible
+            }
+        }
+    }
+}
diff --git a/src/controls/GlobalDrawer.qml b/src/controls/GlobalDrawer.qml
new file mode 100644 (file)
index 0000000..255b660
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Templates 2.3 as T2
+import QtQuick.Controls 2.2 as QQC2
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.13 as Kirigami
+import "private" as P
+
+/**
+ * A specialized form of the QtQuick.Controls.Drawer intended for showing an application's
+ * always-available global actions. Think of it like a mobile version of
+ * a desktop application's menubar.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ *
+ * Kirigami.ApplicationWindow {
+ *  [...]
+ *     globalDrawer: Kirigami.GlobalDrawer {
+ *         actions: [
+ *            Kirigami.Action {
+ *                text: "View"
+ *                icon.name: "view-list-icons"
+ *                Kirigami.Action {
+ *                        text: "action 1"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 2"
+ *                }
+ *                Kirigami.Action {
+ *                        text: "action 3"
+ *                }
+ *            },
+ *            Kirigami.Action {
+ *                text: "Sync"
+ *                icon.name: "folder-sync"
+ *            }
+ *         ]
+ *     }
+ *  [...]
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/components-drawers/#global-drawer">Global Drawers in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/components/navigation/globaldrawer">Human Interface Guidelines on Global Drawers</a>
+ * @see <a href="https://develop.kde.org/hig/patterns-command/drawer/#global-drawer">KDE Human Interface Guidelines' Short Introduction of Global Drawers</a>
+ */
+OverlayDrawer {
+    id: root
+    edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge
+    handleClosedIcon.source: null
+    handleOpenIcon.source: null
+    handleVisible: (modal || !drawerOpen) && (typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true) && (!isMenu || Kirigami.Settings.isMobile)
+
+    enabled: !isMenu || Kirigami.Settings.isMobile
+
+//BEGIN properties
+    /**
+     * @brief This property holds the title displayed at the top of the drawer.
+     * @see kirigami::private::BannerImage::title
+     * @property string title
+     */
+    property alias title: bannerImage.title
+
+    /**
+     * @brief This property holds an icon to be displayed alongside the title.
+     * @see kirigami::private::BannerImage::titleIcon
+     * @see Icon::source
+     * @property var titleIcon
+     */
+    property alias titleIcon: bannerImage.titleIcon
+
+    /**
+     * @brief This property holds the banner image source.
+     * @see kirigami::ShadowedImage::source
+     * @property url bannerImageSource
+     */
+    property alias bannerImageSource: bannerImage.source
+
+    /**
+     * @brief This property holds the actions displayed in the drawer.
+     *
+     * The list of actions can be nested having a tree structure.
+     * A tree depth bigger than 2 is discouraged.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationWindow {
+     *  [...]
+     *     globalDrawer: Kirigami.GlobalDrawer {
+     *         actions: [
+     *            Kirigami.Action {
+     *                text: "View"
+     *                icon.name: "view-list-icons"
+     *                Kirigami.Action {
+     *                    text: "action 1"
+     *                }
+     *                Kirigami.Action {
+     *                    text: "action 2"
+     *                }
+     *                Kirigami.Action {
+     *                    text: "action 3"
+     *                }
+     *            },
+     *            Kirigami.Action {
+     *                text: "Sync"
+     *                icon.name: "folder-sync"
+     *            }
+     *         ]
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     * @property list<Action> actions
+     */
+    property list<QtObject> actions
+
+    /**
+     * @brief This property holds an item that will always be displayed at the top of the drawer.
+     *
+     * If the drawer contents can be scrolled, this item will stay still and won't scroll.
+     *
+     * @note This property is mainly intended for toolbars.
+     * @since org.kde.kirigami 2.12
+     */
+    property Item header
+
+    /**
+     * @brief This property sets drawers banner visibility.
+     *
+     * If true, the banner area (which can contain an image,
+     * an icon, and a title) will be visible.
+     *
+     * default: `the banner will be visible only on mobile platforms`
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property bool bannerVisible: Kirigami.Settings.isMobile
+
+    /**
+     * @brief This property holds items that are displayed above the actions.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationWindow {
+     *  [...]
+     *     globalDrawer: Kirigami.GlobalDrawer {
+     *         actions: [...]
+     *         topContent: [Button {
+     *             text: "Button"
+     *             onClicked: //do stuff
+     *         }]
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     * @property list<QtObject> topContent
+     */
+    property alias topContent: topContent.data
+
+    /**
+     * @brief This property holds items that are displayed under the actions.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationWindow {
+     *  [...]
+     *     globalDrawer: Kirigami.GlobalDrawer {
+     *         actions: [...]
+     *         Button {
+     *             text: "Button"
+     *             onClicked: //do stuff
+     *         }
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     * @note This is a `default` property.
+     * @property list<QtObject> content
+     */
+    default property alias content: mainContent.data
+
+    /**
+     * @brief This property sets whether content items at the top should be shown.
+     * when the drawer is collapsed as a sidebar.
+     *
+     * If you want to keep some items visible and some invisible, set this to
+     * @c false and control the visibility/opacity of individual items,
+     * binded to the OverlayDrawer.collapsed property.
+     *
+     * default: ``false``
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property bool showTopContentWhenCollapsed: false
+
+    /**
+     * @brief This property sets whether content items at the bottom should be shown.
+     * when the drawer is collapsed as a sidebar.
+     *
+     * If you want to keep some items visible and some invisible, set this to
+     * @c false and control the visibility/opacity of individual items,
+     * binded to the collapsed property
+     *
+     * default: ``false``
+     *
+     * @see ::content
+     * @since org.kde.kirigami 2.5
+     */
+    property bool showContentWhenCollapsed: false
+
+    // TODO
+    property bool showHeaderWhenCollapsed: false
+
+    /**
+     * @brief This property sets whether activating a leaf action resets the
+     * menu to show leaf's parent actions.
+     * 
+     * A leaf action is an action without any child actions.
+     *
+     * default: ``true``
+     */
+    property bool resetMenuOnTriggered: true
+
+    /**
+     * @brief This property points to the action acting as a submenu
+     */
+    readonly property Action currentSubMenu: stackView.currentItem ? stackView.currentItem.current: null
+
+    /**
+     * @brief This property sets whether the drawer becomes a menu on the desktop.
+     *
+     * default: ``false``
+     *
+     * @since org.kde.kirigami 2.11
+     */
+    property bool isMenu: false
+
+    /**
+     * @brief This property sets the visibility of the collapse button
+     * when the drawer collapsible.
+     *
+     * default: ``true``
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property bool collapseButtonVisible: true
+//END properties
+
+    /**
+     * @brief This signal notifies that the banner has been clicked.
+     */
+    signal bannerClicked()
+
+    /**
+     * @brief This function reverts the menu back to its initial state
+     */
+    function resetMenu() {
+        stackView.pop(stackView.get(0, T2.StackView.DontLoad));
+        if (root.modal) {
+            root.drawerOpen = false;
+        }
+    }
+
+    // rightPadding: !Kirigami.Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Kirigami.Units.gridUnit : Kirigami.Units.smallSpacing
+
+    Kirigami.Theme.colorSet: modal ? Kirigami.Theme.Window : Kirigami.Theme.View
+
+    onHeaderChanged: {
+        if (header) {
+            header.parent = headerContainer
+            header.Layout.fillWidth = true;
+            if (header.z === undefined) {
+                header.z = 1;
+            }
+            if (header instanceof T2.ToolBar) {
+                header.position = T2.ToolBar.Header
+            } else if (header instanceof T2.TabBar) {
+                header.position = T2.TabBar.Header
+            } else if (header instanceof T2.DialogButtonBox) {
+                header.position = T2.DialogButtonBox.Header
+            }
+        }
+    }
+
+    contentItem: QQC2.ScrollView {
+        id: scrollView
+        //ensure the attached property exists
+        Kirigami.Theme.inherit: true
+        anchors.fill: parent
+        implicitWidth: Math.min (Kirigami.Units.gridUnit * 20, root.parent.width * 0.8)
+        QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
+        QQC2.ScrollBar.vertical.anchors {
+            top: scrollView.top
+            bottom: scrollView.bottom
+            topMargin: headerParent.height + headerParent.y
+        }
+
+        Flickable {
+            id: mainFlickable
+            contentWidth: width
+            contentHeight: mainColumn.Layout.minimumHeight
+            topMargin: headerParent.height
+
+            ColumnLayout {
+                id: headerParent
+                parent: mainFlickable
+                anchors {
+                    left: parent.left
+                    right: parent.right
+                    rightMargin: Math.min(0, -scrollView.width + mainFlickable.width)
+                }
+                spacing: 0
+                y: bannerImage.visible ? Math.max(headerContainer.height, -mainFlickable.contentY) - height : 0
+
+                Layout.fillWidth: true
+                // visible: !bannerImage.empty || root.collapsible
+
+                P.BannerImage {
+                    id: bannerImage
+
+
+                    visible: !bannerImage.empty && opacity > 0 && root.bannerVisible
+                    opacity: !root.collapsed
+                    fillMode: Image.PreserveAspectCrop
+
+                    Behavior on opacity {
+                        OpacityAnimator {
+                            duration: Kirigami.Units.longDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                    // leftPadding: root.collapsible ? collapseButton.width + Kirigami.Units.smallSpacing*2 : topPadding
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: mouse => root.bannerClicked()
+                    }
+                    P.EdgeShadow {
+                        edge: Qt.BottomEdge
+                        visible: bannerImageSource != ""
+                        anchors {
+                            left: parent.left
+                            right: parent.right
+                            bottom: parent.top
+                        }
+                    }
+                }
+                RowLayout {
+                    id: headerContainer
+                    Kirigami.Theme.inherit: false
+                    Kirigami.Theme.colorSet: Kirigami.Theme.Window
+
+                    Layout.fillWidth: true
+                    visible: opacity > 0
+                    // Workaround for https://bugreports.qt.io/browse/QTBUG-90034
+                    Layout.preferredHeight: {
+                        if (children.length > 0 && children[0].visible) {
+                            if (opacity === 1) {
+                                return -1;
+                            } else {
+                                return implicitHeight * opacity;
+                            }
+                        } else {
+                            return 0;
+                        }
+                    }
+                    opacity: !root.collapsed || showHeaderWhenCollapsed
+                    Behavior on opacity {
+                        // not an animator as is binded
+                        NumberAnimation {
+                            duration: Kirigami.Units.longDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                }
+            }
+
+
+            ColumnLayout {
+                id: mainColumn
+                width: mainFlickable.width
+                spacing: 0
+                height: Math.max(root.height - headerParent.height, Layout.minimumHeight)
+
+                ColumnLayout {
+                    id: topContent
+                    spacing: 0
+                    Layout.alignment: Qt.AlignHCenter
+                    Layout.leftMargin: root.leftPadding
+                    Layout.rightMargin: root.rightPadding
+                    Layout.bottomMargin: Kirigami.Units.smallSpacing
+                    Layout.topMargin: root.topPadding
+                    Layout.fillWidth: true
+                    Layout.fillHeight: true
+                    Layout.preferredHeight: implicitHeight * opacity
+                    // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
+                    // as items are added only after this column creation
+                    Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
+                    visible: children.length > 0 && childrenRect.height > 0 && opacity > 0
+                    opacity: !root.collapsed || showTopContentWhenCollapsed
+                    Behavior on opacity {
+                        // not an animator as is binded
+                        NumberAnimation {
+                            duration: Kirigami.Units.longDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                }
+
+                T2.StackView {
+                    id: stackView
+                    clip: true
+                    Layout.fillWidth: true
+                    Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0
+                    Layout.maximumHeight: Layout.minimumHeight
+                    property P.ActionsMenu openSubMenu
+                    initialItem: menuComponent
+                    // NOTE: it's important those are NumberAnimation and not XAnimators
+                    // as while the animation is running the drawer may close, and
+                    // the animator would stop when not drawing see BUG 381576
+                    popEnter: Transition {
+                        NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+
+                    popExit: Transition {
+                        NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+
+                    pushEnter: Transition {
+                        NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+
+                    pushExit: Transition {
+                        NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+
+                    replaceEnter: Transition {
+                        NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+
+                    replaceExit: Transition {
+                        NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
+                    }
+                }
+                Item {
+                    Layout.fillWidth: true
+                    Layout.fillHeight: root.actions.length>0
+                    Layout.minimumHeight: Kirigami.Units.smallSpacing
+                }
+
+                ColumnLayout {
+                    id: mainContent
+                    Layout.alignment: Qt.AlignHCenter
+                    Layout.leftMargin: root.leftPadding
+                    Layout.rightMargin: root.rightPadding
+                    Layout.fillWidth: true
+                    Layout.fillHeight: true
+                    // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
+                    // as items are added only after this column creation
+                    Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
+                    visible: children.length > 0 && (opacity > 0 || mainContentAnimator.running)
+                    opacity: !root.collapsed || showContentWhenCollapsed
+                    Behavior on opacity {
+                        OpacityAnimator {
+                            id: mainContentAnimator
+                            duration: Kirigami.Units.longDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                }
+                Item {
+                    Layout.minimumWidth: Kirigami.Units.smallSpacing
+                    Layout.minimumHeight: root.bottomPadding
+                }
+
+                Component {
+                    id: menuComponent
+
+                    Column {
+                        spacing: 0
+                        property alias model: actionsRepeater.model
+                        property Action current
+
+                        property int level: 0
+                        Layout.maximumHeight: Layout.minimumHeight
+
+                        BasicListItem {
+                            id: backItem
+                            visible: level > 0
+                            icon: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic")
+
+                            label: Kirigami.MnemonicData.richTextLabel
+                            Kirigami.MnemonicData.enabled: backItem.enabled && backItem.visible
+                            Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
+                            Kirigami.MnemonicData.label: qsTr("Back")
+
+                            separatorVisible: false
+                            onClicked: stackView.pop()
+
+                            Keys.onEnterPressed: stackView.pop()
+                            Keys.onReturnPressed: stackView.pop()
+
+                            Keys.onDownPressed: nextItemInFocusChain().focus = true
+                            Keys.onUpPressed: nextItemInFocusChain(false).focus = true
+                        }
+                        Shortcut {
+                            sequence: backItem.Kirigami.MnemonicData.sequence
+                            onActivated: backItem.clicked()
+                        }
+
+                        Repeater {
+                            id: actionsRepeater
+
+                            readonly property bool withSections: {
+                                for (let i = 0; i < root.actions.length; i++) {
+                                    const action = root.actions[i];
+                                    if (!(action.hasOwnProperty("expandible") && action.expandible)) {
+                                        return false;
+                                    }
+                                }
+                                return true;
+                            }
+
+                            model: root.actions
+                            delegate: Column {
+                                width: parent.width
+                                P.GlobalDrawerActionItem {
+                                    id: drawerItem
+                                    visible: (modelData.hasOwnProperty("visible") && modelData.visible) && (root.collapsed || !(modelData.hasOwnProperty("expandible") && modelData.expandible))
+                                    width: parent.width
+                                    onCheckedChanged: {
+                                        // move every checked item into view
+                                        if (checked && topContent.height + backItem.height + (model.index + 1) * height - mainFlickable.contentY > mainFlickable.height) {
+                                            mainFlickable.contentY += height
+                                        }
+                                    }
+                                    Kirigami.Theme.colorSet: drawerItem.visible && !root.modal && !root.collapsed && actionsRepeater.withSections ? Kirigami.Theme.Window : parent.Kirigami.Theme.colorSet
+                                    backgroundColor: Kirigami.Theme.backgroundColor
+                                }
+                                Item {
+                                    id: headerItem
+                                    visible: !root.collapsed && (modelData.hasOwnProperty("expandible") && modelData.expandible && !!modelData.children && modelData.children.length > 0)
+                                    height: sectionHeader.implicitHeight
+                                    width: parent.width
+                                    Kirigami.ListSectionHeader {
+                                        id: sectionHeader
+                                        anchors.fill: parent
+                                        Kirigami.Theme.colorSet: root.modal ? Kirigami.Theme.View : Kirigami.Theme.Window
+                                        contentItem: RowLayout {
+                                            Kirigami.Icon {
+                                                property int size: Kirigami.Units.iconSizes.smallMedium
+                                                Layout.minimumHeight: size
+                                                Layout.maximumHeight: size
+                                                Layout.minimumWidth: size
+                                                Layout.maximumWidth: size
+                                                source: modelData.icon.name || modelData.icon.source
+                                            }
+                                            Heading {
+                                                id: header
+                                                level: 4
+                                                text: modelData.text
+                                            }
+                                            Item {
+                                                Layout.fillWidth: true
+                                            }
+                                        }
+                                    }
+                                }
+                                Repeater {
+                                    id: __repeater
+                                    model: headerItem.visible ? modelData.children : null
+                                    delegate: P.GlobalDrawerActionItem {
+                                        width: parent.width
+                                        opacity: !root.collapsed
+                                        leftPadding: actionsRepeater.withSections && !root.collapsed && !root.modal ? padding * 2 : padding * 4
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+
+                QQC2.ToolButton {
+                    icon.name: root.collapsed ? "view-right-new" : "view-right-close"
+                    Layout.fillWidth: root.collapsed
+                    onClicked: root.collapsed = !root.collapsed
+                    visible: root.collapsible && root.collapseButtonVisible
+                    text: root.collapsed ? "" : qsTr("Close Sidebar")
+
+                    QQC2.ToolTip.visible: root.collapsed && hovered
+                    QQC2.ToolTip.text: qsTr("Open Sidebar")
+                    QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+                }
+            }
+        }
+    }
+}
+
diff --git a/src/controls/Heading.qml b/src/controls/Heading.qml
new file mode 100644 (file)
index 0000000..0693aca
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ *  SPDX-FileCopyrightText: 2012 by Sebastian Kügler <sebas@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.0
+import QtQuick.Controls 2.0 as QQC2
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * @brief A heading label used for subsections of texts.
+ *
+ * The characteristics of the text will be automatically set according to the
+ * Kirigami.PlatformTheme. Use this components for section titles or headings in your UI,
+ * for example page or section titles.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.4 as Kirigami
+ * [...]
+ * Column {
+ *     Kirigami.Heading {
+ *         text: "Apples in the sunlight"
+ *         level: 2
+ *     }
+ *   [...]
+ * }
+ * @endcode
+ *
+ * @see QtQuick.Controls.Label
+ * @see QtQuick.Text
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/style-typography">Typography in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/style/typography">KDE Human Interface Guidelines on Typography</a>
+ * @inherit QtQuick.Controls.Label
+ */
+QQC2.Label {
+    id: heading
+
+    /**
+     * @brief This property holds the level of the heading, determining its font size.
+     *
+     * Acceptable values range from 1 (big) to 5 (small).
+     *
+     * default: ``1``
+     */
+    property int level: 1
+
+    /**
+     * @brief This property holds the point size between heading levels.
+     *
+     * default: ``0``
+     *
+     * @deprecated
+     */
+    property int step: 0
+
+    /**
+     * @brief This enumeration defines heading types.
+     *
+     * This enum helps with heading visibility (making it less or more important).
+     */
+    enum Type {
+        Normal,
+        Primary,
+        Secondary
+    }
+
+    /**
+     * @brief This property holds the heading type.
+     *
+     * The following values are allowed:
+     * * ``Kirigami.Heading.Type.Normal``: Creates a normal heading (default).
+     * * ``Kirigami.Heading.Type.Primary``: Makes the heading more prominent. Useful
+     *   when making the heading bigger is not enough.
+     * * ``Kirigami.Heading.Type.Secondary``: Makes the heading less prominent.
+     *   Useful when an heading is for a less important section in an application.
+     *
+     * @since KDE Frameworks 5.82
+     * @property Kirigami.Heading.Type type
+     */
+    property int type: Kirigami.Heading.Type.Normal
+
+    font.pointSize: __headerPointSize(level)
+    font.weight: type === Kirigami.Heading.Type.Primary ? Font.DemiBold : Font.Normal
+
+    opacity: type === Kirigami.Heading.Type.Secondary ? 0.7 : 1
+
+    Accessible.role: Accessible.Heading
+
+    // TODO KF6: Remove this public method
+    function headerPointSize(l) {
+        console.warn("org.kde.plasma.extras/Heading::headerPointSize() is deprecated. Use font.pointSize directly instead");
+        return __headerPointSize(l);
+    }
+
+    //
+    //  W A R N I N G
+    //  -------------
+    //
+    // This method is not part of the Kirigami API.  It exists purely as an
+    // implementation detail.  It may change from version to
+    // version without notice, or even be removed.
+    //
+    // We mean it.
+    //
+    function __headerPointSize(level) {
+        const n = Kirigami.Theme.defaultFont.pointSize;
+        switch (level) {
+        case 1:
+            return n * 1.35 + step;
+        case 2:
+            return n * 1.20 + step;
+        case 3:
+            return n * 1.15 + step;
+        case 4:
+            return n * 1.10 + step;
+        default:
+            return n + step;
+        }
+    }
+}
diff --git a/src/controls/Hero.qml b/src/controls/Hero.qml
new file mode 100644 (file)
index 0000000..4b36167
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.14
+import QtQuick.Controls 2.4 as QQC2
+import org.kde.kirigami 2.13 as Kirigami
+
+/**
+ * @brief An element that implements a shared element transition, otherwise known as a "hero animation".
+ */
+Item {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds the item to animate from in the Hero animation.
+     */
+    property Item source
+
+    /**
+     * @brief This property holds the item to animate to in the Hero animation.
+     */
+    property Item destination
+
+    /**
+     * @brief This property sets whether the source item will reappear
+     * in the original position when the Hero animation completes.
+     *
+     * default: ``true``
+     */
+    property bool restore: true
+
+    /**
+     * Group of properties related to the mask of the object when performing a hero animation.
+     * This contains the default mask as well as the properties required to create a custom mask.
+     *
+     * The default mask of the Hero will transition from a circle to a rectangle on open(), and
+     * from a rectangle to a circle on close().
+     *
+     * This grouped property has the following sub-properties:
+     * * ``sourceProgress: real`` the progress of the animation, where 0 is the start and 1 is the end.
+     * * ``destinationProgress: real`` the progress of the animation, where 1 is the start and 0 is the end.
+     * * ``mask.sourceHeight: real`` the height of the source item.
+     * * ``mask.sourceWidth: real`` the width of the source item.
+     * * ``mask.destinationWidth: real`` the width of the destination item.
+     * * ``mask.destinationHeight: real`` the height of the destination item.
+     * * ``item: Rectangle`` the item used to mask the Hero during animation. This should bind to the sourceProgress and destinationProgress to change as the animation progresses.
+     *
+     */
+    readonly property QtObject mask: QtObject {
+        /**
+         * @brief This property holds the progress of the animation,
+         * where 0 is the start and 1 is the end.
+         */
+        readonly property real sourceProgress: sourceEffect.progress
+
+        /**
+         * @brief This property holds the progress of the animation,
+         * where 1 is the start and 0 is the end.
+         */
+        readonly property real destinationProgress: destinationEffect.progress
+
+        /**
+         * @brief This property holds the height of the source item.
+         */
+        readonly property real sourceHeight: sourceEffect.height
+
+        /**
+        * @brief This property holds the width of the source item.
+        */
+        readonly property real sourceWidth: sourceEffect.width
+
+        /**
+         * @brief This property holds the width of the destination item.
+         */
+        readonly property real destinationWidth: destinationEffect.width
+
+        /**
+         * @brief This property holds the height of the destination item.
+         */
+        readonly property real destinationHeight: destinationEffect.height
+
+        /**
+         * @brief This property holds the item used to mask the Hero during animation.
+         *
+         * This should bind to the sourceProgress and destinationProgress to change as the animation progresses.
+         */
+        property Item item: Rectangle {
+            visible: false
+            color: "white"
+
+            radius: (width/2) * mask.destinationProgress
+            width: (mask.sourceWidth * mask.sourceProgress) + (mask.destinationWidth * mask.destinationProgress)
+            height: (mask.sourceHeight * mask.sourceProgress) + (mask.destinationHeight * mask.destinationProgress)
+
+            layer.enabled: true
+            layer.smooth: true
+        }
+    }
+
+    property alias duration: sourceAni.duration
+    readonly property QtObject easing: QtObject {
+        property alias amplitude: sourceAni.easing.amplitude
+        property alias bezierCurve: sourceAni.easing.bezierCurve
+        property alias overshoot: sourceAni.easing.overshoot
+        property alias period: sourceAni.easing.period
+        property alias type: sourceAni.easing.type
+    }
+//END properties
+
+    function open() {
+        if (source !== null && destination !== null && !heroAnimation.running) {
+            heroAnimation.source = source
+            heroAnimation.destination = destination
+            heroAnimation.restart()
+        }
+    }
+    function close() {
+        if (source !== null && destination !== null && !heroAnimation.running) {
+            // doing a switcheroo simplifies the code
+            heroAnimation.source = destination
+            heroAnimation.destination = source
+            heroAnimation.restart()
+        }
+    }
+
+    SequentialAnimation {
+        id: heroAnimation
+
+        property Item source: Item {}
+        property Item destination: Item {}
+
+        ScriptAction {
+            script: {
+                heroAnimation.source.layer.enabled = true
+                heroAnimation.source.layer.smooth = true
+                heroAnimation.destination.layer.enabled = true
+                heroAnimation.destination.layer.smooth = true
+                sourceEffect.visible = true
+                destinationEffect.visible = true
+                sourceEffect.source = null
+                sourceEffect.source = heroAnimation.source
+                destinationEffect.source = null
+                destinationEffect.source = heroAnimation.destination
+                heroAnimation.source.opacity = 0
+                heroAnimation.destination.opacity = 0
+                sourceEffect.parent.visible = true
+            }
+        }
+        ParallelAnimation {
+            NumberAnimation {
+                id: sourceAni
+
+                target: sourceEffect
+                property: "progress"
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+            NumberAnimation {
+                target: destinationEffect
+                property: "progress"
+                from: 1
+                to: 0
+                duration: root.duration
+                easing.amplitude: root.easing.amplitude
+                easing.bezierCurve: root.easing.bezierCurve
+                easing.overshoot: root.easing.overshoot
+                easing.period: root.easing.period
+                easing.type: root.easing.type
+            }
+        }
+        ScriptAction {
+            script: {
+                sourceEffect.visible = false
+                destinationEffect.visible = false
+                heroAnimation.source.layer.enabled = false
+                heroAnimation.source.layer.smooth = false
+                heroAnimation.destination.layer.enabled = false
+                heroAnimation.destination.layer.smooth = false
+                heroAnimation.destination.opacity = 1
+                if (root.restore) {
+                    heroAnimation.source.opacity = 1
+                }
+                sourceEffect.parent.visible = false
+            }
+        }
+
+    }
+
+    QtObject {
+        id: __privateShaderSources
+        readonly property string vertexShader: `
+uniform highp mat4 qt_Matrix;
+attribute highp vec4 qt_Vertex;
+attribute highp vec2 qt_MultiTexCoord0;
+varying highp vec2 qt_TexCoord0;
+uniform highp float startX;
+uniform highp float startY;
+uniform highp float targetX;
+uniform highp float targetY;
+uniform highp float scaleWidth;
+uniform highp float scaleHeight;
+uniform highp float progress;
+
+highp mat4 morph = mat4(1.0 + (scaleWidth - 1.0) * progress, 0.0, 0.0, startX*(1.0-progress) + targetX*progress,
+                        0.0, 1.0 + (scaleHeight - 1.0) * progress, 0.0, startY*(1.0-progress) + targetY*progress,
+                        0.0, 0.0, 1.0, 0.0,
+                        0.0, 0.0, 0.0, 1.0);
+
+void main() {
+    qt_TexCoord0 = qt_MultiTexCoord0;
+    gl_Position = qt_Matrix * qt_Vertex * morph;
+}
+        `
+    }
+
+    ShaderEffect {
+        id: sourceEffect
+        x: 0
+        y: 0
+        parent: heroAnimation.source.QQC2.Overlay.overlay
+        width: heroAnimation.source.width
+        height: heroAnimation.source.height
+        visible: false
+        property variant source: heroAnimation.source
+        property real progress: 0
+        property real startX: heroAnimation.source.Kirigami.ScenePosition.x / (applicationWindow().width / 2)
+        property real startY: -heroAnimation.source.Kirigami.ScenePosition.y / (applicationWindow().height / 2)
+
+        property real targetX: scaleWidth - 1 + (heroAnimation.destination.Kirigami.ScenePosition.x * 2) / applicationWindow().width
+        property real targetY: 1-scaleHeight - (heroAnimation.destination.Kirigami.ScenePosition.y * 2) / applicationWindow().height
+        property real scaleWidth: heroAnimation.destination.width/heroAnimation.source.width
+        property real scaleHeight: heroAnimation.destination.height/heroAnimation.source.height
+        vertexShader: __privateShaderSources.vertexShader
+        fragmentShader: `
+varying highp vec2 qt_TexCoord0;
+uniform sampler2D source;
+uniform lowp float qt_Opacity;
+uniform lowp float progress;
+void main() {
+    gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity * (1.0 - progress);
+}
+        `
+    }
+
+    ShaderEffect {
+        id: destinationEffect
+        x: 0
+        y: 0
+        parent: heroAnimation.destination.QQC2.Overlay.overlay
+        width: heroAnimation.destination.width
+        height: heroAnimation.destination.height
+        visible: false
+        property variant source: heroAnimation.destination
+        property real progress: sourceEffect.progress
+        property real startX: heroAnimation.destination.Kirigami.ScenePosition.x / (applicationWindow().width / 2)
+        property real startY: -heroAnimation.destination.Kirigami.ScenePosition.y / (applicationWindow().height / 2)
+
+        property real targetX: scaleWidth - 1 + (heroAnimation.source.Kirigami.ScenePosition.x * 2) / applicationWindow().width
+        property real targetY: 1-scaleHeight - (heroAnimation.source.Kirigami.ScenePosition.y * 2) / applicationWindow().height
+        property real scaleWidth: heroAnimation.source.width/heroAnimation.destination.width
+        property real scaleHeight: heroAnimation.source.height/heroAnimation.destination.height
+
+        property variant maskSource: root.mask.item
+
+        vertexShader: __privateShaderSources.vertexShader
+        fragmentShader: `
+varying highp vec2 qt_TexCoord0;
+uniform sampler2D source;
+uniform sampler2D maskSource;
+uniform lowp float qt_Opacity;
+uniform lowp float progress;
+void main() {
+    gl_FragColor = texture2D(source, qt_TexCoord0) * texture2D(maskSource, qt_TexCoord0).a * qt_Opacity *  (1.0 - progress);
+}
+        `
+    }
+}
diff --git a/src/controls/InlineMessage.qml b/src/controls/InlineMessage.qml
new file mode 100644 (file)
index 0000000..d317b73
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import org.kde.kirigami 2.5 as Kirigami
+import "templates" as T
+
+/**
+ * An inline message item with support for informational, positive,
+ * warning and error types, and with support for associated actions.
+ *
+ * InlineMessage can be used to give information to the user or
+ * interact with the user, without requiring the use of a dialog.
+ *
+ * The InlineMessage item is hidden by default. It also manages its
+ * height (and implicitHeight) during an animated reveal when shown.
+ * You should avoid setting height on an InlineMessage unless it is
+ * already visible.
+ *
+ * Optionally an icon can be set, defaulting to an icon appropriate
+ * to the message type otherwise.
+ *
+ * Optionally a close button can be shown.
+ *
+ * Actions are added from left to right. If more actions are set than
+ * can fit, an overflow menu is provided.
+ *
+ * Example usage:
+ * @code{.qml}
+ * InlineMessage {
+ *     type: Kirigami.MessageType.Error
+ *
+ *     text: "My error message"
+ *
+ *     actions: [
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         },
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         }
+ *     ]
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/components-inlinemessages">Inline Messages in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/inline">KDE Human Interface Guidelines on Inline Messages</a>
+ * @since KDE Frameworks 5.45
+ * @inherit kirigami::templates::InlineMessage
+ */
+T.InlineMessage {
+    id: root
+
+    // a rectangle padded with anchors.margins is used to simulate a border
+    padding: bgFillRect.anchors.margins + Kirigami.Units.smallSpacing
+
+    background: Rectangle {
+        id: bgBorderRect
+
+        color: switch (root.type) {
+            case Kirigami.MessageType.Positive: return Kirigami.Theme.positiveTextColor;
+            case Kirigami.MessageType.Warning: return Kirigami.Theme.neutralTextColor;
+            case Kirigami.MessageType.Error: return Kirigami.Theme.negativeTextColor;
+            default: return Kirigami.Theme.activeTextColor;
+        }
+
+        radius: Kirigami.Units.smallSpacing / 2
+
+        Rectangle {
+            id: bgFillRect
+
+            anchors.fill: parent
+            anchors.margins: 1
+
+            color: Kirigami.Theme.backgroundColor
+
+            radius: bgBorderRect.radius * 0.60
+        }
+
+        Rectangle {
+            anchors.fill: bgFillRect
+
+            color: bgBorderRect.color
+
+            opacity: 0.20
+
+            radius: bgFillRect.radius
+        }
+    }
+}
diff --git a/src/controls/ItemViewHeader.qml b/src/controls/ItemViewHeader.qml
new file mode 100644 (file)
index 0000000..563a2d2
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Templates 2.0 as T2
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.4 as Kirigami
+import "private" as P
+
+/**
+ * An item that can be used as an header for a ListView.
+ * It will play nice with the margin policies of ScrollablePage and can
+ * automatically shrink when the list is scrolled, like the behavior
+ * of list headers in many mobile applications.
+ * It provides some default content: a title and an optional background image
+ * @since org.kde.kirigami 2.1
+ * @inherit kirigami::AbstractItemViewHeader
+ * @deprecated This will be removed in KF6.
+ *
+ * TODO KF6 remove
+ */
+Kirigami.AbstractItemViewHeader {
+    id: root
+    property alias title: heading.text
+    property alias color: heading.color
+
+    property alias backgroundImage: image
+
+    Component.onCompleted: console.warn( "ItemViewHeader is deprecated (since 5.97): No replacemant is available.", (new Error).stack)
+
+    maximumHeight: (backgroundImage.hasImage ? 10 : 6) * Kirigami.Units.gridUnit - (applicationWindow().header ? applicationWindow().header.height : 0) - bottomPadding
+    bottomPadding: Kirigami.Units.smallSpacing
+    leftPadding: Kirigami.Units.smallSpacing
+
+    background: Rectangle {
+        id: backgroundItem
+        color: Kirigami.Theme.backgroundColor
+        Image {
+            id: image
+            anchors.fill: parent
+            readonly property bool hasImage: backgroundImage.status === Image.Ready || backgroundImage.status === Image.Loading
+            fillMode: Image.PreserveAspectCrop
+            asynchronous: true
+        }
+        P.EdgeShadow {
+            edge: root.view.headerPositioning === ListView.InlineHeader ? Qt.BottomEdge : Qt.TopEdge
+            anchors {
+                right: parent.right
+                left: parent.left
+                top: root.view.headerPositioning === ListView.InlineHeader ? undefined : parent.bottom
+                bottom: root.view.headerPositioning === ListView.InlineHeader ? parent.top : undefined
+            }
+        }
+
+        readonly property Page page: {
+            let obj = root.view;
+            while(obj && !obj.hasOwnProperty("title") && !obj.hasOwnProperty("isCurrentPage")) {
+                obj = obj.parent
+            }
+            return obj;
+        }
+        Rectangle {
+            id: rect
+            color: backgroundItem.page && backgroundItem.page.isCurrentPage ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
+            height: root.bottomPadding
+            anchors {
+                left: parent.left
+                right: parent.right
+                bottom: parent.bottom
+            }
+        }
+    }
+
+    contentItem: Item {
+        Kirigami.Heading {
+            id: heading
+            anchors {
+                fill: parent
+                margins:  Kirigami.Units.smallSpacing
+            }
+
+            height: undefined
+            text: page.title
+            fontSizeMode: Text.Fit
+            minimumPointSize: 10
+            font.pointSize: 30
+            horizontalAlignment: Text.AlignRight
+            verticalAlignment: Text.AlignBottom
+            //with an image it needs to be white regardless of system palette
+            color: root.backgroundImage.hasImage ? "white" : Kirigami.Theme.highlightColor
+            opacity: 1
+            elide: Text.ElideRight
+
+            layer.enabled: root.backgroundImage.hasImage
+            layer.effect: GE.DropShadow {
+                horizontalOffset: 0
+                verticalOffset: 2
+                radius: Kirigami.Units.smallSpacing*2
+                samples: 32
+                color: Qt.rgba(0, 0, 0, 0.7)
+            }
+        }
+    }
+}
diff --git a/src/controls/Label.qml b/src/controls/Label.qml
new file mode 100644 (file)
index 0000000..26bd200
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.4 as Kirigami
+import QtQuick.Controls 2.0 as QQC2
+
+// TODO KF6: Remove!
+/**
+ * This is a label which uses the current Theme.
+ *
+ * The characteristics of the text will be automatically set according to the
+ * current Theme. If you need a more customized text item use the Text component
+ * from QtQuick.
+ *
+ * You can use all elements of the QML Text component, in particular the "text"
+ * property to define the label text.
+ *
+ * @inherit QtQuick.Templates.Label
+ * @deprecated This will be removed in KF6. Use QtQuick.Controls.Label directly, it will be styled appropriately.
+ */
+QQC2.Label {
+    height: Math.round(Math.max(paintedHeight, Kirigami.Units.gridUnit * 1.6))
+    verticalAlignment: lineCount > 1 ? Text.AlignTop : Text.AlignVCenter
+
+    activeFocusOnTab: false
+
+    Component.onCompleted: {
+        console.warn("Kirigami.Label is deprecated. Use QtQuickControls2.Label instead")
+    }
+}
diff --git a/src/controls/LinkButton.qml b/src/controls/LinkButton.qml
new file mode 100644 (file)
index 0000000..a3422fa
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.2
+import QtQuick.Controls 2.1 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+
+/**
+ * @brief A button that looks like a link.
+ *
+ * It uses the link color settings and triggers an action when clicked.
+ *
+ * Maps to the Command Link in the HIG:
+ * https://develop.kde.org/hig/components/navigation/commandlink/
+ *
+ * @since KDE Frameworks 5.52
+ * @since org.kde.kirigami 2.6
+ * @inherit QtQuick.Controls.Label
+ */
+QQC2.Label {
+    id: control
+
+    property Action action: null
+
+    /**
+     * @brief This property holds the mouse buttons that the mouse area reacts to.
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-mousearea.html#acceptedButtons-prop">MouseArea.acceptedButtons</a>
+     * @property Qt::MouseButton acceptedButtons
+     */
+    property alias acceptedButtons: area.acceptedButtons
+
+    /**
+     * @brief This property holds the mouse area element covering the button.
+     * @property MouseArea area
+     */
+    property alias mouseArea: area
+
+    activeFocusOnTab: true
+    Accessible.role: Accessible.Button
+    Accessible.name: text
+    Accessible.onPressAction: control.clicked({"button": Qt.LeftButton})
+
+    text: action ? action.text : ""
+    enabled: !action || action.enabled
+    onClicked: mouse => {
+        if (action) {
+            action.trigger();
+        }
+    }
+
+    font.bold: activeFocus
+    font.underline: control.enabled
+    color: enabled ? Kirigami.Theme.linkColor : Kirigami.Theme.textColor
+    horizontalAlignment: Text.AlignHCenter
+    verticalAlignment: Text.AlignVCenter
+    elide: Text.ElideRight
+
+    signal pressed(var mouse)
+    signal clicked(var mouse)
+
+    Keys.onPressed: event => {
+        switch (event.key) {
+        case Qt.Key_Space:
+        case Qt.Key_Enter:
+        case Qt.Key_Return:
+        case Qt.Key_Select:
+            control.clicked({"button": Qt.LeftButton});
+            event.accepted = true;
+            break;
+        case Qt.Key_Menu:
+            control.pressed({"button": Qt.RightButton});
+            event.accepted = true;
+            break;
+        }
+    }
+
+    MouseArea {
+        id: area
+        anchors.fill: parent
+        hoverEnabled: true
+        cursorShape: Qt.PointingHandCursor
+
+        onClicked: mouse => control.clicked(mouse)
+        onPressed: mouse => control.pressed(mouse)
+    }
+}
diff --git a/src/controls/ListItemDragHandle.qml b/src/controls/ListItemDragHandle.qml
new file mode 100644 (file)
index 0000000..767228f
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * Implements a drag handle supposed to be in items in ListViews to reorder items.
+ * 
+ * The QtQuick.ListView must use a model that supports item reordering, such as
+ * QtQml.Models.ListModel.move or QAbstractItemModel instances
+ * with QAbstractItemModel::moveRows correctly implemented.
+ * In order for ListItemDragHandle to work correctly, the list item that is being dragged
+ * should not directly be the delegate of the ListView, but a child of it.
+ *
+ * Example usage:
+ * @include listitemdraghandle.qml
+ *
+ * As seen from the example, we wrapped the AbstractListItem with an
+ * Item component. This is because when dragging the list item around, only the item that
+ * the drag handle is assigned to is moved, and the wrapper Item stays there for
+ * it to take up space so that other list items don't take it.
+ *
+ * @since org.kde.kirigami 2.5
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: root
+
+    /**
+     * @brief This property holds the delegate that will be dragged around.
+     *
+     * This item *must* be a child of the actual ListView's delegate.
+     */
+    property Item listItem
+
+    /**
+     * @brief This property holds the ListView that the delegate belong to.
+     */
+    property ListView listView
+
+    /**
+     * @brief This signal is emitted when the drag handle wants to move the item in the model.
+     *
+     * The following example does the move in the case a ListModel is used:
+     * @code{.qml}
+     *  onMoveRequested: listModel.move(oldIndex, newIndex, 1)
+     * @endcode
+     * @param oldIndex the index the item is currently at
+     * @param newIndex the index we want to move the item to
+     */
+    signal moveRequested(int oldIndex, int newIndex)
+
+    /**
+     * @brief This signal is emitted when the drag operation is complete and the item has been
+     * dropped in the new final position.
+     */
+    signal dropped()
+
+    implicitWidth: Kirigami.Units.iconSizes.smallMedium
+    implicitHeight: implicitWidth
+
+    MouseArea {
+        id: mouseArea
+        anchors.fill: parent
+        drag {
+            target: listItem
+            axis: Drag.YAxis
+            minimumY: 0
+        }
+        cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
+
+        Kirigami.Icon {
+            id: internal
+            source: "handle-sort"
+            property int startY
+            property int mouseDownY
+            property Item originalParent
+            opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
+            property int listItemLastY
+            property bool draggingUp
+
+            function arrangeItem() {
+                const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(mouseArea, 0, internal.mouseDownY).y);
+
+                if (newIndex > -1 && ((internal.draggingUp && newIndex < index) || (!internal.draggingUp && newIndex > index))) {
+                    root.moveRequested(index, newIndex);
+                }
+            }
+
+            anchors.fill: parent
+        }
+        preventStealing: true
+
+
+        onPressed: mouse => {
+            internal.originalParent = listItem.parent;
+            listItem.parent = listView;
+            listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
+            internal.originalParent.z = 99;
+            internal.startY = listItem.y;
+            internal.listItemLastY = listItem.y;
+            internal.mouseDownY = mouse.y;
+            // while dragging listItem's height could change
+            // we want a const maximumY during the dragging time
+            mouseArea.drag.maximumY = listView.height - listItem.height;
+        }
+
+        onPositionChanged: mouse => {
+            if (!pressed || listItem.y === internal.listItemLastY) {
+                return;
+            }
+
+            internal.draggingUp = listItem.y < internal.listItemLastY
+            internal.listItemLastY = listItem.y;
+
+            internal.arrangeItem();
+
+             // autoscroll when the dragging item reaches the listView's top/bottom boundary
+            scrollTimer.running = (listView.contentHeight > listView.height)
+                               && ( (listItem.y === 0 && !listView.atYBeginning) ||
+                                    (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd) );
+        }
+        onReleased: mouse => {
+            listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
+            listItem.parent = internal.originalParent;
+            dropAnimation.running = true;
+            scrollTimer.running = false;
+            root.dropped();
+        }
+        onCanceled: released()
+        SequentialAnimation {
+            id: dropAnimation
+            YAnimator {
+                target: listItem
+                from: listItem.y
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+            PropertyAction {
+                target: listItem.parent
+                property: "z"
+                value: 0
+            }
+        }
+        Timer {
+            id: scrollTimer
+            interval: 50
+            repeat: true
+            onTriggered: {
+                if (internal.draggingUp) {
+                    listView.contentY -= Kirigami.Units.gridUnit;
+                    if (listView.atYBeginning) {
+                        listView.positionViewAtBeginning();
+                        stop();
+                    }
+                } else {
+                    listView.contentY += Kirigami.Units.gridUnit;
+                    if (listView.atYEnd) {
+                        listView.positionViewAtEnd();
+                        stop();
+                    }
+                }
+                internal.arrangeItem();
+            }
+        }
+    }
+}
+
diff --git a/src/controls/ListSectionHeader.qml b/src/controls/ListSectionHeader.qml
new file mode 100644 (file)
index 0000000..8b65a8b
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Björn Feber <bfeber@protonmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.10 as Kirigami
+
+/**
+ * @brief A section delegate for the primitive ListView component.
+ *
+ * It's intended to make all listviews look coherent.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import QtQuick 2.5
+ * import QtQuick.Controls 2.5 as QQC2
+ *
+ * import org.kde.kirigami 2.10 as Kirigami
+ *
+ * ListView {
+ *  [...]
+ *     section.delegate: Kirigami.ListSectionHeader {
+ *         label: section
+ *
+ *         QQC2.Button {
+ *             text: "Button 1"
+ *         }
+ *         QQC2.Button {
+ *             text: "Button 2"
+ *         }
+ *     }
+ *  [...]
+ * }
+ * @endcode
+ * @inherit kirigami::AbstractListItem
+ */
+Kirigami.AbstractListItem {
+    id: listSection
+
+    /**
+     * @brief This property sets the text of the ListView's section header.
+     * @property string label
+     */
+    property alias label: listSection.text
+
+    /** @internal */
+    default property alias _contents: rowLayout.data
+
+    separatorVisible: false
+    sectionDelegate: true
+    hoverEnabled: false
+
+    activeFocusOnTab: false
+
+    // we do not need a background
+    background: Item {}
+
+    topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
+
+    contentItem: RowLayout {
+        id: rowLayout
+        spacing: Kirigami.Units.largeSpacing
+
+        Kirigami.Heading {
+            Layout.fillWidth: rowLayout.children.length === 1
+            Layout.alignment: Qt.AlignVCenter
+
+            opacity: 0.7
+            level: 5
+            type: Kirigami.Heading.Primary
+            text: listSection.text
+            elide: Text.ElideRight
+
+            // we override the Primary type's font weight (DemiBold) for Bold for contrast with small text
+            font.weight: Font.Bold
+        }
+
+        Kirigami.Separator {
+            Layout.fillWidth: true
+            Layout.alignment: Qt.AlignVCenter
+        }
+    }
+}
diff --git a/src/controls/LoadingPlaceholder.qml b/src/controls/LoadingPlaceholder.qml
new file mode 100644 (file)
index 0000000..06ca0b8
--- /dev/null
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * @brief A placeholder for loading pages.
+ *
+ * Example usage:
+ * @code{.qml}
+ * Kirigami.Page {
+ *     Kirigami.LoadingPlaceholder {
+ *         anchors.centerIn: parent
+ *     }
+ * }
+ * @endcode
+ *
+ * @code{.qml}
+ * Kirigami.Page {
+ *     Kirigami.LoadingPlaceholder {
+ *         anchors.centerIn: parent
+ *         determinate: true
+ *         progressBar.value: loadingValue
+ *     }
+ * }
+ * @endcode
+ * @inherit kirigami::PlaceholderMessage
+ */
+Kirigami.PlaceholderMessage {
+    id: loadingPlaceholder
+
+    /**
+     * @brief This property holds whether the loading message shows a
+     * determinate progress bar or not.
+     *
+     * This should be @c true if you want to display the actual
+     * percentage when it's loading.
+     *
+     * default: ``false``
+     */
+    property bool determinate: false
+
+    /**
+     * @brief This property holds a progress bar.
+     *
+     * This should be used to access the progress bar to change its value.
+     *
+     * @property QtQuick.Controls.ProgressBar _progressBar
+     */
+    property alias progressBar: _progressBar
+
+    text: qsTr("Loading…")
+
+    QQC2.ProgressBar {
+        id: _progressBar
+        Layout.alignment: Qt.AlignHCenter
+        Layout.fillWidth: true
+        Layout.maximumWidth: Kirigami.Units.gridUnit * 20
+        indeterminate: !determinate
+        from: 0
+        to: 100
+    }
+}
diff --git a/src/controls/MenuDialog.qml b/src/controls/MenuDialog.qml
new file mode 100644 (file)
index 0000000..1169db0
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+    SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * A dialog that prompts users with a context menu, with
+ * list items that perform actions.
+ *
+ * Example usage:
+ * @code{.qml}
+ * Kirigami.MenuDialog {
+ *     title: i18n("Track Options")
+ *
+ *     actions: [
+ *         Kirigami.Action {
+ *             iconName: "media-playback-start"
+ *             text: i18nc("Start playback of the selected track", "Play")
+ *             tooltip: i18n("Start playback of the selected track")
+ *         },
+ *         Kirigami.Action {
+ *             enabled: false
+ *             iconName: "document-open-folder"
+ *             text: i18nc("Show the file for this song in the file manager", "Show in folder")
+ *             tooltip: i18n("Show the file for this song in the file manager")
+ *         },
+ *         Kirigami.Action {
+ *             iconName: "documentinfo"
+ *             text: i18nc("Show track metadata", "View details")
+ *             tooltip: i18n("Show track metadata")
+ *         },
+ *         Kirigami.Action {
+ *             iconName: "list-add"
+ *             text: i18nc("Add the track to the queue, right after the current track", "Play next")
+ *             tooltip: i18n("Add the track to the queue, right after the current track")
+ *         },
+ *         Kirigami.Action {
+ *             iconName: "list-add"
+ *             text: i18nc("Enqueue current track", "Add to queue")
+ *             tooltip: i18n("Enqueue current track")
+ *         }
+ *     ]
+ * }
+ * @endcode
+ * @see kirigami::Dialog
+ * @see kirigami::PromptDialog
+ * @see <a href="https://develop.kde.org/hig/components/navigation/dialog">Human Interface Guidelines on Dialogs</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/message">Human Interface Guidelines on Modal Message Dialogs</a>
+ * @inherit kirigami::Dialog
+ */
+Kirigami.Dialog {
+
+    /**
+     * @brief This property holds the actions displayed in the context menu.
+     */
+    property list<QtObject> actions
+
+    /**
+     * @brief This property holds the content header, which appears above the actions.
+     * but below the header bar.
+     */
+    property Item contentHeader
+
+    /**
+     * @brief This property holds the content header.
+     *
+     * This makes it possible to access its internal properties to, for example, change its padding:
+     * ``contentHeaderControl.topPadding``
+     *
+     * @property QtQuick.Controls.Control contentHeaderControl
+     */
+    property alias contentHeaderControl: columnHeader
+
+    preferredWidth: Kirigami.Units.gridUnit * 20
+    padding: 0
+
+    ColumnLayout {
+        id: column
+        spacing: 0
+
+        QQC2.Control {
+            id: columnHeader
+            topPadding: 0
+            bottomPadding: 0
+            leftPadding: 0
+            rightPadding: 0
+            contentItem: contentHeader
+        }
+
+        Repeater {
+            model: actions
+
+            delegate: Kirigami.BasicListItem {
+                Layout.fillWidth: true
+                Layout.preferredHeight: Kirigami.Units.gridUnit * 2
+
+                iconSize: Kirigami.Units.gridUnit
+                leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
+                rightPadding: Kirigami.Units.largeSpacing + + Kirigami.Units.smallSpacing
+
+                icon: modelData.icon.name
+                text: modelData.text
+                onClicked: modelData.trigger(this)
+
+                enabled: modelData.enabled
+
+                visible: modelData.visible
+
+                QQC2.ToolTip.visible: modelData.tooltip !== "" && hoverHandler.hovered
+                QQC2.ToolTip.text: modelData.tooltip
+                HoverHandler { id: hoverHandler }
+            }
+        }
+    }
+}
diff --git a/src/controls/NavigationTabBar.qml b/src/controls/NavigationTabBar.qml
new file mode 100644 (file)
index 0000000..74313be
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2021 Devin Lin <espidev@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Layouts 1.15
+import QtGraphicalEffects 1.12 as GE
+import QtQuick.Templates 2.15 as T
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * @brief Page navigation tab-bar, used as an alternative to sidebars for 3-5 elements.
+ *
+ * Can be combined with secondary toolbars above (if in the footer) to provide page actions.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import QtQuick 2.15
+ * import QtQuick.Controls 2.15
+ * import QtQuick.Layouts 1.15
+ * import org.kde.kirigami 2.20 as Kirigami
+ *
+ * Kirigami.ApplicationWindow {
+ *     title: "Clock"
+ *
+ *     pageStack.initialPage: worldPage
+ *     Kirigami.Page {
+ *         id: worldPage
+ *         title: "World"
+ *         visible: false
+ *     }
+ *     Kirigami.Page {
+ *         id: timersPage
+ *         title: "Timers"
+ *         visible: false
+ *     }
+ *     Kirigami.Page {
+ *         id: stopwatchPage
+ *         title: "Stopwatch"
+ *         visible: false
+ *     }
+ *     Kirigami.Page {
+ *         id: alarmsPage
+ *         title: "Alarms"
+ *         visible: false
+ *     }
+ *
+ *
+ *     footer: Kirigami.NavigationTabBar {
+ *         actions: [
+ *             Kirigami.Action {
+ *                 iconName: "globe"
+ *                 text: "World"
+ *                 checked: worldPage.visible
+ *                 onTriggered: {
+ *                      if (!worldPage.visible) {
+ *                          while (pageStack.depth > 0) {
+ *                              pageStack.pop();
+ *                          }
+ *                          pageStack.push(worldPage);
+ *                     }
+ *                 }
+ *             },
+ *             Kirigami.Action {
+ *                 iconName: "player-time"
+ *                 text: "Timers"
+ *                 checked: timersPage.visible
+ *                 onTriggered: {
+ *                     if (!timersPage.visible) {
+ *                         while (pageStack.depth > 0) {
+ *                             pageStack.pop();
+ *                         }
+ *                         pageStack.push(timersPage);
+ *                     }
+ *                 }
+ *             },
+ *             Kirigami.Action {
+ *                 iconName: "chronometer"
+ *                 text: "Stopwatch"
+ *                 checked: stopwatchPage.visible
+ *                 onTriggered: {
+ *                     if (!stopwatchPage.visible) {
+ *                         while (pageStack.depth > 0) {
+ *                             pageStack.pop();
+ *                         }
+ *                         pageStack.push(stopwatchPage);
+ *                     }
+ *                 }
+ *             },
+ *             Kirigami.Action {
+ *                 iconName: "notifications"
+ *                 text: "Alarms"
+ *                 checked: alarmsPage.visible
+ *                 onTriggered: {
+ *                     if (!alarmsPage.visible) {
+ *                         while (pageStack.depth > 0) {
+ *                             pageStack.pop();
+ *                         }
+ *                         pageStack.push(alarmsPage);
+ *                     }
+ *                 }
+ *             }
+ *         ]
+ *     }
+ * }
+ * @endcode
+ * @see kirigami::NavigationTabButton
+ * @see <a href="https://develop.kde.org/hig/components/navigation/navigationtabbar">Human Interface Guidelines on Navigation Tab Bars</a>
+ * @since KDE Frameworks 5.87
+ * @since org.kde.kirigami 2.19
+ * @inherit QtQuick.Templates.Toolbar
+ */
+
+T.ToolBar {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds the list of actions displayed in the toolbar.
+     */
+    property list<Kirigami.Action> actions
+
+    /**
+     * @brief The property holds the maximum width of the toolbar actions, before margins are added.
+     */
+    property real maximumContentWidth: {
+        const minDelegateWidth = Kirigami.Units.gridUnit * 5;
+        // Always have at least the width of 5 items, so that small amounts of actions look natural.
+        return minDelegateWidth * Math.max(actions.length, 5);
+    }
+
+    /**
+     * @brief This property holds the background color of the toolbar.
+     *
+     * default: @link Kirigami.PlatformTheme.highlightColor Kirigami.Theme.highlightColor @endlink
+     */
+    property color backgroundColor: Kirigami.Theme.backgroundColor
+
+    /**
+     * @brief This property holds the foreground color of the toolbar (text, icon).
+     */
+    property color foregroundColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.85)
+
+    /**
+     * @brief This property holds the highlight foreground color (text, icon when action is checked).
+     */
+    property color highlightForegroundColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.85)
+
+    /**
+     * @brief This property holds the color of the highlight bar when an action is checked.
+     *
+     * default: @link Kirigami.PlatformTheme.highlightColor Kirigami.Theme.highlightColor @endlink
+     */
+    property color highlightBarColor: Kirigami.Theme.highlightColor
+
+    /**
+     * @brief This property sets whether the toolbar should provide its own shadow.
+     *
+     * default: ``true``
+     */
+    property bool shadow: true
+
+    /**
+     * @brief This property holds the index of currently checked tab.
+     *
+     * If the index set is out of bounds, or the triggered signal did not change any checked property of an action, the index
+     * will remain the same.
+     */
+    property int currentIndex: tabGroup.checkedButton && tabGroup.buttons.length > 0 ? tabGroup.checkedButton.tabIndex : -1
+
+    /**
+     * @brief This property holds the number of tab buttons.
+     */
+    readonly property int count: tabGroup.buttons.length
+
+    /**
+     * @brief This property holds the ButtonGroup used to manage the tabs.
+     */
+    readonly property T.ButtonGroup tabGroup: tabGroup
+
+    /**
+     * @brief This property sets whether the icon colors should be masked with a single color.
+     *
+     * This only applies to buttons generated by the actions property.
+     *
+     * default: ``true``
+     *
+     * @since KDE Frameworks 5.96
+     */
+    property bool recolorIcons: true
+
+    /**
+     * @brief This property holds the calculated width that buttons on the tab bar use.
+     *
+     * @since KDE Frameworks 5.102
+     */
+    property real buttonWidth: {
+        // Counting buttons because Repeaters can be counted among visibleChildren
+        let visibleButtonCount = 0;
+        const minWidth = contentItem.height * 0.75;
+        for (let i = 0; i < contentItem.visibleChildren.length; ++i) {
+            if (contentItem.width / visibleButtonCount >= minWidth && // make buttons go off the screen if there is physically no room for them
+                contentItem.visibleChildren[i] instanceof T.AbstractButton) { // Checking for AbstractButtons because any AbstractButton can act as a tab
+                ++visibleButtonCount;
+            }
+        }
+
+        return Math.round(contentItem.width / visibleButtonCount);
+    }
+//END properties
+
+    onCurrentIndexChanged: {
+        if (currentIndex === -1) {
+            if (tabGroup.checkState !== Qt.Unchecked) {
+                tabGroup.checkState = Qt.Unchecked;
+            }
+            return;
+        }
+        if (tabGroup.checkedButton.tabIndex !== currentIndex) {
+            const buttonForCurrentIndex = tabGroup.buttons[currentIndex]
+            if (buttonForCurrentIndex.action) {
+                // trigger also toggles and causes clicked() to be emitted
+                buttonForCurrentIndex.action.trigger();
+            } else {
+                // toggle() does not trigger the action,
+                // so don't use it if you want to use an action.
+                // It also doesn't cause clicked() to be emitted.
+                buttonForCurrentIndex.toggle();
+            }
+        }
+    }
+
+    // Using Math.round() on horizontalPadding can cause the contentItem to jitter left and right when resizing the window.
+    horizontalPadding: Math.floor(Math.max(0, width - root.maximumContentWidth) / 2)
+    contentWidth: Math.ceil(Math.min(root.availableWidth, root.maximumContentWidth))
+    implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
+    implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
+
+    Kirigami.Theme.colorSet: Kirigami.Theme.Window
+
+    background: Rectangle { // color & shadow
+        implicitHeight: Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2
+        color: root.backgroundColor
+        GE.RectangularGlow {
+            anchors.fill: parent
+            z: -1
+            visible: root.shadow
+            glowRadius: 5
+            spread: 0.3
+            color: Qt.rgba(0.0, 0.0, 0.0, 0.15)
+        }
+    }
+
+    // Using Row because setting just width is more convenient than having to set Layout.minimumWidth and Layout.maximumWidth
+    contentItem: Row {
+        id: rowLayout
+        spacing: root.spacing
+    }
+
+    // Used to manage which tab is checked and change the currentIndex
+    T.ButtonGroup {
+        id: tabGroup
+        exclusive: true
+        buttons: root.contentItem.children
+
+        onCheckedButtonChanged: {
+            if (!checkedButton) {
+                return
+            }
+            if (root.currentIndex !== checkedButton.tabIndex) {
+                root.currentIndex = checkedButton.tabIndex;
+            }
+        }
+    }
+
+    // Using an Instantiator instead of a Repeater allows us to use parent.visibleChildren.length without including a Repeater in that count.
+    Instantiator {
+        id: instantiator
+        model: root.actions
+        delegate: NavigationTabButton {
+            id: delegate
+            parent: root.contentItem
+            action: modelData
+            visible: modelData.visible
+            width: root.buttonWidth
+            recolorIcon: root.recolorIcons
+            T.ButtonGroup.group: tabGroup
+            // Workaround setting the action when checkable is not explicitly set making tabs uncheckable
+            onActionChanged: action.checkable = true
+
+            foregroundColor: root.foregroundColor
+            highlightForegroundColor: root.highlightForegroundColor
+            highlightBarColor: root.highlightBarColor
+        }
+    }
+}
diff --git a/src/controls/NavigationTabButton.qml b/src/controls/NavigationTabButton.qml
new file mode 100644 (file)
index 0000000..3965e57
--- /dev/null
@@ -0,0 +1,253 @@
+/* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
+ * SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Templates 2.15 as T
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * @brief Navigation buttons to be used for the NavigationTabBar component.
+ *
+ * It supplies its own padding, and also supports using
+ * <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-abstractbutton.html#display-prop">AbstractButton.display</a>
+ * to be used in column lists.
+ *
+ * Alternative way to the "actions" property on NavigationTabBar, as it can be used
+ * with a QtQuick.Repeater to generate buttons from models.
+ *
+ * Example usage:
+ * @code{.qml}
+ * Kirigami.NavigationTabBar {
+ *      id: navTabBar
+ *      Kirigami.NavigationTabButton {
+ *          visible: true
+ *          icon.name: "document-save"
+ *          text: `test ${tabIndex + 1}`
+ *          QQC2.ButtonGroup.group: navTabBar.tabGroup
+ *      }
+ *      Kirigami.NavigationTabButton {
+ *          visible: false
+ *          icon.name: "document-send"
+ *          text: `test ${tabIndex + 1}`
+ *          QQC2.ButtonGroup.group: navTabBar.tabGroup
+ *      }
+ *      actions: [
+ *          Kirigami.Action {
+ *              visible: true
+ *              icon.name: "edit-copy"
+ *              icon.height: 32
+ *              icon.width: 32
+ *              text: `test 3`
+ *              checked: true
+ *          },
+ *          Kirigami.Action {
+ *              visible: true
+ *              icon.name: "edit-cut"
+ *              text: `test 4`
+ *              checkable: true
+ *          },
+ *          Kirigami.Action {
+ *              visible: false
+ *              icon.name: "edit-paste"
+ *              text: `test 5`
+ *          },
+ *          Kirigami.Action {
+ *              visible: true
+ *              icon.source: "../logo.png"
+ *              text: `test 6`
+ *              checkable: true
+ *          }
+ *      ]
+ *  }
+ * @endcode
+ * @since KDE Frameworks 5.87
+ * @since org.kde.kirigami 2.19
+ * @inherit QtQuick.Templates.TabButton
+ */
+T.TabButton {
+    id: control
+
+    /**
+     * @brief This property specifies the index of this tab within the tab bar.
+     */
+    readonly property int tabIndex: {
+        let tabIdx = 0
+        for (let i = 0; i < parent.children.length; ++i) {
+            if (parent.children[i] === this) {
+                return tabIdx
+            }
+            // Checking for AbstractButtons because any AbstractButton can act as a tab
+            if (parent.children[i] instanceof T.AbstractButton) {
+                ++tabIdx
+            }
+        }
+        return -1
+    }
+
+    /**
+     * @brief This property sets whether the icon colors should be masked with a single color.
+     *
+     * default: ``true``
+     *
+     * @since KDE Frameworks 5.96
+     */
+    property bool recolorIcon: true
+
+    property color foregroundColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.85)
+    property color highlightForegroundColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.85)
+    property color highlightBarColor: Kirigami.Theme.highlightColor
+
+    property color pressedColor: Qt.rgba(highlightBarColor.r, highlightBarColor.g, highlightBarColor.b, 0.3)
+    property color hoverSelectColor: Qt.rgba(highlightBarColor.r, highlightBarColor.g, highlightBarColor.b, 0.2)
+    property color checkedBorderColor: Qt.rgba(highlightBarColor.r, highlightBarColor.g, highlightBarColor.b, 0.7)
+    property color pressedBorderColor: Qt.rgba(highlightBarColor.r, highlightBarColor.g, highlightBarColor.b, 0.9)
+
+    implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+                            implicitContentWidth + leftPadding + rightPadding)
+    implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+                             implicitContentHeight + topPadding + bottomPadding)
+
+    display: T.AbstractButton.TextUnderIcon
+
+    Kirigami.Theme.colorSet: Kirigami.Theme.Window
+    Kirigami.Theme.inherit: false
+
+    // not using the hover handler built into control, since it seems to misbehave and
+    // permanently report hovered after a touch event
+    HoverHandler {
+        id: hoverHandler
+    }
+
+    padding: Kirigami.Units.smallSpacing
+    spacing: Kirigami.Units.smallSpacing
+
+    icon.height: control.display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
+    icon.width: control.display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
+    icon.color: control.checked ? control.highlightForegroundColor : control.foregroundColor
+
+    background: Rectangle {
+        Kirigami.Theme.colorSet: Kirigami.Theme.Button
+        Kirigami.Theme.inherit: false
+
+        implicitHeight: (control.display === T.AbstractButton.TextBesideIcon) ? 0 : (Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2)
+
+        color: "transparent"
+
+        Rectangle {
+            width: parent.width - Kirigami.Units.largeSpacing
+            height: parent.height - Kirigami.Units.largeSpacing
+            anchors.centerIn: parent
+
+            radius: Kirigami.Units.smallSpacing
+            color: control.down ? pressedColor : (control.checked || hoverHandler.hovered ? hoverSelectColor : "transparent")
+
+            border.color: control.checked ? checkedBorderColor : (control.down ? pressedBorderColor : color)
+            border.width: 1
+
+            Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
+            Behavior on border.color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
+        }
+    }
+
+    contentItem: GridLayout {
+        id: gridLayout
+        columnSpacing: 0
+        rowSpacing: (label.visible && label.lineCount > 1) ? 0 : control.spacing
+
+        // if this is a row or a column
+        columns: control.display !== T.AbstractButton.TextBesideIcon ? 1 : 2
+
+        property real verticalMargins: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.largeSpacing : 0
+
+        Kirigami.Icon {
+            id: icon
+            source: control.icon.name || control.icon.source
+            isMask: control.recolorIcon
+            visible: control.icon.name !== '' && control.icon.source !== '' && control.display !== T.AbstractButton.TextOnly
+            color: control.icon.color
+
+            Layout.topMargin: gridLayout.verticalMargins
+            Layout.bottomMargin: gridLayout.verticalMargins
+            Layout.leftMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
+            Layout.rightMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
+
+            Layout.alignment: {
+                if (control.display === T.AbstractButton.TextBesideIcon) {
+                    // row layout
+                    return Qt.AlignVCenter | Qt.AlignRight;
+                } else {
+                    // column layout
+                    return Qt.AlignHCenter | ((!label.visible || label.lineCount > 1) ? Qt.AlignVCenter : Qt.AlignBottom);
+                }
+            }
+            implicitHeight: source ? control.icon.height : 0
+            implicitWidth: source ? control.icon.width : 0
+
+            Behavior on color { ColorAnimation {} }
+            Behavior on opacity { NumberAnimation {} }
+        }
+        QQC2.Label {
+            id: label
+            Kirigami.MnemonicData.enabled: control.enabled && control.visible
+            Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
+            Kirigami.MnemonicData.label: control.text
+
+            text: Kirigami.MnemonicData.richTextLabel
+            horizontalAlignment: (control.display === T.AbstractButton.TextBesideIcon) ? Text.AlignLeft : Text.AlignHCenter
+
+            visible: control.display !== T.AbstractButton.IconOnly
+            wrapMode: Text.Wrap
+            elide: Text.ElideMiddle
+            color: control.checked ? control.highlightForegroundColor : control.foregroundColor
+
+            font.bold: control.checked
+            font.family: Kirigami.Theme.smallFont.family
+            font.pointSize: {
+                if (control.display === T.AbstractButton.TextBesideIcon) {
+                    // row layout
+                    return Kirigami.Theme.defaultFont.pointSize;
+                } else {
+                    // column layout
+                    return icon.visible ? Kirigami.Theme.smallFont.pointSize : Kirigami.Theme.defaultFont.pointSize * 1.20; // 1.20 is equivalent to level 2 heading
+                }
+            }
+
+            Behavior on color { ColorAnimation {} }
+            Behavior on opacity { NumberAnimation {} }
+
+            Layout.topMargin: gridLayout.verticalMargins
+            Layout.bottomMargin: gridLayout.verticalMargins
+
+            Layout.alignment: {
+                if (control.display === T.AbstractButton.TextBesideIcon) {
+                    // row layout
+                    return Qt.AlignVCenter | Qt.AlignLeft;
+                } else {
+                    // column layout
+                    return icon.visible ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter;
+                }
+            }
+
+            // Work around bold text changing implicit size
+            Layout.preferredWidth: boldMetrics.implicitWidth
+            Layout.preferredHeight: boldMetrics.implicitHeight * label.lineCount
+            Layout.fillWidth: true
+
+            QQC2.Label {
+                id: boldMetrics
+                visible: false
+                text: parent.text
+                font.bold: true
+                font.family: Kirigami.Theme.smallFont.family
+                font.pointSize: Kirigami.Theme.smallFont.pointSize
+                horizontalAlignment: Text.AlignHCenter
+                wrapMode: QQC2.Label.Wrap
+                elide: Text.ElideMiddle
+            }
+        }
+    }
+}
diff --git a/src/controls/OverlayDrawer.qml b/src/controls/OverlayDrawer.qml
new file mode 100644 (file)
index 0000000..bee7b4e
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Templates 2.0 as T2
+import org.kde.kirigami 2.15 as Kirigami
+import "private" as P
+import "templates" as T
+
+/**
+ * @brief Overlay drawers are used to expose additional UI elements needed for
+ * small secondary tasks for which the main UI elements are not needed.
+ *
+ * Overlay drawers can be used to create two kinds of components, a modal drawer
+ * and an inline drawer. A modal drawer darkens the rest of the application and
+ * grabs focus until confirmed, whereas an inline drawer does not.
+ *
+ * Unlike an OverlaySheet that appears in the center of the application, an OverlayDrawer
+ * can be attached to an edge of the application, usually the top or the bottom edges.
+ *
+ * @see Visit https://develop.kde.org/docs/getting-started/kirigami/components-drawers to read more
+ * about modal and non-modal drawers.
+ *
+ * Example usage:
+ * @include overlaydrawer.qml
+ *
+ * @inherit kirigami::templates::OverlayDrawer
+ */
+T.OverlayDrawer {
+    id: root
+
+//BEGIN Properties
+    focus: false
+    modal: true
+    drawerOpen: !modal
+    closePolicy: modal ? T2.Popup.CloseOnEscape | T2.Popup.CloseOnReleaseOutside : T2.Popup.NoAutoClose
+    handleVisible: interactive && (modal || !drawerOpen) && (typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true)
+
+    // FIXME: set to false when it does not lead to blocking closePolicy.
+    // See Kirigami bug: 454119
+    interactive: true
+
+    onPositionChanged: {
+        if (!modal && !root.peeking && !root.animating) {
+            position = 1;
+        }
+    }
+
+    background: Rectangle {
+        color: Kirigami.Theme.backgroundColor
+
+        Item {
+            parent: root.handle
+            anchors.fill: parent
+
+            Kirigami.ShadowedRectangle {
+                id: handleGraphics
+                anchors.centerIn: parent
+
+                Kirigami.Theme.colorSet: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.colorSet : Kirigami.Theme.Button
+
+                Kirigami.Theme.backgroundColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.backgroundColor : undefined
+
+                Kirigami.Theme.textColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.textColor : undefined
+
+                Kirigami.Theme.inherit: false
+                color: root.handle.pressed ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
+
+                visible: !parent.parent.handleAnchor || !parent.parent.handleAnchor.visible || root.handle.pressed || (root.modal && root.position > 0)
+
+                shadow.color: Qt.rgba(0, 0, 0, root.handle.pressed ? 0.6 : 0.4)
+                shadow.yOffset: 1
+                shadow.size: Kirigami.Units.gridUnit / 2
+
+                width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
+                height: width
+                radius: 2
+                Behavior on color {
+                    ColorAnimation {
+                        duration: Kirigami.Units.longDuration
+                        easing.type: Easing.InOutQuad
+                    }
+                }
+            }
+            Loader {
+                anchors.centerIn: handleGraphics
+                width: height
+                height: Kirigami.Units.iconSizes.smallMedium
+
+                Kirigami.Theme.colorSet: handleGraphics.Kirigami.Theme.colorSet
+                Kirigami.Theme.backgroundColor: handleGraphics.Kirigami.Theme.backgroundColor
+                Kirigami.Theme.textColor: handleGraphics.Kirigami.Theme.textColor
+
+                asynchronous: true
+
+                source: {
+                    let edge = root.edge;
+                    if (Qt.application.layoutDirection === Qt.RightToLeft) {
+                        if (edge === Qt.LeftEdge) {
+                            edge = Qt.RightEdge;
+                        } else {
+                            edge = Qt.LeftEdge;
+                        }
+                    }
+
+                    if ((root.handleClosedIcon.source || root.handleClosedIcon.name)
+                        && (root.handleOpenIcon.source || root.handleOpenIcon.name)) {
+                        return Qt.resolvedUrl("templates/private/GenericDrawerIcon.qml");
+                    } else if (edge === Qt.LeftEdge ) {
+                        return Qt.resolvedUrl("templates/private/MenuIcon.qml");
+                    } else if(edge === Qt.RightEdge && root.hasOwnProperty("actions")) {
+                        return Qt.resolvedUrl("templates/private/ContextIcon.qml");
+                    } else {
+                        return "";
+                    }
+                }
+                onItemChanged: {
+                    if (item) {
+                        item.drawer = Qt.binding(function(){return root});
+                        item.color = Qt.binding(function(){return root.handle.pressed ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor});
+                    }
+                }
+            }
+        }
+
+        Kirigami.Separator {
+            LayoutMirroring.enabled: false
+            // LayoutMirroring.childrenInherit: true
+            anchors {
+                right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
+                left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
+                top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
+                bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
+            }
+            visible: !root.modal
+        }
+        P.EdgeShadow {
+            z: -2
+            visible: root.modal
+            edge: root.edge
+            anchors {
+                right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
+                left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
+                top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
+                bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
+            }
+
+            opacity: root.position === 0 ? 0 : 1
+
+            Behavior on opacity {
+                NumberAnimation {
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/OverlaySheet.qml b/src/controls/OverlaySheet.qml
new file mode 100644 (file)
index 0000000..1b8f23f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import org.kde.kirigami 2.12 as Kirigami
+import "private" as P
+import "templates" as T
+
+/**
+ * @brief An overlay sheet that covers the current Page content.
+ *
+ * Its contents can be scrolled up or down, scrolling all the way up or
+ * all the way down, dismisses it.
+ * Use this for big, modal dialogs or information display, that can't be
+ * logically done as a new separate Page, even if potentially
+ * are taller than the screen space.
+ * @inherit kirigami::templates::OverlaySheet
+ */
+T.OverlaySheet {
+    id: root
+
+    leftInset: 0
+    topInset: 0
+    rightInset: 0
+    bottomInset: 0
+
+    background: P.DefaultCardBackground {}
+}
diff --git a/src/controls/Page.qml b/src/controls/Page.qml
new file mode 100644 (file)
index 0000000..b767e16
--- /dev/null
@@ -0,0 +1,489 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Layouts 1.2
+import QtQuick.Templates 2.1 as T2
+import QtQuick.Controls 2.1 as QQC2
+import org.kde.kirigami 2.10 as Kirigami
+import "private" as P
+
+/**
+ * Page is a container for all the app pages: everything pushed to the
+ * @link ApplicationWindow::pageStack pageStack @endlink should be a Page.
+ *
+ * For content that should be scrollable, such as QtQuick.ListView, use ScrollablePage instead.
+ * @inherit QtQuick.Controls.Page
+ */
+QQC2.Page {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief If the central element of the page is a Flickable
+     * (ListView and Gridview as well) you can set it there.
+     *
+     * Normally, you wouldn't need to do that, but just use the
+     * ScrollablePage element instead.
+     *
+     * Use this if your flickable has some non standard properties,
+     * such as not covering the whole Page.
+     *
+     * @see kirigami::ScrollablePage
+     */
+    property Flickable flickable
+
+    /**
+     * @brief Defines the contextual actions for the page:
+     * an easy way to assign actions in the right sliding panel
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.ApplicationWindow {
+     *  [...]
+     *     contextDrawer: Kirigami.ContextDrawer {
+     *         id: contextDrawer
+     *     }
+     *  [...]
+     * }
+     * @endcode
+     *
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     *
+     * Kirigami.Page {
+     *   [...]
+     *     actions.contextualActions: [
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         },
+     *         Kirigami.Action {
+     *             icon.name: "edit"
+     *             text: "Action text"
+     *             onTriggered: {
+     *                 // do stuff
+     *             }
+     *         }
+     *     ]
+     *   [...]
+     * }
+     * @endcode
+     * @warning This will be removed in KF6.
+     * @property list<QtQml.QtObject> contextualActions
+     */
+    // TODO: remove
+    property alias contextualActions: actionsGroup.contextualActions
+
+    /**
+     * @brief An optional single action for the action button.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     * Kirigami.Page {
+     *     actions.main: Kirigami.Action {
+     *         icon.name: "edit"
+     *         onTriggered: {
+     *             // do stuff
+     *         }
+     *     }
+     * }
+     * @endcode
+     * @warning This will be removed in KF6.
+     * @property Action mainAction
+     */
+    //TODO: remove
+    property alias mainAction: actionsGroup.main
+
+    /**
+     * @brief An optional extra action at the left of the main action button.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     * Kirigami.Page {
+     *     actions.left: Kirigami.Action {
+     *         icon.name: "edit"
+     *         onTriggered: {
+     *             // do stuff
+     *         }
+     *     }
+     * }
+     * @endcode
+     * @warning This will be removed in KF6.
+     * @property Action leftAction
+     */
+    // TODO: remove
+    property alias leftAction: actionsGroup.left
+
+    /**
+     * @brief An optional extra action at the right of the main action button.
+     *
+     * Example usage:
+     * @code{.qml}
+     * import org.kde.kirigami 2.4 as Kirigami
+     * Kirigami.Page {
+     *     actions.right: Kirigami.Action {
+     *         icon.name: "edit"
+     *         onTriggered: {
+     *             // do stuff
+     *         }
+     *     }
+     * }
+     * @endcode
+     * @warning This will be removed in KF6.
+     * @property Action rightAction
+     */
+    // TODO: remove
+    property alias rightAction: actionsGroup.right
+
+    /**
+     * @brief This property holds the actions group.
+     * @code
+     * import org.kde.kirigami 2.4 as Kirigami
+     * Kirigami.Page {
+     *     actions {
+     *         main: Kirigami.Action {...}
+     *         left: Kirigami.Action {...}
+     *         right: Kirigami.Action {...}
+     *         contextualActions: [
+     *             Kirigami.Action {...},
+     *             Kirigami.Action {...}
+     *         ]
+     *     }
+     * }
+     * @endcode
+     * @see <a href="https://develop.kde.org/hig/components/navigation/actionbutton">KDE Human Interface Guidelines on Actions</a>
+     * @property kirigami::private::PageActionPropertyGroup actions
+     */
+    readonly property alias actions: actionsGroup
+
+    /**
+     * @brief This property specifies us if it is the currently active page.
+     *
+     * Specifies if it's the currently selected page in the window's pages
+     * row, or if layers are used whether this is the topmost item on the
+     * layers stack. If the page is not attached to either a column view or
+     * a stack view, expect this to be true.
+     *
+     * @since org.kde.kirigami 2.1
+     */
+    //TODO KF6: remove this or at least all the assumptions about the internal tree structure of items
+    readonly property bool isCurrentPage: Kirigami.ColumnView.view
+            ? (Kirigami.ColumnView.index === Kirigami.ColumnView.view.currentIndex && Kirigami.ColumnView.view.parent.parent.currentItem === Kirigami.ColumnView.view.parent)
+            : (parent && parent instanceof QQC2.StackView
+                ? parent.currentItem === root
+                : true)
+
+    /**
+     * An item which stays on top of every other item in the page,
+     * if you want to make sure some elements are completely in a
+     * layer on top of the whole content, parent items to this one.
+     * It's a "local" version of ApplicationWindow's overlay
+     *
+     * @property Item overlay
+     * @since org.kde.kirigami 2.5
+     */
+    readonly property alias overlay: overlayItem
+
+    /**
+     * @brief This holds the icon that represents this page.
+     */
+    property P.ActionIconGroup icon: P.ActionIconGroup {}
+
+    /**
+     * @brief Whether this page needs user attention.
+     */
+    property bool needsAttention
+
+    /**
+     * @brief Progress of a task this page is doing.
+     *
+     * Set to undefined to indicate that there are no ongoing tasks.
+     *
+     * default: ``undefined``
+     *
+     * @property real progress
+     */
+    property var progress: undefined
+
+    /**
+     * @brief The delegate which will be used to draw the page title.
+     *
+     * It can be customized to put any kind of Item in there.
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    property Component titleDelegate: Component {
+        id: defaultTitleDelegate
+        P.DefaultPageTitleDelegate {
+            text: root.title
+        }
+    }
+
+    /**
+     * The item used as global toolbar for the page
+     * present only if we are in a PageRow as a page or as a layer,
+     * and the style is either Titles or ToolBar.
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    readonly property Item globalToolBarItem: globalToolBar.item
+
+    /**
+     * @brief This property holds the style for the automatically generated
+     * toolbar of the PageRow where the Page is inserted.
+     *
+     * By default, the Page's globalToolBarStyle determines the style used by
+     * the PageRow where it is inserted, updating the PageRow's globalToolBar
+     * style. This allows for a single page to override the application toolbar
+     * style for itself.
+     *
+     * It is discouraged to use this, except in very specific cases, like a
+     * chat application that can't have controls at the bottom except for a
+     * text field. If the Page is not in a PageRow, by default, the toolbar
+     * will be invisible, so Page::globalToolBarStyle has to be explicitly
+     * set to ApplicationHeaderStyle.ToolBar to be used in that case.
+     */
+    property int globalToolBarStyle: {
+        if (globalToolBar.row && !globalToolBar.stack) {
+            return globalToolBar.row.globalToolBar.actualStyle;
+        } else if (globalToolBar.stack) {
+            return Kirigami.Settings.isMobile ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.ToolBar;
+        } else {
+            return Kirigami.ApplicationHeaderStyle.None;
+        }
+    }
+//END properties
+
+//BEGIN signals and signal handlers
+    /**
+     * Emitted when a visualization for the actions is about to
+     * be shown, such as the toolbar menu or the ContextDrawer.
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    signal contextualActionsAboutToShow
+
+    /**
+     * @brief Emitted when the application requests a Back action.
+     *
+     * For instance a global "back" shortcut or the Android
+     * Back button has been pressed.
+     * The page can manage the back event by itself,
+     * and if it set event.accepted = true, it will stop the main
+     * application to manage the back event.
+     */
+    signal backRequested(var event);
+
+
+    // Look for sheets and cose them
+    // FIXME: port Sheets to Popup?
+    onBackRequested: event => {
+        let item;
+        for (const i in root.resources) {
+            item = root.resources[i];
+            if (item.hasOwnProperty("close") && item.hasOwnProperty("sheetOpen") && item.sheetOpen) {
+                item.close()
+                event.accepted = true;
+                return;
+            }
+        }
+    }
+
+    onHeaderChanged: {
+        if (header) {
+            header.anchors.top = Qt.binding(() => globalToolBar.visible ? globalToolBar.bottom : root.top);
+        }
+    }
+
+    Component.onCompleted: {
+        headerChanged();
+        parentChanged(root.parent);
+        globalToolBar.syncSource();
+        actionButtons.pageComplete = true
+    }
+
+    onParentChanged: {
+        if (!parent) {
+            return;
+        }
+        globalToolBar.stack = null;
+        globalToolBar.row = null;
+
+        if (root.Kirigami.ColumnView.view) {
+            globalToolBar.row = root.Kirigami.ColumnView.view.__pageRow;
+        }
+        if (root.T2.StackView.view) {
+            globalToolBar.stack = root.T2.StackView.view;
+            globalToolBar.row = root.T2.StackView.view ? root.T2.StackView.view.parent : null;
+        }
+        if (globalToolBar.row) {
+            root.globalToolBarStyleChanged.connect(globalToolBar.syncSource);
+            globalToolBar.syncSource();
+        }
+    }
+//END signals and signal handlers
+
+    // NOTE: contentItem will be created if not existing (and contentChildren of Page would become its children) This with anchors enforces the geometry we want, where globalToolBar is a super-header, on top of header
+    contentItem: Item {
+        anchors {
+            top: (root.header && root.header.visible)
+                    ? root.header.bottom
+                    : (globalToolBar.visible ? globalToolBar.bottom : parent.top)
+            topMargin: root.topPadding + root.spacing
+            bottom: (root.footer && root.footer.visible) ? root.footer.top : parent.bottom
+            bottomMargin: root.bottomPadding + root.spacing
+        }
+    }
+
+    background: Rectangle {
+        color: Kirigami.Theme.backgroundColor
+    }
+
+    implicitHeight: ((header && header.visible) ? header.implicitHeight : 0) + ((footer && footer.visible) ? footer.implicitHeight : 0) + contentItem.implicitHeight + topPadding + bottomPadding
+    implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
+
+    // FIXME: on material the shadow would bleed over
+    clip: root.header !== null;
+
+    padding: Kirigami.Units.gridUnit
+    bottomPadding: actionButtons.item ? actionButtons.height : verticalPadding
+
+    // in data in order for them to not be considered for contentItem, contentChildren, contentData
+    data: [
+        P.PageActionPropertyGroup {
+            id: actionsGroup
+        },
+
+        Item {
+            id: overlayItem
+            parent: root
+            z: 9997
+            anchors {
+                fill: parent
+                topMargin: globalToolBar.height
+            }
+        },
+        // global top toolbar if we are in a PageRow (in the row or as a layer)
+        Loader {
+            id: globalToolBar
+            z: 9999
+            height: item ? item.implicitHeight : 0
+            anchors {
+                left:  parent.left
+                right: parent.right
+                top: parent.top
+            }
+            property Kirigami.PageRow row
+            property T2.StackView stack
+
+            // don't load async so that on slower devices we don't have the page content height changing while loading in
+            // otherwise, it looks unpolished and jumpy
+            asynchronous: false
+
+            visible: active
+            active: (root.titleDelegate !== defaultTitleDelegate || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.Titles)
+            onActiveChanged: {
+                if (active) {
+                    syncSource();
+                }
+            }
+
+            function syncSource() {
+                if (root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar &&
+                    root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.Titles &&
+                    root.titleDelegate !== defaultTitleDelegate) {
+                    sourceComponent = root.titleDelegate;
+                } else if (active) {
+                    const url = root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
+                        ? "private/globaltoolbar/ToolBarPageHeader.qml"
+                        : "private/globaltoolbar/TitlesPageHeader.qml";
+                    // TODO: find container reliably, remove assumption
+                    setSource(Qt.resolvedUrl(url), {
+                        pageRow: Qt.binding(() => row),
+                        page: root,
+                        current: Qt.binding(() => {
+                            if (!row && !stack) {
+                                return true;
+                            } else if (stack) {
+                                return stack;
+                            } else {
+                                return row.currentIndex === root.Kirigami.ColumnView.level;
+                            }
+                        }),
+                    });
+                }
+            }
+        },
+        // bottom action buttons
+        Loader {
+            id: actionButtons
+            z: 9999
+            parent: root
+            anchors {
+                left: parent.left
+                right: parent.right
+                bottom: parent.bottom
+            }
+            // if the page doesn't inherit, assume it has custom colors we want to use them here too
+            Kirigami.Theme.inherit: !root.Kirigami.Theme.inherit
+            Kirigami.Theme.colorSet: Kirigami.Theme.Button
+
+            // It should be T2.Page, Qt 5.7 doesn't like it
+            property Item page: root
+            height: item ? item.implicitHeight : 0
+
+            asynchronous: true
+
+            property bool pageComplete: false
+
+            active: {
+                // Important! Do not do anything until the page has been
+                // completed, so we are sure what the globalToolBarStyle is,
+                // otherwise we risk creating the content and then discarding it.
+                if (!pageComplete) {
+                    return false;
+                }
+
+                if ((globalToolBar.row && globalToolBar.row.globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar)
+                    || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
+                    || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.None) {
+                    return false;
+                }
+
+                if (!root.actions.main && !root.actions.left && !root.actions.right && root.actions.contextualActions.length === 0) {
+                    return false;
+                }
+
+                // Legacy
+                if (typeof applicationWindow === "undefined") {
+                    return true;
+                }
+
+                if (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1) {
+                    return false;
+                }
+
+                if (applicationWindow().footer && applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") !== -1) {
+                    return false;
+                }
+
+                return true;
+            }
+
+            source: Qt.resolvedUrl("./private/ActionButton.qml")
+        }
+    ]
+
+    Layout.fillWidth: true
+}
diff --git a/src/controls/PagePoolAction.qml b/src/controls/PagePoolAction.qml
new file mode 100644 (file)
index 0000000..b9d5883
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQml 2.7
+import QtQuick.Controls 2.5 as QQC2
+import org.kde.kirigami 2.11 as Kirigami
+
+/**
+ * An action used to load Pages coming from a common PagePool
+ * in a PageRow or QtQuick.Controls.StackView.
+ *
+ * @see PagePool
+ */
+Kirigami.Action {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds the url or filename of the page that this action will load.
+     */
+    property string page
+
+    /**
+     * @brief This property holds the PagePool object used by this PagePoolAction.
+     *
+     * PagePool will make sure only one instance of the page identified by the page url will be created and reused.
+     * PagePool::lastLoadedUrl will be used to control the mutual exclusivity of the checked
+     * state of the PagePoolAction instances sharing the same PagePool.
+     */
+    property Kirigami.PagePool pagePool
+
+    /**
+     * This property accepts either a PageRow or a StackView.
+     * The component that will instantiate the pages, which has to work with a stack logic.
+     * PageRow is recommended, but will work with StackView as well.
+     *
+     * default: `bound to the global ApplicationWindow::pageStack, which is a PageRow by default`
+     */
+    property Item pageStack: typeof applicationWindow !== 'undefined' ? applicationWindow().pageStack : null
+
+    /**
+     * @brief This property sets the page in the ::pageStack after which
+     * new pages will be pushed.
+     *
+     * All pages present after the given basePage will be removed from the ::pageStack
+     */
+    property QQC2.Page basePage
+
+    /**
+     * This property holds a function that generate the property values for the created page
+     * when it is pushed onto the PagePool.
+     *
+     * Example usage:
+     * @code{.qml}
+     * Kirigami.PagePoolAction {
+     *     text: i18n("Security")
+     *     icon.name: "security-low"
+     *     page: Qt.resolvedUrl("Security.qml")
+     *     initialProperties: {
+     *         return {
+     *             room: root.room
+     *         }
+     *     }
+     * }
+     * @endcode
+     * @property QVariantMap initialProperties
+     */
+    property var initialProperties
+
+    /**
+      * @brief This property sets whether PagePoolAction will use the layers property
+      * implemented by the ::pageStack.
+      *
+      * This is intended for use with PageRow layers to allow PagePoolActions to
+      * push context-specific pages onto the layers stack.
+      *
+      * default: ``false``
+      *
+      * @since KDE Frameworks 5.70
+      * @since org.kde.kirigami 2.12
+      */
+    property bool useLayers: false
+//END properties
+
+    /**
+      * @returns the page item held in the PagePool or null if it has not been loaded yet.
+      */
+    function pageItem() {
+        return pagePool.pageForUrl(page)
+    }
+
+    /**
+      * @returns @c true if the page has been loaded and placed on pageStack.layers
+      * and useLayers is true, otherwise returns null.
+      */
+    function layerContainsPage() {
+        if (!useLayers || !pageStack.hasOwnProperty("layers")) return false
+
+        const item = pageStack.layers.find((item, index) => {
+            return item === pagePool.pageForUrl(page)
+        })
+        return !!item
+    }
+
+    /**
+      * @returns @c true if the page has been loaded and placed on the ::pageStack,
+      * otherwise returns null.
+      */
+    function stackContainsPage() {
+        if (useLayers) return false
+        return pageStack.columnView.containsItem(pagePool.pageForUrl(page))
+    }
+
+    checkable: true
+
+    onTriggered: {
+        if (page.length === 0 || !pagePool || !pageStack) {
+            return;
+        }
+
+        // User intends to "go back" to this layer.
+        if (layerContainsPage() && pageItem() !== pageStack.layers.currentItem) {
+            pageStack.layers.replace(pageItem(), pageItem()) // force pop above
+            return
+        }
+
+        // User intends to "go back" to this page.
+        if (stackContainsPage()) {
+            if (pageStack.hasOwnProperty("layers")) {
+                pageStack.layers.clear()
+            }
+        }
+
+        const pageStack_ = useLayers ? pageStack.layers : pageStack
+
+        if (initialProperties && typeof(initialProperties) !== "object") {
+            console.warn("initialProperties must be of type object");
+            return;
+        }
+
+        if (!pageStack_.hasOwnProperty("pop") || typeof pageStack_.pop !== "function" || !pageStack_.hasOwnProperty("push") || typeof pageStack_.push !== "function") {
+            return;
+        }
+
+        if (pagePool.isLocalUrl(page)) {
+            if (basePage) {
+                pageStack_.pop(basePage);
+
+            } else if (!useLayers) {
+                pageStack_.clear();
+            }
+
+            pageStack_.push(initialProperties ?
+                               pagePool.loadPageWithProperties(page, initialProperties) :
+                               pagePool.loadPage(page));
+        } else {
+            const callback = function(item) {
+                if (basePage) {
+                    pageStack_.pop(basePage);
+
+                } else if (!useLayers) {
+                    pageStack_.clear();
+                }
+                pageStack_.push(item);
+            };
+
+            if (initialProperties) {
+                pagePool.loadPage(page, initialProperties, callback);
+
+            } else {
+                pagePool.loadPage(page, callback);
+            }
+        }
+    }
+
+    // Exposing this as a property is required as Action does not have a default property
+    /** @internal */
+    property QtObject _private: QtObject {
+        id: _private
+
+        function setChecked(checked) {
+            root.checked = checked
+        }
+
+        function clearLayers() {
+            pageStack.layers.clear()
+        }
+
+        property list<Connections> connections: [
+            Connections {
+                target: pageStack
+
+                function onCurrentItemChanged() {
+                    if (root.useLayers) {
+                        if (root.layerContainsPage()) {
+                            _private.clearLayers()
+                        }
+                        if (root.checkable)
+                            _private.setChecked(false);
+
+                    } else {
+                        if (root.checkable)
+                            _private.setChecked(root.stackContainsPage());
+                    }
+                }
+            },
+            Connections {
+                enabled: pageStack.hasOwnProperty("layers")
+                target: pageStack.layers
+
+                function onCurrentItemChanged() {
+                    if (root.useLayers && root.checkable) {
+                        _private.setChecked(root.layerContainsPage());
+
+                    } else {
+                        if (pageStack.layers.depth === 1 && root.stackContainsPage()) {
+                            _private.setChecked(true)
+                        }
+                    }
+                }
+            }
+        ]
+    }
+}
diff --git a/src/controls/PageRow.qml b/src/controls/PageRow.qml
new file mode 100644 (file)
index 0000000..9f1d1e1
--- /dev/null
@@ -0,0 +1,1003 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.2
+import QtQml.Models 2.2
+import QtQuick.Templates 2.0 as QT
+import QtQuick.Controls 2.0 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+import "private/globaltoolbar" as GlobalToolBar
+import "templates" as KT
+
+/**
+ * PageRow implements a row-based navigation model, which can be used
+ * with a set of interlinked information pages. Items are pushed in the
+ * back of the row and the view scrolls until that row is visualized.
+ * A PageRow can show a single Page or multple pages at the same time,
+ * depending on the window width: on a phone a single column should be fullscreen,
+ * while on a tablet or a desktop more than one column can be visible at the same time.
+ *
+ * Example usage:
+ * @include pagerow.qml
+ *
+ * @see ColumnViewAttached
+ * @inherit QtQuick.Templates.Control
+ */
+QT.Control {
+    id: root
+
+//BEGIN PROPERTIES
+    /**
+     * @brief This property holds the number of items currently pushed onto the view.
+     * @property int depth
+     */
+    property alias depth: columnView.count
+
+    /**
+     * @brief This property holds the last page in the row.
+     * @property Page lastItem
+     */
+    readonly property Item lastItem: columnView.contentChildren.length > 0 ?  columnView.contentChildren[columnView.contentChildren.length - 1] : null
+
+    /**
+     * @brief This property points to the currently active page.
+     * @property Kirigami.Page currentItem
+     */
+    property alias currentItem: columnView.currentItem
+
+    /**
+     * @brief This property holds the index of the currently active page.
+     * @see ::currentItem
+     * @property int currentIndex
+     */
+    property alias currentIndex: columnView.currentIndex
+
+    /**
+     * @brief This property sets the initial page/s for this PageRow.
+     * @note This can optionally be set to an array of pages by using the JavaScript array notation [].
+     * @property Kirigami.Page initialPage
+     */
+    property variant initialPage
+
+    /**
+     * @brief This property holds the main ColumnView of this Row.
+     * @see ColumnView
+     * @property Kirigami.ColumnView contentItem
+     */
+    contentItem: columnView
+
+    /**
+     * @brief This property holds the ColumnView that this PageRow owns.
+     *
+     * Generally, you shouldn't need to change the value of this property.
+     *
+     * @property Kirigami.ColumnView columnView
+     * @since org.kde.kirigami 2.12
+     */
+    property alias columnView: columnView
+
+    /**
+     * @brief This property holds a list of all pages in the PageRow.
+     * @property list<Kirigami.Page> items
+     * @since org.kde.kirigami 2.6
+     */
+    property alias items: columnView.contentChildren;
+
+    /**
+     * @brief This property holds all visible pages in the PageRow,
+     * excluding those which are scrolled away.
+     * @property list<Kirigami.Page> visibleItems
+     * @since org.kde.kirigami 2.6
+     */
+    property alias visibleItems: columnView.visibleItems
+
+    /**
+     * @brief This property holds the first page in the PageRow that is at least partially visible.
+     * @note Pages before that one (the one contained in the property) will be out of the viewport.
+     * @see ColumnView::firstVisibleItem
+     * @property Item firstVisibleItem
+     * @since org.kde.kirigami 2.6
+     */
+    property alias firstVisibleItem: columnView.firstVisibleItem
+
+    /**
+     * @brief This property holds the last page in the PageRow that is at least partially visible.
+     * @note Pages after that one (the one contained in the property) will be out of the viewport.
+     * @see ColumnView::lastVisibleItem
+     * @property Item lastVisibleItem
+     * @since org.kde.kirigami 2.6
+     */
+    property alias lastVisibleItem: columnView.lastVisibleItem
+
+    /**
+     * @brief This property holds the default width for a column.
+     *
+     * default: ``20 * Kirigami.Units.gridUnit``
+     *
+     * @note Pages can override it using implicitWidth, Layout.fillWidth, Layout.minimumWidth etc.
+     */
+    property int defaultColumnWidth: Kirigami.Units.gridUnit * 20
+
+    /**
+     * @brief This property sets whether it is possible to go back/forward
+     * by swiping with a gesture on the content view.
+     *
+     * default: ``true``
+     *
+     * @property bool interactive
+     */
+    property alias interactive: columnView.interactive
+
+    /**
+     * @brief This property specifies whether the PageRow is wide enough to show multiple pages.
+     * @since KDE Frameworks 5.37
+     */
+    readonly property bool wideMode: root.width >= root.defaultColumnWidth*2 && depth >= 2
+
+    /**
+     * @brief This property sets whether the separators between pages should be displayed.
+     *
+     * default: ``true``
+     *
+     * @property bool separatorVisible
+     * @since KDE Frameworks 5.38
+     */
+    property alias separatorVisible: columnView.separatorVisible
+
+    /**
+     * @brief This property sets the appearance of an optional global toolbar for the whole PageRow.
+     *
+     * This grouped property has the following sub-properties:
+     * * ``style: ApplicationHeaderStyle::Status``: how the top bar controls should be represented to the user.
+     * * ``actualStyle``: this property holds the currently used style. It can be different when ``style`` is set to ``ApplicationHeaderStyle.Auto``
+     * * ``showNavigationButtons: ApplicationHeaderStyle::NavigationButton``: a
+     * combination of flags that determines whether to show the back and forward
+     * button.
+     * * ``toolbarActionAlignment: Qt::Alignment``: how to horizontally align the actions when using the ``ApplicationHeaderStyle.ToolBar`` style. Note that anything but ``Qt.AlignRight`` will cause the title to be hidden (default: ``Qt.AlignRight``).
+     * * ``minimumHeight: int`` Minimum height of the header, which will be resized when scrolling. Only in Mobile mode (default: ``preferredHeight``, sliding but no scaling).
+     * * ``preferredHeight: int`` The height the toolbar will usually have.
+     * * ``maximumHeight: int `` The height the toolbar will have in mobile mode when the app is in reachable mode (default: preferredHeight * 1.5).
+     * * ``leftReservedSpace: int, readonly`` How many pixels of extra space are reserved at the left of the page toolbar (typically for navigation buttons or a drawer handle).
+     * * ``rightReservedSpace: int, readonly`` How many pixels of extra space  are reserved at the right of the page toolbar (typically for a drawer handle).
+     *
+     * @property kirigami::private::globaltoolbar::PageRowGlobalToolBarStyleGroup globalToolBar
+     * @since KDE Frameworks 5.48
+     */
+    readonly property alias globalToolBar: globalToolBar
+
+    /**
+     * @brief This property assigns a drawer as an internal left sidebar for this PageRow.
+     *
+     * In this case, when open and not modal, the drawer contents will be in the same layer as the base pagerow.
+     * Pushing any other layer on top will cover the sidebar.
+     *
+     * @since KDE Frameworks 5.84
+     */
+    // TODO KF6: globaldrawer should use actions also used by this sidebar instead of reparenting globaldrawer contents?
+    property OverlayDrawer leftSidebar
+
+    /**
+     * @brief This property holds the modal layers.
+     *
+     * Sometimes an application needs a modal page that always covers all the rows.
+     * For instance the full screen image of an image viewer or a settings page.
+     *
+     * @property QtQuick.Controls.StackView layers
+     * @since KDE Frameworks 5.38
+     */
+    property alias layers: layersStack
+
+    /**
+     * @brief This property holds whether to automatically pop pages at the top of the stack if they are not visible.
+     *
+     * If a user navigates to a previous page on the stack (ex. pressing back button) and pages above
+     * it on the stack are not visible, they will be popped if this property is true.
+     *
+     * @since KDE Frameworks 5.101
+     */
+    property bool popHiddenPages: false
+//END PROPERTIES
+
+//BEGIN FUNCTIONS
+    /**
+     * @brief Pushes a page on the stack.
+     *
+     * The page can be defined as a component, item or string.
+     * If an item is used then the page will get re-parented.
+     * If a string is used then it is interpreted as a url that is used to load a page
+     * component.
+     * The last pushed page will become the current item.
+     *
+     * @param page The page can also be given as an array of pages.
+     *     In this case all those pages will
+     *     be pushed onto the stack. The items in the stack can be components, items or
+     *     strings just like for single pages.
+     *     Additionally an object can be used, which specifies a page and an optional
+     *     properties property.
+     *     This can be used to push multiple pages while still giving each of
+     *     them properties.
+     *     When an array is used the transition animation will only be to the last page.
+     *
+     * @param properties The properties argument is optional and allows defining a
+     * map of properties to set on the page. If page is actually an array of pages, properties should also be an array of key/value maps
+     * @return The new created page (or the last one if it was an array)
+     */
+    function push(page, properties) {
+        const item = insertPage(depth, page, properties);
+        currentIndex = depth - 1;
+        return item;
+    }
+
+    /**
+     * @brief Pushes a page as a new dialog on desktop and as a layer on mobile.
+     * @param page The page can be defined as a component, item or string. If an item is
+     *             used then the page will get re-parented. If a string is used then it
+     *             is interpreted as a url that is used to load a page component. Once
+     *             pushed the page gains the methods `closeDialog` allowing to close itself.
+     *             Kirigami only supports calling `closeDialog` once.
+     * @param properties The properties given when initializing the page.
+     * @param windowProperties The properties given to the initialized window on desktop.
+     * @return Returns a newly created page.
+     */
+    function pushDialogLayer(page, properties = {}, windowProperties = {}) {
+        let item;
+        if (Kirigami.Settings.isMobile) {
+            if (QQC2.ApplicationWindow.window.width > Kirigami.Units.gridUnit * 40) {
+                // open as a QQC2.Dialog
+                const dialog = Qt.createQmlObject(`
+                    import QtQuick 2.15;
+                    import QtQuick.Controls 2.15;
+                    import QtQuick.Layouts 1.15;
+                    import org.kde.kirigami 2.20 as Kirigami;
+                    Kirigami.Dialog {
+                        id: dialog
+                        modal: true;
+                        leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0;
+                        clip: true
+                        header: Kirigami.AbstractApplicationHeader {
+                            pageRow: null
+                            page: null
+                            minimumHeight: Kirigami.Units.gridUnit * 1.6
+                            maximumHeight: Kirigami.Units.gridUnit * 1.6
+                            preferredHeight: Kirigami.Units.gridUnit * 1.6
+
+                            Keys.onEscapePressed: event => {
+                                if (dialog.opened) {
+                                    dialog.close();
+                                } else {
+                                    event.accepted = false;
+                                }
+                            }
+
+                            contentItem: RowLayout {
+                                width: parent.width
+                                Kirigami.Heading {
+                                    Layout.leftMargin: Kirigami.Units.largeSpacing
+                                    text: dialog.title
+                                    elide: Text.ElideRight
+                                }
+                                Item {
+                                    Layout.fillWidth: true;
+                                }
+                                Kirigami.Icon {
+                                    id: closeIcon
+                                    Layout.alignment: Qt.AlignVCenter
+                                    Layout.rightMargin: Kirigami.Units.largeSpacing
+                                    Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
+                                    Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
+                                    source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
+                                    active: closeMouseArea.containsMouse
+                                    MouseArea {
+                                        id: closeMouseArea
+                                        hoverEnabled: true
+                                        anchors.fill: parent
+                                        onClicked: mouse => dialog.close();
+                                    }
+                                }
+                            }
+                        }
+                        contentItem: Control { topPadding: 0; leftPadding: 0; rightPadding: 0; bottomPadding: 0; }
+                    }`, QQC2.ApplicationWindow.overlay);
+                dialog.width = Qt.binding(() => QQC2.ApplicationWindow.window.width - Kirigami.Units.gridUnit * 5);
+                dialog.height = Qt.binding(() => QQC2.ApplicationWindow.window.height - Kirigami.Units.gridUnit * 5);
+                dialog.x = Kirigami.Units.gridUnit * 2.5;
+                dialog.y = Kirigami.Units.gridUnit * 2.5;
+
+                if (typeof page === "string") {
+                    // url => load component and then load item from component
+                    const component = Qt.createComponent(Qt.resolvedUrl(page));
+                    item = component.createObject(dialog.contentItem, properties);
+                    component.destroy();
+                    dialog.contentItem.contentItem = item
+                } else if (page instanceof Component) {
+                    item = page.createObject(dialog.contentItem, properties);
+                    dialog.contentItem.contentItem = item
+                } else if (page instanceof Item) {
+                    item = page;
+                    page.parent = dialog.contentItem;
+                }
+                dialog.title = Qt.binding(() => item.title);
+
+                // Pushing a PageRow is supported but without PageRow toolbar
+                if (item.globalToolBar && item.globalToolBar.style) {
+                    item.globalToolBar.style = Kirigami.ApplicationHeaderStyle.None
+                }
+                Object.defineProperty(item, 'closeDialog', {
+                    value: function() {
+                        dialog.close();
+                    }
+                });
+                dialog.open();
+            } else {
+                // open as a layer
+                item = layers.push(page, properties);
+                Object.defineProperty(item, 'closeDialog', {
+                    value: function() {
+                        layers.pop();
+                    }
+                });
+            }
+        } else {
+            // open as a new window
+            if (!windowProperties.modality) {
+                windowProperties.modality = Qt.WindowModal;
+            }
+            if (!windowProperties.height) {
+                windowProperties.height = Kirigami.Units.gridUnit * 30;
+            }
+            if (!windowProperties.width) {
+                windowProperties.width = Kirigami.Units.gridUnit * 50;
+            }
+            if (!windowProperties.minimumWidth) {
+                windowProperties.minimumWidth = Kirigami.Units.gridUnit * 20;
+            }
+            if (!windowProperties.minimumHeight) {
+                windowProperties.minimumHeight = Kirigami.Units.gridUnit * 15;
+            }
+            if (!windowProperties.flags) {
+                windowProperties.flags = Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint;
+            }
+            const windowComponent = Qt.createComponent(Qt.resolvedUrl("./ApplicationWindow.qml"));
+            const window = windowComponent.createObject(root, windowProperties);
+            windowComponent.destroy();
+            item = window.pageStack.push(page, properties);
+            Object.defineProperty(item, 'closeDialog', {
+                value: function() {
+                    window.close();
+                }
+            });
+        }
+        item.Keys.escapePressed.connect(event => item.closeDialog());
+        return item;
+    }
+
+    /**
+     * @brief Inserts a new page or a list of new pages at an arbitrary position.
+     *
+     * The page can be defined as a component, item or string.
+     * If an item is used then the page will get re-parented.
+     * If a string is used then it is interpreted as a url that is used to load a page
+     * component.
+     * The current Page will not be changed, currentIndex will be adjusted
+     * accordingly if needed to keep the same current page.
+     *
+     * @param page The page can also be given as an array of pages.
+     *     In this case all those pages will
+     *     be pushed onto the stack. The items in the stack can be components, items or
+     *     strings just like for single pages.
+     *     Additionally an object can be used, which specifies a page and an optional
+     *     properties property.
+     *     This can be used to push multiple pages while still giving each of
+     *     them properties.
+     *     When an array is used the transition animation will only be to the last page.
+     *
+     * @param properties The properties argument is optional and allows defining a
+     * map of properties to set on the page. If page is actually an array of pages, properties should also be an array of key/value maps
+     * @return The new created page (or the last one if it was an array)
+     * @since org.kde.kirigami 2.7
+     */
+    function insertPage(position, page, properties) {
+        if (!page) {
+            return null
+        }
+        //don't push again things already there
+        if (page.createObject === undefined && typeof page !== "string" && columnView.containsItem(page)) {
+            print("The item " + page + " is already in the PageRow");
+            return null;
+        }
+
+        position = Math.max(0, Math.min(depth, position));
+
+        columnView.pop(columnView.currentItem);
+
+        // figure out if more than one page is being pushed
+        let pages;
+        let propsArray = [];
+        if (page instanceof Array) {
+            pages = page;
+            page = pages.pop();
+            //compatibility with pre-qqc1 api, can probably be removed
+            if (page.createObject === undefined && page.parent === undefined && typeof page !== "string") {
+                properties = properties || page.properties;
+                page = page.page;
+            }
+        }
+        if (properties instanceof Array) {
+            propsArray = properties;
+            properties = propsArray.pop();
+        } else {
+            propsArray = [properties];
+        }
+
+        // push any extra defined pages onto the stack
+        if (pages) {
+            for (let i = 0; i < pages.length; i++) {
+                let tPage = pages[i];
+                let tProps = propsArray[i];
+                //compatibility with pre-qqc1 api, can probably be removed
+                if (tPage.createObject === undefined && tPage.parent === undefined && typeof tPage !== "string") {
+                    if (columnView.containsItem(tPage)) {
+                        print("The item " + page + " is already in the PageRow");
+                        continue;
+                    }
+                    tProps = tPage.properties;
+                    tPage = tPage.page;
+                }
+
+                pagesLogic.initAndInsertPage(position, tPage, tProps);
+                ++position;
+            }
+        }
+
+        // initialize the page
+        const pageItem = pagesLogic.initAndInsertPage(position, page, properties);
+
+        pagePushed(pageItem);
+
+        return pageItem;
+    }
+
+    /**
+     * Move the page at position fromPos to the new position toPos
+     * If needed, currentIndex will be adjusted
+     * in order to keep the same current page.
+     * @since org.kde.kirigami 2.7
+     */
+    function movePage(fromPos, toPos) {
+        columnView.moveItem(fromPos, toPos);
+    }
+
+    /**
+     * @brief Remove the given page.
+     * @param page The page can be given both as integer position or by reference
+     * @return The page that has just been removed
+     * @since org.kde.kirigami 2.7
+     */
+    function removePage(page) {
+        if (depth === 0) {
+            return null;
+        }
+
+        return columnView.removeItem(page);
+    }
+
+    /**
+     * @brief Pops a page off the stack and all the pages after it.
+     * @param page If page is specified then the stack is unwound to that page,
+     * to unwind to the first page specify page as null.
+     * @return The page instance that was popped off the stack.
+     */
+    function pop(page) {
+        if (depth === 0) {
+            return null;
+        }
+
+        return columnView.pop(page);
+    }
+
+    /**
+     * @brief Replaces a page on the stack.
+     * @param page The page can also be given as an array of pages.
+     *     In this case all those pages will
+     *     be pushed onto the stack. The items in the stack can be components, items or
+     *     strings just like for single pages.
+     *     the current page and all pagest after it in the stack will be removed.
+     *     Additionally an object can be used, which specifies a page and an optional
+     *     properties property.
+     *     This can be used to push multiple pages while still giving each of
+     *     them properties.
+     *     When an array is used the transition animation will only be to the last page.
+     * @param properties The properties argument is optional and allows defining a
+     * map of properties to set on the page.
+     * @see ::push() for details.
+     */
+    function replace(page, properties) {
+        if (!page) {
+            return null;
+        }
+
+        // Remove all pages on top of the one being replaced.
+        if (currentIndex >= 0) {
+            columnView.pop(columnView.contentChildren[currentIndex]);
+        } else {
+            console.warn("There's no page to replace");
+        }
+
+        // Figure out if more than one page is being pushed.
+        let pages;
+        let propsArray = [];
+        if (page instanceof Array) {
+            pages = page;
+            page = pages.shift();
+        }
+        if (properties instanceof Array) {
+            propsArray = properties;
+            properties = propsArray.shift();
+        } else {
+            propsArray = [properties];
+        }
+
+        // Replace topmost page.
+        let pageItem = pagesLogic.initPage(page, properties);
+        if (depth > 0)
+            columnView.replaceItem(depth - 1, pageItem);
+        else {
+            console.log("Calling replace on empty PageRow", pageItem)
+            columnView.addItem(pageItem)
+        }
+        pagePushed(pageItem);
+
+        // Push any extra defined pages onto the stack.
+        if (pages) {
+            for (let i = 0; i < pages.length; i++) {
+                const tPage = pages[i];
+                const tProps = propsArray[i];
+
+                pageItem = pagesLogic.initPage(tPage, tProps);
+                columnView.addItem(pageItem);
+                pagePushed(pageItem);
+            }
+        }
+
+        currentIndex = depth - 1;
+        return pageItem;
+    }
+
+    /**
+     * @brief Clears the page stack.
+     *
+     * Destroy (or reparent) all the pages contained.
+     */
+    function clear() {
+        return columnView.clear();
+    }
+
+    /**
+     * @return the page at idx
+     * @param idx the depth of the page we want
+     */
+    function get(idx) {
+        return columnView.contentChildren[idx];
+    }
+
+    /**
+     * Go back to the previous index and scroll to the left to show one more column.
+     */
+    function flickBack() {
+        if (depth > 1) {
+            currentIndex = Math.max(0, currentIndex - 1);
+        }
+    }
+
+    /**
+     * Acts as if you had pressed the "back" button on Android or did Alt-Left on desktop,
+     * "going back" in the layers and page row. Results in a layer being popped if available,
+     * or the currentIndex being set to currentIndex-1 if not available.
+     *
+     * @param event Optional, an event that will be accepted if a page is successfully
+     * "backed" on
+     */
+    function goBack(event = null) {
+        const backEvent = {accepted: false}
+
+        if (layersStack.depth >= 1) {
+            try { // app code might be screwy, but we still want to continue functioning if it throws an exception
+                layersStack.currentItem.backRequested(backEvent)
+            } catch (error) {}
+
+            if (!backEvent.accepted) {
+                if (layersStack.depth > 1) {
+                    layersStack.pop()
+                    if (event) event.accepted = true
+                    return
+                }
+            }
+        }
+
+        if (root.currentIndex >= 1) {
+            try { // app code might be screwy, but we still want to continue functioning if it throws an exception
+                root.currentItem.backRequested(backEvent)
+            } catch (error) {}
+
+            if (!backEvent.accepted) {
+                if (root.depth > 1) {
+                    root.currentIndex = Math.max(0, root.currentIndex - 1)
+                    if (event) event.accepted = true
+                }
+            }
+        }
+    }
+
+    /**
+     * Acts as if you had pressed the "forward" shortcut on desktop,
+     * "going forward" in the page row. Results in the active page
+     * becoming the next page in the row from the current active page,
+     * i.e. currentIndex + 1.
+     */
+    function goForward() {
+        root.currentIndex = Math.min(root.depth-1, root.currentIndex + 1)
+    }
+//END FUNCTIONS
+
+//BEGIN signals & signal handlers
+    /**
+     * @brief Emitted when a page has been inserted anywhere.
+     * @param position where the page has been inserted
+     * @param page the new page
+     * @since org.kde.kirigami 2.7
+     */
+    signal pageInserted(int position, Item page)
+
+    /**
+     * @brief Emitted when a page has been pushed to the bottom.
+     * @param page the new page
+     * @since org.kde.kirigami 2.5
+     */
+    signal pagePushed(Item page)
+
+    /**
+     * @brief Emitted when a page has been removed from the row.
+     * @param page the page that has been removed: at this point it's still valid,
+     *           but may be auto deleted soon.
+     * @since org.kde.kirigami 2.5
+     */
+    signal pageRemoved(Item page)
+
+    onLeftSidebarChanged: {
+        if (leftSidebar && !leftSidebar.modal) {
+            modalConnection.onModalChanged();
+        }
+    }
+
+    Keys.onReleased: {
+        if (event.key === Qt.Key_Back) {
+            this.goBack(event)
+        }
+    }
+
+    onInitialPageChanged: {
+        if (initialPage) {
+            clear();
+            push(initialPage, null)
+        }
+    }
+/*
+    onActiveFocusChanged:  {
+        if (activeFocus) {
+            layersStack.currentItem.forceActiveFocus()
+            if (columnView.activeFocus) {
+                print("SSS"+columnView.currentItem)
+                columnView.currentItem.forceActiveFocus();
+            }
+        }
+    }
+*/
+//END signals & signal handlers
+
+    Connections {
+        id: modalConnection
+        target: root.leftSidebar
+        function onModalChanged() {
+            if (leftSidebar.modal) {
+                const sidebar = sidebarControl.contentItem;
+                const background = sidebarControl.background;
+                sidebarControl.contentItem = null;
+                leftSidebar.contentItem = sidebar;
+                sidebarControl.background = null;
+                leftSidebar.background = background;
+
+                sidebar.visible = true;
+                background.visible = true;
+            } else {
+                const sidebar = leftSidebar.contentItem
+                const background = leftSidebar.background
+                leftSidebar.contentItem=null
+                sidebarControl.contentItem = sidebar
+                leftSidebar.background=null
+                sidebarControl.background = background
+
+                sidebar.visible = true;
+                background.visible = true;
+            }
+        }
+    }
+
+    implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
+    implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
+
+    Shortcut {
+        sequences: [ StandardKey.Back ]
+        onActivated: root.goBack()
+    }
+    Shortcut {
+        sequences: [ StandardKey.Forward ]
+        onActivated: root.goForward()
+    }
+
+    Keys.forwardTo: [currentItem]
+
+    GlobalToolBar.PageRowGlobalToolBarStyleGroup {
+        id: globalToolBar
+        readonly property int leftReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.leftReservedSpace : 0
+        readonly property int rightReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.rightReservedSpace : 0
+        readonly property int height: globalToolBarUI.height
+        readonly property Item leftHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.leftHandleAnchor : null
+        readonly property Item rightHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.rightHandleAnchor : null
+    }
+
+    QQC2.StackView {
+        id: layersStack
+        z: 99
+        anchors {
+            fill: parent
+        }
+        // placeholder as initial item
+        initialItem: columnViewLayout
+
+        function clear() {
+            // don't let it kill the main page row
+            const d = layersStack.depth;
+            for (let i = 1; i < d; ++i) {
+                pop();
+            }
+        }
+
+        popEnter: Transition {
+            OpacityAnimator {
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+        }
+        popExit: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: 0
+                    to: height/2
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+            }
+        }
+
+        pushEnter: Transition {
+            ParallelAnimation {
+                // NOTE: It's a PropertyAnimation instead of an Animator because with an animator the item will be visible for an instant before starting to fade
+                PropertyAnimation {
+                    property: "opacity"
+                    from: 0
+                    to: 1
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: height/2
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+            }
+        }
+
+
+        pushExit: Transition {
+            OpacityAnimator {
+                from: 1
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+        }
+
+        replaceEnter: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 0
+                    to: 1
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: height/2
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+            }
+        }
+
+        replaceExit: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+                YAnimator {
+                    from: 0
+                    to: -height/2
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+            }
+        }
+    }
+
+    Loader {
+        id: globalToolBarUI
+        anchors {
+            left: parent.left
+            top: parent.top
+            right: parent.right
+        }
+        z: 100
+        property QT.Control pageRow: root
+        active: (!firstVisibleItem || firstVisibleItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.None) &&
+                (globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None || (firstVisibleItem && firstVisibleItem.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar))
+        visible: active
+        height: active ? implicitHeight : 0
+        // If load is asynchronous, it will fail to compute the initial implicitHeight
+        // https://bugs.kde.org/show_bug.cgi?id=442660
+        asynchronous: false
+        source: Qt.resolvedUrl("private/globaltoolbar/PageRowGlobalToolBarUI.qml");
+    }
+
+    QtObject {
+        id: pagesLogic
+        readonly property var componentCache: new Array()
+
+        function getPageComponent(page) {
+            let pageComp;
+
+            if (page.createObject) {
+                // page defined as component
+                pageComp = page;
+            } else if (typeof page === "string") {
+                // page defined as string (a url)
+                pageComp = pagesLogic.componentCache[page];
+                if (!pageComp) {
+                    pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page);
+                }
+            } else if (typeof page === "object" && !(page instanceof Item) && page.toString !== undefined) {
+                // page defined as url (QML value type, not a string)
+                pageComp = pagesLogic.componentCache[page.toString()];
+                if (!pageComp) {
+                    pageComp = pagesLogic.componentCache[page.toString()] = Qt.createComponent(page.toString());
+                }
+            }
+
+            return pageComp
+        }
+
+        function initPage(page, properties) {
+            const pageComp = getPageComponent(page, properties);
+
+            if (pageComp) {
+                // instantiate page from component
+                // FIXME: parent directly to columnView or root?
+                page = pageComp.createObject(null, properties || {});
+
+                if (pageComp.status === Component.Error) {
+                    throw new Error("Error while loading page: " + pageComp.errorString());
+                }
+            } else {
+                // copy properties to the page
+                for (const prop in properties) {
+                    if (properties.hasOwnProperty(prop)) {
+                        page[prop] = properties[prop];
+                    }
+                }
+            }
+            return page;
+        }
+
+        function initAndInsertPage(position, page, properties) {
+            page = initPage(page, properties);
+            columnView.insertItem(position, page);
+            return page;
+        }
+    }
+
+    RowLayout {
+        id: columnViewLayout
+        spacing: 1
+        readonly property alias columnView: columnView
+        QQC2.Control {
+            id: sidebarControl
+            Layout.fillHeight: true
+            visible: contentItem !== null && root.leftDrawer && root.leftDrawer.visible
+            leftPadding: root.leftSidebar ? root.leftSidebar.leftPadding : 0
+            topPadding: root.leftSidebar ? root.leftSidebar.topPadding : 0
+            rightPadding: root.leftSidebar ? root.leftSidebar.rightPadding : 0
+            bottomPadding: root.leftSidebar ? root.leftSidebar.bottomPadding : 0
+        }
+        Kirigami.ColumnView {
+            id: columnView
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            topPadding: globalToolBarUI.item && globalToolBarUI.item.breadcrumbVisible
+                        ? globalToolBarUI.height : 0
+
+            // Internal hidden api for Page
+            readonly property Item __pageRow: root
+            acceptsMouse: Kirigami.Settings.isMobile
+            columnResizeMode: root.wideMode ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
+            columnWidth: root.defaultColumnWidth
+
+            onItemInserted: root.pageInserted(position, item);
+            onItemRemoved: root.pageRemoved(item);
+
+            onVisibleItemsChanged: {
+                // implementation of `popHiddenPages` option
+                if (root.popHiddenPages) {
+                    // manually fetch lastItem here rather than use root.lastItem property, since that binding may not have updated yet
+                    let lastItem = columnView.contentChildren[columnView.contentChildren.length - 1];
+                    let lastVisibleItem = columnView.lastVisibleItem;
+                    
+                    // pop every page that isn't visible and at the top of the stack
+                    while (lastItem && columnView.lastVisibleItem && 
+                        lastItem !== columnView.lastVisibleItem && columnView.containsItem(lastItem)) {
+                        root.pop();
+                    }
+                }
+            }
+        }
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        height: Kirigami.Units.smallSpacing
+        x: (columnView.width - width) * (columnView.contentX / (columnView.contentWidth - columnView.width))
+        width: columnView.width * (columnView.width/columnView.contentWidth)
+        color: Kirigami.Theme.textColor
+        opacity: 0
+        onXChanged: {
+            opacity = 0.3
+            scrollIndicatorTimer.restart();
+        }
+        Behavior on opacity {
+            OpacityAnimator {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+        Timer {
+            id: scrollIndicatorTimer
+            interval: Kirigami.Units.longDuration * 4
+            onTriggered: parent.opacity = 0;
+        }
+    }
+}
diff --git a/src/controls/PasswordField.qml b/src/controls/PasswordField.qml
new file mode 100644 (file)
index 0000000..f2ce6ca
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * @brief This is a standard password text field.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.20 as Kirigami
+ *
+ * Kirigami.PasswordField {
+ *     id: passwordField
+ *     onAccepted: {
+ *         // check if passwordField.text is valid
+ *     }
+ * }
+ * @endcode
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @since KDE Frameworks 5.57
+ * @inherit kirigami::ActionTextField
+ */
+Kirigami.ActionTextField {
+    id: root
+
+    /**
+     * @brief This property specifies whether the password will be displayed in cleartext rather than obfuscated.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.57
+     */
+    property bool showPassword: false
+
+    echoMode: root.showPassword ? TextInput.Normal : TextInput.Password
+    placeholderText: qsTr("Password")
+    inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhSensitiveData
+    rightActions: Kirigami.Action {
+        text: root.showPassword ? i18n("Hide Password") : i18n("Show Password")
+        icon.name: root.showPassword ? "password-show-off" : "password-show-on"
+        onTriggered: root.showPassword = !root.showPassword
+    }
+
+    Keys.onPressed: event => {
+        if (event.matches(StandardKey.Undo)) {
+            // Disable undo action for security reasons
+            // See QTBUG-103934
+            event.accepted = true
+        }
+    }
+}
diff --git a/src/controls/PlaceholderMessage.qml b/src/controls/PlaceholderMessage.qml
new file mode 100644 (file)
index 0000000..5e9341c
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 by Nate Graham <nate@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import "private" as P
+
+/**
+ * @brief A placeholder message indicating that a view is empty.
+ *
+ * The message comprises a label with text, an optional explanation below the main text,
+ * an optional icon above all the text, and an optional button below all the text which
+ * can be used to easily show the user what to do next to add content to the view.
+ *
+ * The top-level component is a QtQuick.Layouts.ColumnLayout, so additional components items
+ * can simply be added as child items and they will be positioned sanely.
+ *
+ * Example usage:
+ ** Used as a "this view is empty" message
+ * @code{.qml}
+ * import org.kde.kirigami 2.12 as Kirigami
+ *
+ * ListView {
+ *     id: listView
+ *     model: [...]
+ *     delegate: [...]
+ *
+ *     Kirigami.PlaceholderMessage {
+ *         anchors.centerIn: parent
+ *         width: parent.width - (Kirigami.Units.largeSpacing * 4)
+ *
+ *         visible: listView.count === 0
+ *
+ *         text: "There are no items in this list"
+ *     }
+ * }
+ * @endcode
+ *
+ ** Used as a "here's how to proceed" message:
+ * @code{.qml}
+ * import org.kde.kirigami 2.12 as Kirigami
+ *
+ * ListView {
+ *     id: listView
+ *     model: [...]
+ *     delegate: [...]
+ *
+ *     Kirigami.PlaceholderMessage {
+ *         anchors.centerIn: parent
+ *         width: parent.width - (Kirigami.Units.largeSpacing * 4)
+ *
+ *         visible: listView.count === 0
+ *
+ *         text: "Add an item to proceed"
+ *
+ *         helpfulAction: Kirigami.Action {
+ *             icon.name: "list-add"
+ *             text: "Add item..."
+ *             onTriggered: {
+ *                 [...]
+ *             }
+ *         }
+ *     }
+ *     [...]
+ * }
+ * @endcode
+ *
+ ** Used as a "there was a problem here" message:
+ * @code{.qml}
+ * import org.kde.kirigami 2.12 as Kirigami
+ *
+ * Kirigami.Page {
+ *     id: root
+ *     readonly property bool networkConnected: [...]
+ *
+ *     Kirigami.PlaceholderMessage {
+ *         anchors.centerIn: parent
+ *         width: parent.width - (Kirigami.Units.largeSpacing * 4)
+ *
+ *         visible: root.networkConnected
+ *
+ *         icon.name: "network-disconnect"
+ *         text: "Unable to load content
+ *         explanation: "Please try again later"
+ *     }
+ * }
+ * @endcode
+ *
+ ** Used as a loading indicator:
+ * @code{.qml}
+ * import org.kde.kirigami 2.12 as Kirigami
+ *
+ * Kirigami.Page {
+ *     id: root
+ *     readonly property bool loading: [...]
+ *     readonly property int completionStatus: [...]
+ *
+ *     Kirigami.PlaceholderMessage {
+ *         anchors.centerIn: parent
+ *         width: parent.width - (Kirigami.Units.largeSpacing * 4)
+ *
+ *         visible: root.loading
+ *
+ *         icon.name: "my-awesome-app-icon"
+ *         text: "Loading this awesome app"
+ *
+ *         ProgressBar {
+ *             Layout.preferredWidth: Kirigami.Units.gridUnit * 20
+ *             value: root.completionStatus
+ *             from: 0
+ *             to: 100
+ *         }
+ *     }
+ * }
+ * @endcode
+ *
+ ** Used as a "Here's what you do next" button:
+ * @code{.qml}
+ * import org.kde.kirigami 2.12 as Kirigami
+ *
+ * Kirigami.Page {
+ *     id: root
+ *
+ *     Kirigami.PlaceholderMessage {
+ *         anchors.centerIn: parent
+ *         width: parent.width - (Kirigami.Units.largeSpacing * 4)
+ *
+ *         visible: root.loading
+ *
+ *         helpfulAction: Kirigami.Action {
+ *             icon.name: "list-add"
+ *             text: "Add item..."
+ *             onTriggered: {
+ *                 [...]
+ *             }
+ *         }
+ *     }
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/hig/patterns-content/placeholdermessage">KDE Human Interface Guidelines on Placeholder Messages</a>
+ * @since org.kde.kirigami 2.12
+ * @inherit QtQuick.Layouts.ColumnLayout
+ */
+ColumnLayout {
+    id: root
+
+    enum Type {
+        Actionable,
+        Informational
+    }
+
+//BEGIN properties
+    /**
+     * @brief This property holds the PlaceholderMessage type.
+     *
+     * The following values are allowed:
+     * * ``Kirigami.PlaceholderMessage.Type.Actionable``: makes it more attention grabbing. Useful when the user is expected to interact with the message.
+     * * ``Kirigami.PlaceholderMessage.Type.Informational``: makes it less prominent. Useful when the message is only informational.
+     *
+     * default: `if a ::helpfulAction is provided this will be of type Actionable otherwise of type Informational.`
+     *
+     * @since KDE Frameworks 5.94
+     */
+    property int type: actionButton.action && actionButton.action.enabled ? PlaceholderMessage.Type.Actionable : PlaceholderMessage.Type.Informational
+
+    /**
+     * @brief This property holds the text to show in the placeholder label.
+     *
+     * Optional; if not defined, the message will have no large text label
+     * text. If both text: and explanation: are omitted, the message will have
+     * no text and only an icon, action button, and/or other custom content.
+     *
+     * @since KDE Frameworks 5.70
+     */
+    property string text
+
+    /**
+     * @brief This property holds the smaller explanatory text to show below the larger title-style text
+     *
+     * Useful for providing a user-friendly explanation on how to proceed.
+     *
+     * Optional; if not defined, the message will have no supplementary
+     * explanatory text.
+     *
+     * @since KDE Frameworks 5.80
+     */
+    property string explanation
+
+    /**
+     * @brief This property provides an icon to display above the top text label.
+     *
+     * Optional; if undefined, the message will have no icon.
+     * Falls back to `undefined` if the specified icon is not valid or cannot
+     * be loaded.
+     *
+     * @since KDE Frameworks 5.70
+     */
+    property P.ActionIconGroup icon: P.ActionIconGroup {}
+
+    /**
+     * @brief This property holds an action that helps the user proceed.
+     *
+     * Typically used to guide the user to the next step for adding
+     * content or items to an empty view.
+     *
+     * Optional; if undefined, no button will appear below the text label.
+     *
+     * @property QtQuick.Controls.Action helpfulAction
+     * @since KDE Frameworks 5.70
+     */
+    property alias helpfulAction: actionButton.action
+//END properties
+
+    spacing: Kirigami.Units.largeSpacing
+
+    Kirigami.Icon {
+        visible: source !== undefined
+        opacity: 0.5
+
+        Layout.alignment: Qt.AlignHCenter
+        Layout.preferredWidth: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
+        Layout.preferredHeight: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
+
+        source: {
+            if (root.icon.source.length > 0) {
+                return root.icon.source
+            } else if (root.icon.name.length > 0) {
+                return root.icon.name
+            }
+            return undefined
+        }
+    }
+
+    Kirigami.Heading {
+        text: root.text
+        visible: text.length > 0
+
+        type: Kirigami.Heading.Primary
+        opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
+
+
+        Layout.fillWidth: true
+        horizontalAlignment: Qt.AlignHCenter
+        verticalAlignment: Qt.AlignVCenter
+
+        wrapMode: Text.WordWrap
+    }
+
+    QQC2.Label {
+        text: root.explanation
+        visible:  root.explanation !== ""
+        opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
+
+        horizontalAlignment: Qt.AlignHCenter
+        wrapMode: Text.WordWrap
+
+        Layout.fillWidth: true
+    }
+
+    QQC2.Button {
+        id: actionButton
+
+        Layout.alignment: Qt.AlignHCenter
+        Layout.topMargin: Kirigami.Units.gridUnit
+
+        visible: action && action.enabled
+    }
+}
diff --git a/src/controls/PromptDialog.qml b/src/controls/PromptDialog.qml
new file mode 100644 (file)
index 0000000..e1f9f01
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+    SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * A simple dialog to quickly prompt a user with information,
+ * and possibly perform an action.
+ *
+ * Provides content padding (instead of padding outside of the scroll
+ * area). Also has a default Dialog::preferredWidth, as well as the ::subtitle property.
+ *
+ * <b>Note:</b> If a ::mainItem is specified, it will replace
+ * the ::subtitle label, and so the respective property will have no effect.
+ *
+ * Example usage:
+ * @code{.qml}
+ * Kirigami.PromptDialog {
+ *     title: "Reset settings?"
+ *     subtitle: "The stored settings for the application will be deleted, with the defaults restored."
+ *     standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
+ *
+ *     onAccepted: console.log("Accepted")
+ *     onRejected: console.log("Rejected")
+ * }
+ * @endcode
+ *
+ * Text field prompt dialog:
+ * @code{.qml}
+ * Kirigami.PromptDialog {
+ *     id: textPromptDialog
+ *     title: "New Folder"
+ *
+ *     standardButtons: Kirigami.Dialog.NoButton
+ *     customFooterActions: [
+ *         Kirigami.Action {
+ *             text: qsTr("Create Folder")
+ *             iconName: "dialog-ok"
+ *             onTriggered: {
+ *                 showPassiveNotification("Created");
+ *                 textPromptDialog.close();
+ *             }
+ *         },
+ *         Kirigami.Action {
+ *             text: qsTr("Cancel")
+ *             iconName: "dialog-cancel"
+ *             onTriggered: {
+ *                 textPromptDialog.close();
+ *             }
+ *         }
+ *     ]
+ *
+ *     QQC2.TextField {
+ *         placeholderText: qsTr("Folder name...")
+ *     }
+ * }
+ * @endcode
+ * @see kirigami::Dialog
+ * @see kirigami::MenuDialog
+ * @see <a href="https://develop.kde.org/hig/components/navigation/dialog">Human Interface Guidelines on Dialogs</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/message">Human Interface Guidelines on Modal Message Dialogs</a>
+ * @inherit kirigami::Dialog
+ */
+Kirigami.Dialog {
+    default property alias mainItem: control.contentItem
+
+    /**
+     * The text to use in the dialog's contents.
+     */
+    property string subtitle
+
+    /**
+     * The padding around the content, within the scroll area.
+     *
+     * Default is `Kirigami.Units.largeSpacing`.
+     */
+    property real contentPadding: Kirigami.Units.largeSpacing
+
+    /**
+     * The top padding of the content, within the scroll area.
+     */
+    property real contentTopPadding: contentPadding
+
+    /**
+     * The bottom padding of the content, within the scroll area.
+     */
+    property real contentBottomPadding: contentPadding
+
+    /**
+     * The left padding of the content, within the scroll area.
+     */
+    property real contentLeftPadding: contentPadding
+
+    /**
+     * The right padding of the content, within the scroll area.
+     */
+    property real contentRightPadding: contentPadding
+
+    padding: 0 // we want content padding, not padding of the scrollview
+    preferredWidth: Kirigami.Units.gridUnit * 18
+
+    QQC2.Control {
+        id: control
+        topPadding: contentTopPadding
+        bottomPadding: contentBottomPadding
+        leftPadding: contentLeftPadding
+        rightPadding: contentRightPadding
+
+        contentItem: Kirigami.SelectableLabel {
+            text: subtitle
+            wrapMode: QQC2.Label.Wrap
+        }
+    }
+}
diff --git a/src/controls/RouterWindow.qml b/src/controls/RouterWindow.qml
new file mode 100644 (file)
index 0000000..82bb7ac
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.12 as Kirigami
+
+//TODO KF6: this seems to have ended up barely used if at all. can be removed?
+// Investigate why almost identical api is used a lot on different frameworks
+// like Flutter but not in plamo apps.
+
+/**
+ * @brief An ApplicationWindow with a preconfigured PageRouter.
+ *
+ * In order to call functions on the PageRouter, use @link PageRouterAttached  the attached Kirigami.PageRouter object @endlink.
+ *
+ * @warning This might be removed in KF6 due to not being used a lot.
+ * @inherit kirigami::ApplicationWindow
+ */
+Kirigami.ApplicationWindow {
+    id: __kirigamiApplicationWindow
+
+    /**
+     * @see kirigami::PageRouter::routes
+     * @property list<Kirigami.PageRoute> route
+     */
+    default property alias routes: __kirigamiPageRouter.routes
+
+    /**
+     * @see kirigami::PageRouter::initialRoute
+     * @property string initialRoute
+     */
+    property alias initialRoute: __kirigamiPageRouter.initialRoute
+
+    /**
+     * @brief This property holds this window's PageRouter.
+     * @property kirigami::PageRouter
+     */
+    property alias router: __kirigamiPageRouter
+
+    Kirigami.PageRouter {
+        id: __kirigamiPageRouter
+        pageStack: __kirigamiApplicationWindow.pageStack.columnView
+    }
+}
diff --git a/src/controls/ScrollablePage.qml b/src/controls/ScrollablePage.qml
new file mode 100644 (file)
index 0000000..56416e9
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.19 as Kirigami
+import org.kde.kirigami.templates 2.2 as KT
+import "private"
+
+
+// TODO KF6: undo many workarounds to make existing code work?
+
+/**
+ * @brief ScrollablePage is a Page that holds scrollable content, such as a QtQuick.ListView.
+ *
+ * Scrolling and scrolling indicators will be automatically managed.
+ *
+ * Example usage:
+ * @code{.qml}
+ * ScrollablePage {
+ *     id: root
+ *     // The page will automatically be scrollable
+ *     Rectangle {
+ *         width: root.width
+ *         height: 99999
+ *     }
+ * }
+ * @endcode
+ * @warning Do not put a QtQuick.Controls.ScrollView inside of a ScrollablePage;
+ * children of a ScrollablePage are already inside a QtQuick.Controls.ScrollView.
+ *
+ * Another behavior added by this class is a "scroll down to refresh" behavior
+ * It also can give the contents of the flickable to have more top margins in order
+ * to make possible to scroll down the list to reach it with the thumb while using the
+ * phone with a single hand.
+ *
+ * Implementations should handle the refresh themselves as follows
+ *
+ * Example usage:
+ * @code{.qml}
+ * Kirigami.ScrollablePage {
+ *     id: view
+ *     supportsRefreshing: true
+ *     onRefreshingChanged: {
+ *         if (refreshing) {
+ *             myModel.refresh();
+ *         }
+ *     }
+ *     ListView {
+ *         // NOTE: MyModel doesn't come from the components,
+ *         // it's purely an example on how it can be used together
+ *         // some application logic that can update the list model
+ *         // and signals when it's done.
+ *         model: MyModel {
+ *             onRefreshDone: view.refreshing = false;
+ *         }
+ *         delegate: BasicListItem {}
+ *     }
+ * }
+ * [...]
+ * @endcode
+ */
+Kirigami.Page {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property specifies whether the list is asking for a refresh.
+     *
+     * This property will automatically be set to @c true when the user pulls the list down enough,
+     * which in return, shows a loading spinner. When this is set to true, it signals
+     * the application logic to start its refresh procedure.
+     *
+     * default: ``false``
+     *
+     * @note The application itself will have to set back this property to @c false when done.
+     */
+    property bool refreshing: false
+
+    /**
+     * @brief This property sets whether scrollable page supports "pull down to refresh" behaviour.
+     *
+     * default: ``false``
+     */
+    property bool supportsRefreshing: false
+
+    /**
+     * @brief This property holds the main Flickable item of this page.
+     * @deprecated here for compatibility; will be removed in KF6.
+     */
+    property Flickable flickable: Flickable {} // FIXME KF6: this empty flickable exists for compatibility reasons. some apps assume flickable exists right from the beginning but ScrollView internally assumes it does not
+    onFlickableChanged: scrollView.contentItem = flickable;
+
+    /**
+     * @brief This property sets the vertical scrollbar policy.
+     * @property Qt::ScrollBarPolicy verticalScrollBarPolicy
+     */
+    property int verticalScrollBarPolicy
+
+    /**
+     * @brief This property sets the horizontal scrollbar policy.
+     * @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
+     */
+    property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
+
+    default property alias scrollablePageData: itemsParent.data
+    property alias scrollablePageChildren: itemsParent.children
+
+    /*
+     * @deprecated here for compatibility; will be removed in KF6.
+     */
+    property QtObject mainItem
+    onMainItemChanged: {
+        print("Warning: the mainItem property is deprecated");
+        scrollablePageData.push(mainItem);
+    }
+
+    /**
+     * @brief This property sets whether it is possible to navigate the items in a view that support it.
+     *
+     * If true, and if the QtQuick.Flickable is in an item view (e.g. QtQuick.ListView, QtQuick.GridView),
+     * it will be possible to navigate the view current items with keyboard up/down arrow buttons.
+     * Also, any key event will be forwarded to the current list item.
+     *
+     * default: ``true``
+     */
+    property bool keyboardNavigationEnabled: true
+//END properties
+
+    contentHeight: flickable ? flickable.contentHeight : 0
+    implicitHeight: {
+        let height = contentHeight + topPadding + bottomPadding;
+        if (header && header.visible) {
+            height += header.implicitHeight;
+        }
+        if (footer && footer.visible) {
+            height += footer.implicitHeight;
+        }
+        return height;
+    }
+
+    implicitWidth: {
+        if (flickable) {
+            if (flickable.contentItem) {
+                return flickable.contentItem.implicitWidth;
+            } else {
+                return contentItem.implicitWidth + leftPadding + rightPadding;
+            }
+        } else {
+            return 0;
+        }
+    }
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: flickable && flickable.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
+
+    Keys.forwardTo: {
+        if (root.keyboardNavigationEnabled && root.flickable) {
+            if (("currentItem" in root.flickable) && root.flickable.currentItem) {
+                return [ root.flickable.currentItem, root.flickable ];
+            } else {
+                return [ root.flickable ];
+            }
+        } else {
+            return [];
+        }
+    }
+
+    contentItem: QQC2.ScrollView {
+        id: scrollView
+        anchors {
+            top: (root.header && root.header.visible)
+                    ? root.header.bottom
+                    // FIXME: for now assuming globalToolBarItem is in a Loader, which needs to be get rid of
+                    : (globalToolBarItem && globalToolBarItem.parent && globalToolBarItem.visible
+                        ? globalToolBarItem.parent.bottom
+                        : parent.top)
+            bottom: (root.footer && root.footer.visible) ? root.footer.top : parent.bottom
+            left: parent.left
+            right: parent.right
+            topMargin: root.refreshing ? busyIndicatorLoader.height : 0
+            Behavior on topMargin {
+                NumberAnimation {
+                    easing.type: Easing.InOutQuad
+                    duration: Kirigami.Units.longDuration
+                }
+            }
+        }
+        QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
+        QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
+    }
+
+    data: [
+        // Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
+        MouseArea {
+            id: scrollingArea
+            width: root.flickable.width
+            height: Math.max(root.flickable.height, implicitHeight)
+            implicitHeight: {
+                let impl = 0;
+                for (const i in itemsParent.visibleChildren) {
+                    const child = itemsParent.visibleChildren[i];
+                    if (child.implicitHeight <= 0) {
+                        impl = Math.max(impl, child.height);
+                    } else {
+                        impl = Math.max(impl, child.implicitHeight);
+                    }
+                }
+                return impl + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
+            }
+            Item {
+                id: itemsParent
+                property Flickable flickable
+                anchors {
+                    fill: parent
+                    leftMargin: root.leftPadding
+                    topMargin: root.topPadding
+                    rightMargin: root.rightPadding
+                    bottomMargin: root.bottomPadding
+                }
+                onChildrenChanged: {
+                    const child = children[children.length - 1];
+                    if (child instanceof QQC2.ScrollView) {
+                        print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
+                    }
+                }
+            }
+            Binding {
+                target: root.flickable
+                property: "bottomMargin"
+                value: root.bottomPadding
+                restoreMode: Binding.RestoreBinding
+            }
+        },
+
+        Loader {
+            id: busyIndicatorLoader
+            z: 99
+            y: root.flickable.verticalLayoutDirection === ListView.BottomToTop
+                ? -root.flickable.contentY + root.flickable.originY + height
+                : -root.flickable.contentY + root.flickable.originY - height
+            width: root.flickable.width
+            height: Kirigami.Units.gridUnit * 4
+            active: root.supportsRefreshing
+
+            sourceComponent: Item {
+                id: busyIndicatorFrame
+
+                QQC2.BusyIndicator {
+                    id: busyIndicator
+                    z: 1
+                    anchors.centerIn: parent
+                    running: root.refreshing
+                    visible: root.refreshing
+                    // Android busywidget QQC seems to be broken at custom sizes
+                }
+                Rectangle {
+                    id: spinnerProgress
+                    anchors {
+                        fill: busyIndicator
+                        margins: Kirigami.Units.smallSpacing
+                    }
+                    radius: width
+                    visible: supportsRefreshing && !refreshing && progress > 0
+                    color: "transparent"
+                    opacity: 0.8
+                    border.color: Kirigami.Theme.backgroundColor
+                    border.width: Kirigami.Units.smallSpacing
+                    property real progress: supportsRefreshing && !refreshing ? (busyIndicatorLoader.y / busyIndicatorFrame.height) : 0
+                }
+                GE.ConicalGradient {
+                    source: spinnerProgress
+                    visible: spinnerProgress.visible
+                    anchors.fill: spinnerProgress
+                    gradient: Gradient {
+                        GradientStop { position: 0.00; color: Kirigami.Theme.highlightColor }
+                        GradientStop { position: spinnerProgress.progress; color: Kirigami.Theme.highlightColor }
+                        GradientStop { position: spinnerProgress.progress + 0.01; color: "transparent" }
+                        GradientStop { position: 1.00; color: "transparent" }
+                    }
+                }
+
+                Connections {
+                    target: busyIndicatorLoader
+                    function onYChanged() {
+                        if (!supportsRefreshing) {
+                            return;
+                        }
+
+                        if (!root.refreshing && busyIndicatorLoader.y > busyIndicatorFrame.height / 2 + topPadding) {
+                            refreshTriggerTimer.running = true;
+                        } else {
+                            refreshTriggerTimer.running = false;
+                        }
+                    }
+                }
+                Timer {
+                    id: refreshTriggerTimer
+                    interval: 500
+                    onTriggered: {
+                        if (!root.refreshing && busyIndicatorLoader.y > busyIndicatorFrame.height / 2 + topPadding) {
+                            root.refreshing = true;
+                        }
+                    }
+                }
+            }
+        }
+    ]
+
+    Component.onCompleted: {
+        let flickableFound = false;
+        for (const i in itemsParent.data) {
+            const child = itemsParent.data[i];
+            if (child instanceof Flickable) {
+                // If there were more flickable children, take the last one, as behavior compatibility
+                // with old internal ScrollView
+                child.activeFocusOnTab = true;
+                root.flickable = child;
+                flickableFound = true;
+                if (child instanceof ListView) {
+                    child.keyNavigationEnabled = true;
+                    child.keyNavigationWraps = false;
+                }
+            } else if (child instanceof Item) {
+                child.anchors.left = itemsParent.left;
+                child.anchors.right = itemsParent.right;
+            } else if (child instanceof KT.OverlaySheet) {
+                // Reparent sheets, needs to be done before Component.onCompleted
+                if (child.parent === itemsParent || child.parent === null) {
+                    child.parent = root;
+                }
+            }
+        }
+
+        if (flickableFound) {
+            scrollView.contentItem = root.flickable;
+            root.flickable.parent = scrollView;
+            // The flickable needs focus only if the page didn't already explicitly set focus to some other control (eg a text field in the header)
+            Qt.callLater(() => {
+                if (root.activeFocus) {
+                    root.flickable.forceActiveFocus();
+                }
+            });
+            // Some existing code incorrectly uses anchors
+            root.flickable.anchors.fill = undefined;
+            root.flickable.anchors.left = undefined;
+            root.flickable.anchors.right = undefined;
+            root.flickable.anchors.top = undefined;
+            root.flickable.anchors.bottom = undefined;
+            scrollingArea.visible = false;
+        } else {
+            scrollView.contentItem = root.flickable;
+            scrollingArea.parent = root.flickable.contentItem;
+            scrollingArea.visible = true;
+            root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
+            root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
+        }
+        root.flickable.flickableDirection = Flickable.VerticalFlick;
+    }
+}
diff --git a/src/controls/SearchField.qml b/src/controls/SearchField.qml
new file mode 100644 (file)
index 0000000..5023a13
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
+ *  SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * @brief This is a standard QtQuick.Controls.TextField following the KDE HIG,
+ * which, by default, uses Ctrl+F as the focus keyboard shortcut
+ * and "Search…" as a placeholder text.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.20 as Kirigami
+ *
+ * Kirigami.SearchField {
+ *     id: searchField
+ *     onAccepted: console.log("Search text is " + searchField.text)
+ * }
+ * @endcode
+ * @inherit kirigami::ActionTextField
+ */
+Kirigami.ActionTextField {
+    id: root
+    /**
+     * @brief This property sets whether the accepted signal is fired automatically
+     * when the text is changed.
+     *
+     * Setting this to @c false will require that the user presses return or enter
+     * (similarly to QtQuick.TextInput).
+     *
+     * default: ``true``
+     *
+     * @since KDE Frameworks 5.81
+     * @since org.kde.kirigami 2.16
+     */
+    property bool autoAccept: true
+
+    /**
+     * @brief This property sets whether to delay automatic acceptance of the search input.
+     *
+     * Set this to @c true if your search is expensive (such as for online
+     * operations or in exceptionally slow data sets) and want to delay it
+     * for 2.5 seconds.
+     *
+     * @note If you must have immediate feedback (filter-style), use the
+     * text property directly instead of accepted()
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.81
+     * @since org.kde.kirigami 2.16
+     */
+    property bool delaySearch: false
+
+    // padding to accommodate search icon nicely
+    leftPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
+        return _rightActionsRow.width + Kirigami.Units.smallSpacing
+    } else {
+        return (activeFocus || root.text.length > 0 ? 0 : (searchIcon.width + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing * 2
+    }
+    rightPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
+        return (activeFocus || root.text.length > 0 ? 0 : (searchIcon.width + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing * 2
+    } else {
+        return _rightActionsRow.width + Kirigami.Units.smallSpacing
+    }
+
+    Kirigami.Icon {
+        id: searchIcon
+        opacity: root.activeFocus || text.length > 0 ? 0 : 1
+        LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
+        anchors.left: root.left
+        anchors.leftMargin: Kirigami.Units.smallSpacing * 2
+        anchors.verticalCenter: root.verticalCenter
+        anchors.verticalCenterOffset: Math.round((root.topPadding - root.bottomPadding) / 2)
+        implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
+        implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
+        color: root.placeholderTextColor
+
+        source: "search"
+
+        Behavior on opacity {
+            NumberAnimation {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+
+    placeholderText: qsTr("Search…")
+
+    Accessible.name: qsTr("Search")
+    Accessible.searchEdit: true
+
+    focusSequence: StandardKey.Find
+    inputMethodHints: Qt.ImhNoPredictiveText
+    EnterKey.type: Qt.EnterKeySearch
+    rightActions: [
+        Kirigami.Action {
+            //ltr confusingly refers to the direction of the arrow in the icon, not the text direction which it should be used in
+            icon.name: root.effectiveHorizontalAlignment === TextInput.AlignRight ? "edit-clear-locationbar-ltr" : "edit-clear-locationbar-rtl"
+            visible: root.text.length > 0
+            text: qsTr("Clear search")
+            onTriggered: {
+                root.clear();
+                // Since we are always sending the accepted signal here (whether or not the user has requested
+                // that the accepted signal be delayed), stop the delay timer that gets started by the text changing
+                // above, so that we don't end up sending two of those in rapid succession.
+                fireSearchDelay.stop();
+                root.accepted();
+            }
+        }
+    ]
+
+    Timer {
+        id: fireSearchDelay
+        interval: root.delaySearch ? Kirigami.Units.humanMoment : Kirigami.Units.shortDuration
+        running: false; repeat: false;
+        onTriggered: {
+            if (root.acceptableInput) {
+                root.accepted();
+            }
+        }
+    }
+    onAccepted: {
+        fireSearchDelay.running = false
+    }
+    onTextChanged: {
+        if (root.autoAccept) {
+            fireSearchDelay.restart();
+        } else {
+            fireSearchDelay.stop();
+        }
+    }
+}
diff --git a/src/controls/SelectableLabel.qml b/src/controls/SelectableLabel.qml
new file mode 100644 (file)
index 0000000..3dbe665
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
+ *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+
+/**
+ * @brief This is a label which supports text selection.
+ *
+ * You can use all elements of QtQuick.Controls.TextArea,
+ * in particular the "text" property to define the label text.
+ *
+ * Example usage:
+ * @code{.qml}
+ * ...
+ *     Kirigami.SelectableLabel {
+ *         text: "Label"
+ *     }
+ * ...
+ * @endcode
+ * @see https://bugreports.qt.io/browse/QTBUG-14077
+ * @since KDE Frameworks 5.95
+ * @since org.kde.kirigami 2.20
+ * @inherit QtQuick.Controls.TextArea
+ */
+QQC2.TextArea {
+    id: selectableLabel
+
+    /**
+     * @brief This property holds the cursor shape that will appear whenever
+     * the mouse is hovering over the label.
+     *
+     * default: @c Qt.IBeamCursor
+     *
+     * @property Qt::CursorShape cursorShape
+     */
+    property alias cursorShape: hoverHandler.cursorShape
+
+    padding: 0
+    leftPadding: 0
+    rightPadding: 0
+    topPadding: 0
+    bottomPadding: 0
+
+    activeFocusOnTab: false
+    readOnly: true
+    wrapMode: Text.WordWrap
+    textFormat: TextEdit.AutoText
+    verticalAlignment: TextEdit.AlignTop
+
+    Accessible.selectableText: true
+    Accessible.editable: false
+
+    background: Item {}
+
+    HoverHandler {
+        id: hoverHandler
+        // By default HoverHandler accepts the left button while it shouldn't accept anything,
+        // causing https://bugreports.qt.io/browse/QTBUG-106489.
+        // Qt.NoButton unfortunately is not a valid value for acceptedButtons.
+        // Disabling masks the problem, but
+        // there is no proper workaround other than an upstream fix
+        // See qqc2-desktop-style Label.qml
+        enabled: false
+        cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
+    }
+}
diff --git a/src/controls/Separator.qml b/src/controls/Separator.qml
new file mode 100644 (file)
index 0000000..c598ded
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.12 as Kirigami
+
+/**
+ * @brief A visual separator.
+ *
+ * Useful for splitting one set of items from another.
+ *
+ * @inherit QtQuick.Rectangle
+ */
+Rectangle {
+    id: root
+    implicitHeight: 1
+    implicitWidth: 1
+    Accessible.role: Accessible.Separator
+
+    enum Weight {
+        Light,
+        Normal
+    }
+
+    /**
+     * @brief This property holds the visual weight of the separator.
+     *
+     * The following values are allowed:
+     * * ``Kirigami.Separator.Weight.Light``
+     * * ``Kirigami.Separator.Weight.Normal``
+     *
+     * default: ``Separator.Weight.Normal``
+     *
+     * @since KDE Frameworks 5.72
+     * @since org.kde.kirigami 2.12
+     */
+    property int weight: Separator.Weight.Normal
+
+    /* TODO: If we get a separator color role, change this to
+     * mix weights lower than Normal with the background color
+     * and mix weights higher than Normal with the text color.
+     */
+    color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, weight === Separator.Weight.Light ? 0.07 : 0.15)
+}
diff --git a/src/controls/ShadowedImage.qml b/src/controls/ShadowedImage.qml
new file mode 100644 (file)
index 0000000..8f44927
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *  SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.12 as Kirigami
+
+/**
+ * @brief An image with a shadow.
+ *
+ * This item will render a image, with a shadow below it. The rendering is done
+ * using distance fields, which provide greatly improved performance. The shadow is
+ * rendered outside of the item's bounds, so the item's width and height are the
+ * don't include the shadow.
+ *
+ * Example usage:
+ * @code{.qml}
+ * import org.kde.kirigami 2.19
+ *
+ * ShadowedImage {
+ *     source: 'qrc:/myKoolGearPicture.png'
+ *
+ *     radius: 20
+ *
+ *     shadow.size: 20
+ *     shadow.xOffset: 5
+ *     shadow.yOffset: 5
+ *
+ *     border.width: 2
+ *     border.color: Kirigami.Theme.textColor
+ *
+ *     corners.topLeftRadius: 4
+ *     corners.topRightRadius: 5
+ *     corners.bottomLeftRadius: 2
+ *     corners.bottomRightRadius: 10
+ * }
+ * @endcode
+ * @since KDE Frameworks 5.69
+ * @since org.kde.kirigami 2.12
+ * @inherit QtQuick.Item
+ */
+Item {
+
+//BEGIN properties
+    /**
+     * @brief This property holds the color that will be underneath the image.
+     *
+     * This will be visible if the image has transparancy.
+     *
+     * @see kirigami::ShadowedRectangle::radius
+     * @property color color
+     */
+    property alias color: shadowRectangle.color
+
+    /**
+     * @brief This propery holds the corner radius of the image.
+     * @see kirigami::ShadowedRectangle::radius
+     * @property real radius
+     */
+    property alias radius: shadowRectangle.radius
+
+    /**
+     * @brief This property holds shadow's properties group.
+     * @see kirigami::ShadowedRectangle::shadow
+     * @property kirigami::ShadowedRectangle::ShadowGroup shadow
+     */
+    property alias shadow: shadowRectangle.shadow
+
+    /**
+     * @brief This propery holds the border's properties of the image.
+     * @see kirigami::ShadowedRectangle::border
+     * @property kirigami::ShadowedRectangle::BorderGroup border
+     */
+    property alias border: shadowRectangle.border
+
+    /**
+     * @brief This propery holds the corner radius properties of the image.
+     * @see kirigami::ShadowedRectangle::corners
+     * @property kirigami::ShadowedRectangle::CornersGroup corners
+     */
+    property alias corners: shadowRectangle.corners
+
+    /**
+     * @brief This propery holds the source of the image.
+     * @brief QtQuick.Image.source
+     */
+    property alias source: image.source
+
+    /**
+     * @brief This property sets whether this image should be loaded asynchronously.
+     *
+     * Set this to @c false if you want the main thread to load the image, which
+     * blocks it until the image is loaded. Setting this to @c true loads the
+     * image in a separate thread which is useful when maintaining a responsive
+     * user interface is more desirable than having images immediately visible.
+     *
+     * @see QtQuick.Image.asynchronous
+     * @property bool asynchronous
+     */
+    property alias asynchronous: image.asynchronous
+
+    /**
+     * @brief This property defines what happens when the source image has a different
+     * size than the item.
+     * @see QtQuick.Image.fillMode
+     * @property int fillMode
+     */
+    property alias fillMode: image.fillMode
+
+    /**
+     * @brief This property holds the scaled width and height of the full-frame image.
+     * @see QtQuick.Image.sourceSize
+     */
+    property alias sourceSize: image.sourceSize
+//END properties
+
+    Image {
+        id: image
+        anchors.fill: parent
+        visible: shadowRectangle.softwareRendering
+    }
+
+    Kirigami.ShadowedTexture {
+        id: shadowRectangle
+        anchors.fill: parent
+
+        source: image.status === Image.Ready ? image : null
+    }
+}
diff --git a/src/controls/SwipeListItem.qml b/src/controls/SwipeListItem.qml
new file mode 100644 (file)
index 0000000..68eebad
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "private" as P
+import "templates" as T
+
+/**
+ * An item delegate that shows actions on the right side, which are, on mobile mode,
+ * obtainable by dragging away the item with the handle. If the app is not in mobile mode,
+ * the actions are always shown to the user.
+ *
+ * Example usage:
+ * @code{.qml}
+ * ListView {
+ *     model: myModel
+ *     delegate: SwipeListItem {
+ *         QQC2.Label {
+ *             text: model.text
+ *         }
+ *         actions: [
+ *              Action {
+ *                  icon.name: "document-decrypt"
+ *                  onTriggered: print("Action 1 clicked")
+ *              },
+ *              Action {
+ *                  icon.name: model.action2Icon
+ *                  onTriggered: //do something
+ *              }
+ *         ]
+ *     }
+ *
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/hig/components/editing/list">KDE Human Interface Guidelines on List Views and List Items</a>
+ * @inherit kirigami::templates::SwipeListItem
+ */
+T.SwipeListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {}
+}
diff --git a/src/controls/ToolBarApplicationHeader.qml b/src/controls/ToolBarApplicationHeader.qml
new file mode 100644 (file)
index 0000000..563fb06
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+import "private" as P
+
+
+// TODO KF6: Remove!
+/**
+ * @brief ToolBarApplicationHeader represents a toolbar that
+ * will display the actions of the current page.
+ *
+ * Both Contextual actions and the main, left and right actions
+ *
+ * @deprecated This will be removed in KF6.
+ */
+ApplicationHeader {
+    id: header
+
+    preferredHeight: 42
+    maximumHeight: preferredHeight
+    headerStyle: ApplicationHeaderStyle.Titles
+
+    // FIXME: needs a property definition to have its own type in qml
+    /** @internal */
+    property string _internal: ""
+
+    Component.onCompleted: print("Warning: ToolbarApplicationHeader is deprecated, remove and use the automatic internal toolbar instead.")
+    pageDelegate: Item {
+        id: delegateItem
+        readonly property bool current: __appWindow.pageStack.currentIndex === index
+        implicitWidth: titleTextMetrics.width/2 + buttonTextMetrics.collapsedButtonsWidth
+
+        RowLayout {
+            id: titleLayout
+            anchors {
+                verticalCenter: parent.verticalCenter
+                left: parent.left
+                right: actionsLayout.left
+            }
+            Kirigami.Separator {
+                id: separator
+                Layout.preferredHeight: parent.height * 0.6
+            }
+
+            Kirigami.Heading {
+                id: title
+                Layout.fillWidth: true
+
+                Layout.preferredWidth: implicitWidth
+                Layout.minimumWidth: Math.min(titleTextMetrics.width, delegateItem.width - buttonTextMetrics.requiredWidth)
+                leftPadding: Kirigami.Units.largeSpacing
+                opacity: delegateItem.current ? 1 : 0.4
+                maximumLineCount: 1
+                color: Kirigami.Theme.textColor
+                elide: Text.ElideRight
+                text: page ? page.title : ""
+            }
+        }
+
+        TextMetrics {
+            id: titleTextMetrics
+            text: page ? page.title : ""
+            font: title.font
+        }
+        TextMetrics {
+            id: buttonTextMetrics
+            text: (page.actions.left ? page.actions.left.text : "") + (page.actions.main ? page.actions.main.text : "") + (page.actions.right ? page.actions.right.text : "")
+            readonly property int collapsedButtonsWidth: ctxActionsButton.width + (page.actions.left ? ctxActionsButton.width + Kirigami.Units.gridUnit : 0) + (page.actions.main ? ctxActionsButton.width + Kirigami.Units.gridUnit : 0) + (page.actions.right ? ctxActionsButton.width + Kirigami.Units.gridUnit : 0)
+            readonly property int requiredWidth: width + collapsedButtonsWidth
+        }
+
+        RowLayout {
+            id: actionsLayout
+            anchors {
+                verticalCenter: parent.verticalCenter
+                right: ctxActionsButton.visible ? ctxActionsButton.left : parent.right
+            }
+
+            readonly property bool toobig: delegateItem.width - titleTextMetrics.width - Kirigami.Units.gridUnit < buttonTextMetrics.requiredWidth
+
+            P.PrivateActionToolButton {
+                Layout.alignment: Qt.AlignVCenter
+                action: page && page.actions ? page.actions.left : null
+                display: parent.toobig ? QQC2.AbstractButton.IconOnly : QQC2.AbstractButton.TextBesideIcon
+            }
+            P.PrivateActionToolButton {
+                Layout.alignment: Qt.AlignVCenter
+                Layout.rightMargin: Kirigami.Units.smallSpacing
+                action: page && page.actions ? page.actions.main : null
+                display: parent.toobig ? QQC2.AbstractButton.IconOnly : QQC2.AbstractButton.TextBesideIcon
+                flat: false
+            }
+            P.PrivateActionToolButton {
+                Layout.alignment: Qt.AlignVCenter
+                action: page && page.actions ? page.actions.right : null
+                display: parent.toobig ? QQC2.AbstractButton.IconOnly : QQC2.AbstractButton.TextBesideIcon
+            }
+        }
+
+        P.PrivateActionToolButton {
+            id: ctxActionsButton
+            showMenuArrow: page.actions.contextualActions.length === 1
+            anchors {
+                right: parent.right
+                verticalCenter: parent.verticalCenter
+                rightMargin: Kirigami.Units.smallSpacing
+            }
+            Kirigami.Action {
+                id: overflowAction
+                icon.name: "overflow-menu"
+                tooltip: qsTr("More Actions")
+                visible: children.length > 0
+                children: page && page.actions.contextualActions ? page.actions.contextualActions : null
+            }
+
+            action: page && page.actions.contextualActions.length === 1 ? page.actions.contextualActions[0] : overflowAction
+        }
+    }
+}
diff --git a/src/controls/UrlButton.qml b/src/controls/UrlButton.qml
new file mode 100644 (file)
index 0000000..8e7ff5a
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.2
+import org.kde.kirigami 2.20 as Kirigami
+import org.kde.kirigami.private 2.6 as KirigamiPrivate
+import QtQuick.Controls 2.1 as QQC2
+
+/**
+ * @brief A link button that contains a URL.
+ *
+ * It will open the URL by default, allowing to copy it if triggered with the
+ * secondary mouse button.
+ *
+ * @since KDE Frameworks 5.63
+ * @since org.kde.kirigami 2.6
+ * @inherit QtQuick.LinkButton
+ */
+Kirigami.LinkButton {
+    id: button
+
+    /**
+     * @brief This property holds the URL the button links to.
+     */
+    property string url
+
+    text: url
+    enabled: !!url
+    visible: text.length > 0
+    acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+    Accessible.name: button.text !== button.url ? button.text : button.url
+    Accessible.description: i18nc("@info:whatsthis", "Open link %1", button.text !== button.url ? button.url : "")
+
+    onPressed: if (mouse.button === Qt.RightButton) {
+        menu.popup()
+    }
+    onClicked: if (mouse.button !== Qt.RightButton) {
+        Qt.openUrlExternally(url)
+    }
+
+    QQC2.ToolTip {
+        // If button's text has been overridden, show a tooltip to expose the raw URL
+        visible: button.text !== button.url && button.mouseArea.containsMouse
+        text: url
+    }
+
+    QQC2.Menu {
+        id: menu
+        QQC2.MenuItem {
+            text: qsTr("Copy Link to Clipboard")
+            icon.name: "edit-copy"
+            onClicked: KirigamiPrivate.CopyHelperPrivate.copyTextToClipboard(button.url)
+        }
+    }
+}
diff --git a/src/controls/private/ActionButton.qml b/src/controls/private/ActionButton.qml
new file mode 100644 (file)
index 0000000..269fc04
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.0 as QQC2
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.16 as Kirigami
+
+Item {
+    id: root
+
+    anchors {
+        left: parent.left
+        right: parent.right
+        bottom: parent.bottom
+        bottomMargin: root.page.footer ? root.page.footer.height : 0
+    }
+    //smallSpacing for the shadow
+    implicitHeight: button.height + Kirigami.Units.smallSpacing
+    clip: true
+
+    readonly property Kirigami.Page page: root.parent.page
+    //either Action or QAction should work here
+
+    function isActionAvailable(action) { return action && (action.hasOwnProperty("visible") ? action.visible === undefined || action.visible : !action.hasOwnProperty("visible")); }
+
+    readonly property QtObject action: root.page && isActionAvailable(root.page.mainAction) ? root.page.mainAction : null
+    readonly property QtObject leftAction: root.page && isActionAvailable(root.page.leftAction) ? root.page.leftAction : null
+    readonly property QtObject rightAction: root.page && isActionAvailable(root.page.rightAction) ? root.page.rightAction : null
+
+    readonly property bool hasApplicationWindow: typeof applicationWindow !== "undefined" && applicationWindow
+    readonly property bool hasGlobalDrawer: typeof globalDrawer !== "undefined" && globalDrawer
+    readonly property bool hasContextDrawer: typeof contextDrawer !== "undefined" && contextDrawer
+
+    transform: Translate {
+        id: translateTransform
+    }
+
+    states: [
+        State {
+            when: mouseArea.internalVisibility
+            PropertyChanges {
+                target: translateTransform
+                y: 0
+            }
+            PropertyChanges {
+                target: root
+                opacity: 1
+            }
+            PropertyChanges {
+                target: root
+                visible: true
+            }
+        },
+        State {
+            when: !mouseArea.internalVisibility
+            PropertyChanges {
+                target: translateTransform
+                y: button.height
+            }
+            PropertyChanges {
+                target: root
+                opacity: 0
+            }
+            PropertyChanges {
+                target: root
+                visible: false
+            }
+        }
+    ]
+    transitions: Transition {
+        ParallelAnimation {
+            NumberAnimation {
+                target: translateTransform
+                property: "y"
+                duration: Kirigami.Units.longDuration
+                easing.type: mouseArea.internalVisibility ? Easing.InQuad : Easing.OutQuad
+            }
+            OpacityAnimator {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+
+    onWidthChanged: button.x = Qt.binding(() => (root.width / 2 - button.width / 2))
+    Item {
+        id: button
+        x: root.width/2 - button.width/2
+
+        property int mediumIconSizing: Kirigami.Units.iconSizes.medium
+        property int largeIconSizing: Kirigami.Units.iconSizes.large
+
+        anchors.bottom: edgeMouseArea.bottom
+
+        implicitWidth: implicitHeight + mediumIconSizing*2 + Kirigami.Units.gridUnit
+        implicitHeight: largeIconSizing + Kirigami.Units.largeSpacing*2
+
+
+        onXChanged: {
+            if (mouseArea.pressed || edgeMouseArea.pressed || fakeContextMenuButton.pressed) {
+                if (root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal) {
+                    globalDrawer.peeking = true;
+                    globalDrawer.visible = true;
+                    if (Qt.application.layoutDirection === Qt.LeftToRight) {
+                        globalDrawer.position = Math.min(1, Math.max(0, (x - root.width/2 + button.width/2)/globalDrawer.contentItem.width + mouseArea.drawerShowAdjust));
+                    } else {
+                        globalDrawer.position = Math.min(1, Math.max(0, (root.width/2 - button.width/2 - x)/globalDrawer.contentItem.width + mouseArea.drawerShowAdjust));
+                    }
+                }
+                if (root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal) {
+                    contextDrawer.peeking = true;
+                    contextDrawer.visible = true;
+                    if (Qt.application.layoutDirection === Qt.LeftToRight) {
+                        contextDrawer.position = Math.min(1, Math.max(0, (root.width/2 - button.width/2 - x)/contextDrawer.contentItem.width + mouseArea.drawerShowAdjust));
+                    } else {
+                        contextDrawer.position = Math.min(1, Math.max(0, (x - root.width/2 + button.width/2)/contextDrawer.contentItem.width + mouseArea.drawerShowAdjust));
+                    }
+                }
+            }
+        }
+
+        MouseArea {
+            id: mouseArea
+            anchors.fill: parent
+
+            visible: action !== null || leftAction !== null || rightAction !== null
+            property bool internalVisibility: (!root.hasApplicationWindow || (applicationWindow().controlsVisible && applicationWindow().height > root.height*2)) && (root.action === null || root.action.visible === undefined || root.action.visible)
+            preventStealing: true
+
+            drag {
+                target: button
+                //filterChildren: true
+                axis: Drag.XAxis
+                minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2
+                maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2
+            }
+
+            property var downTimestamp;
+            property int startX
+            property int startMouseY
+            property real drawerShowAdjust
+
+            readonly property int currentThird: (3*mouseX)/width
+            readonly property QtObject actionUnderMouse: {
+                switch(currentThird) {
+                    case 0: return leftAction;
+                    case 1: return action;
+                    case 2: return rightAction;
+                    default: return null
+                }
+            }
+
+            hoverEnabled: true
+
+            QQC2.ToolTip.visible: containsMouse && !Kirigami.Settings.tabletMode && actionUnderMouse
+            QQC2.ToolTip.text: actionUnderMouse ? actionUnderMouse.text : ""
+            QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+
+            onPressed: mouse => {
+                // search if we have a page to set to current
+                if (root.hasApplicationWindow && applicationWindow().pageStack.currentIndex !== undefined && root.page.Kirigami.ColumnView.level !== undefined) {
+                    // search the button parent's parent, that is the page parent
+                    // this will make the context drawer open for the proper page
+                    applicationWindow().pageStack.currentIndex = root.page.Kirigami.ColumnView.level;
+                }
+                downTimestamp = (new Date()).getTime();
+                startX = button.x + button.width/2;
+                startMouseY = mouse.y;
+                drawerShowAdjust = 0;
+            }
+            onReleased: mouse => {
+                tooltipHider.restart();
+                if (root.hasGlobalDrawer) globalDrawer.peeking = false;
+                if (root.hasContextDrawer) contextDrawer.peeking = false;
+                // pixel/second
+                const x = button.x + button.width/2;
+                const speed = ((x - startX) / ((new Date()).getTime() - downTimestamp) * 1000);
+                drawerShowAdjust = 0;
+
+                // project where it would be a full second in the future
+                if (root.hasContextDrawer && root.hasGlobalDrawer && globalDrawer.modal && x + speed > Math.min(root.width/4*3, root.width/2 + globalDrawer.contentItem.width/2)) {
+                    globalDrawer.open();
+                    contextDrawer.close();
+                } else if (root.hasContextDrawer && x + speed < Math.max(root.width/4, root.width/2 - contextDrawer.contentItem.width/2)) {
+                    if (root.hasContextDrawer && contextDrawer.modal) {
+                        contextDrawer.open();
+                    }
+                    if (root.hasGlobalDrawer && globalDrawer.modal) {
+                        globalDrawer.close();
+                    }
+                } else {
+                    if (root.hasGlobalDrawer && globalDrawer.modal) {
+                        globalDrawer.close();
+                    }
+                    if (root.hasContextDrawer && contextDrawer.modal) {
+                        contextDrawer.close();
+                    }
+                }
+                // Don't rely on native onClicked, but fake it here:
+                // Qt.startDragDistance is not adapted to devices dpi in case
+                // of Android, so consider the button "clicked" when:
+                // *the button has been dragged less than a gridunit
+                // *the finger is still on the button
+                if (Math.abs((button.x + button.width/2) - startX) < Kirigami.Units.gridUnit &&
+                    mouse.y > 0) {
+
+                    //if an action has been assigned, trigger it
+                    if (actionUnderMouse && actionUnderMouse.trigger) {
+                        actionUnderMouse.trigger();
+                    }
+
+                    if (actionUnderMouse && actionUnderMouse.hasOwnProperty("children") && actionUnderMouse.children.length > 0) {
+                        let subMenuUnderMouse;
+                        switch (actionUnderMouse) {
+                        case leftAction:
+                            subMenuUnderMouse = leftActionSubMenu;
+                            break;
+                        case mainAction:
+                            subMenuUnderMouse = mainActionSubMenu;
+                            break
+                        case rightAction:
+                            subMenuUnderMouse = rightActionSubMenu;
+                            break;
+                        }
+                        if (subMenuUnderMouse && !subMenuUnderMouse.visible) {
+                            subMenuUnderMouse.visible = true;
+                        }
+                    }
+                }
+            }
+
+            onPositionChanged: mouse => {
+                drawerShowAdjust = Math.min(0.3, Math.max(0, (startMouseY - mouse.y)/(Kirigami.Units.gridUnit*15)));
+                button.xChanged();
+            }
+            onPressAndHold: mouse => {
+                if (!actionUnderMouse) {
+                    return;
+                }
+
+                // if an action has been assigned, show a message like a tooltip
+                if (actionUnderMouse && actionUnderMouse.text && Kirigami.Settings.tabletMode) {
+                    tooltipHider.stop();
+                    QQC2.ToolTip.show(actionUnderMouse.text);
+                    // The tooltip is shown perpetually while we are pressed and held, and
+                    // we start tooltipHider below when the press is released. This ensures
+                    // that the user can have as much time as they want to read the tooltip,
+                    // and also that the tooltip is hidden in a pleasant manner that does
+                    // not feel overly urgent.
+                }
+            }
+            Timer {
+                id: tooltipHider
+                interval: Kirigami.Units.humanMoment
+                onTriggered: {
+                    mouseArea.QQC2.ToolTip.hide();
+                }
+            }
+            Connections {
+                target: root.hasGlobalDrawer ? globalDrawer : null
+                function onPositionChanged() {
+                    if ( globalDrawer && globalDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) {
+                        if (Qt.application.layoutDirection === Qt.LeftToRight) {
+                            button.x = globalDrawer.contentItem.width * globalDrawer.position + root.width/2 - button.width/2;
+                        } else {
+                            button.x = -globalDrawer.contentItem.width * globalDrawer.position + root.width/2 - button.width/2
+                        }
+                    }
+                }
+            }
+            Connections {
+                target: root.hasContextDrawer ? contextDrawer : null
+                function onPositionChanged() {
+                    if (contextDrawer && contextDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) {
+                        if (Qt.application.layoutDirection === Qt.LeftToRight) {
+                            button.x = root.width/2 - button.width/2 - contextDrawer.contentItem.width * contextDrawer.position;
+                        } else {
+                            button.x = root.width/2 - button.width/2 + contextDrawer.contentItem.width * contextDrawer.position;
+                        }
+                    }
+                }
+            }
+
+            Item {
+                id: background
+                anchors {
+                    fill: parent
+                }
+
+                Rectangle {
+                    id: buttonGraphics
+                    radius: width/2
+                    anchors.centerIn: parent
+                    height: parent.height - Kirigami.Units.smallSpacing*2
+                    width: height
+                    enabled: root.action && root.action.enabled
+                    visible: root.action
+                    readonly property bool pressed: root.action && root.action.enabled && ((root.action === mouseArea.actionUnderMouse && mouseArea.pressed) || root.action.checked)
+                    property color baseColor: root.action && root.action.icon && root.action.icon.color && root.action.icon.color !== undefined && root.action.icon.color.a > 0 ? root.action.icon.color : Kirigami.Theme.highlightColor
+                    color: pressed ? Qt.darker(baseColor, 1.3) : baseColor
+
+                    ActionsMenu {
+                        id: mainActionSubMenu
+                        y: -height
+                        x: -width/2 + parent.width/2
+                        actions: root.action && root.action.hasOwnProperty("children") ? root.action.children : ""
+                        submenuComponent: Component {
+                            ActionsMenu {}
+                        }
+                    }
+                    Kirigami.Icon {
+                        id: icon
+                        anchors.centerIn: parent
+                        width: button.mediumIconSizing
+                        height: width
+                        source: root.action && root.action.icon.name ? root.action.icon.name : ""
+                        selected: true
+                        color: root.action && root.action.icon && root.action.icon.color && root.action.icon.color.a > 0 ? root.action.icon.color : (selected ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor)
+                    }
+                    Behavior on color {
+                        ColorAnimation {
+                            duration: Kirigami.Units.shortDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                    Behavior on x {
+                        NumberAnimation {
+                            duration: Kirigami.Units.longDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                }
+                // left button
+                Rectangle {
+                    id: leftButtonGraphics
+                    z: -1
+                    anchors {
+                        left: parent.left
+                        bottom: parent.bottom
+                        bottomMargin: Kirigami.Units.smallSpacing
+                    }
+                    enabled: root.leftAction && root.leftAction.enabled
+                    radius: 2
+                    height: button.mediumIconSizing + Kirigami.Units.smallSpacing * 2
+                    width: height + (root.action ? Kirigami.Units.gridUnit*2 : 0)
+                    visible: root.leftAction
+
+                    readonly property bool pressed: root.leftAction && root.leftAction.enabled && ((mouseArea.actionUnderMouse === root.leftAction && mouseArea.pressed) || root.leftAction.checked)
+                    property color baseColor: root.leftAction && root.leftAction.icon && root.leftAction.icon.color && root.leftAction.icon.color !== undefined && root.leftAction.icon.color.a > 0 ? root.leftAction.icon.color : Kirigami.Theme.highlightColor
+                    color: pressed ? baseColor : Kirigami.Theme.backgroundColor
+                    Behavior on color {
+                        ColorAnimation {
+                            duration: Kirigami.Units.shortDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                    ActionsMenu {
+                        id: leftActionSubMenu
+                        y: -height
+                        x: -width/2 + parent.width/2
+                        actions: root.leftAction && root.leftAction.hasOwnProperty("children") ? root.leftAction.children : ""
+                        submenuComponent: Component {
+                            ActionsMenu {}
+                        }
+                    }
+                    Kirigami.Icon {
+                        source: root.leftAction && root.leftAction.icon.name ? root.leftAction.icon.name : ""
+                        width: button.mediumIconSizing
+                        height: width
+                        selected: leftButtonGraphics.pressed
+                        color: root.leftAction && root.leftAction.icon && root.leftAction.icon.color && root.leftAction.icon.color.a > 0 ? root.leftAction.icon.color : (selected ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor)
+                        anchors {
+                            left: parent.left
+                            verticalCenter: parent.verticalCenter
+                            margins: root.action ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.smallSpacing
+                        }
+                    }
+                }
+                //right button
+                Rectangle {
+                    id: rightButtonGraphics
+                    z: -1
+                    anchors {
+                        right: parent.right
+                        // verticalCenter: parent.verticalCenter
+                        bottom: parent.bottom
+                        bottomMargin: Kirigami.Units.smallSpacing
+                    }
+                    enabled: root.rightAction && root.rightAction.enabled
+                    radius: 2
+                    height: button.mediumIconSizing + Kirigami.Units.smallSpacing * 2
+                    width: height + (root.action ? Kirigami.Units.gridUnit*2 : 0)
+                    visible: root.rightAction
+                    readonly property bool pressed: root.rightAction && root.rightAction.enabled && ((mouseArea.actionUnderMouse === root.rightAction && mouseArea.pressed) || root.rightAction.checked)
+                    property color baseColor: root.rightAction && root.rightAction.icon && root.rightAction.icon.color && root.rightAction.icon.color !== undefined && root.rightAction.icon.color.a > 0 ? root.rightAction.icon.color : Kirigami.Theme.highlightColor
+                    color: pressed ? baseColor : Kirigami.Theme.backgroundColor
+                    Behavior on color {
+                        ColorAnimation {
+                            duration: Kirigami.Units.shortDuration
+                            easing.type: Easing.InOutQuad
+                        }
+                    }
+                    ActionsMenu {
+                        id: rightActionSubMenu
+                        y: -height
+                        x: -width/2 + parent.width/2
+                        actions: root.rightAction && root.rightAction.hasOwnProperty("children") ? root.rightAction.children : ""
+                        submenuComponent: Component {
+                            ActionsMenu {}
+                        }
+                    }
+                    Kirigami.Icon {
+                        source: root.rightAction && root.rightAction.icon.name ? root.rightAction.icon.name : ""
+                        width: button.mediumIconSizing
+                        height: width
+                        selected: rightButtonGraphics.pressed
+                        color: root.rightAction && root.rightAction.icon && root.rightAction.icon.color && root.rightAction.icon.color.a > 0 ? root.rightAction.icon.color : (selected ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor)
+                        anchors {
+                            right: parent.right
+                            verticalCenter: parent.verticalCenter
+                            margins: root.action ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.smallSpacing
+                        }
+                    }
+                }
+            }
+
+            GE.DropShadow {
+                anchors.fill: background
+                horizontalOffset: 0
+                verticalOffset: 1
+                radius: Kirigami.Units.gridUnit /2
+                samples: 16
+                color: Qt.rgba(0, 0, 0, mouseArea.pressed ? 0.6 : 0.4)
+                source: background
+            }
+        }
+    }
+
+    MouseArea {
+        id: fakeContextMenuButton
+        anchors {
+            right: edgeMouseArea.right
+            bottom: parent.bottom
+            margins: Kirigami.Units.smallSpacing
+        }
+        drag {
+            target: button
+            axis: Drag.XAxis
+            minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2
+            maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2
+        }
+        visible: root.page.actions && root.page.actions.contextualActions.length > 0 && ((typeof applicationWindow === "undefined") || applicationWindow().wideScreen)
+            // using internal pagerow api
+            && ((typeof applicationWindow !== "undefined") && root.page && root.page.parent ? root.page.Kirigami.ColumnView.level < applicationWindow().pageStack.depth-1 : (typeof applicationWindow === "undefined"))
+
+        width: button.mediumIconSizing + Kirigami.Units.smallSpacing*2
+        height: width
+
+
+        GE.DropShadow {
+            anchors.fill: handleGraphics
+            horizontalOffset: 0
+            verticalOffset: 1
+            radius: Kirigami.Units.gridUnit /2
+            samples: 16
+            color: Qt.rgba(0, 0, 0, fakeContextMenuButton.pressed ? 0.6 : 0.4)
+            source: handleGraphics
+        }
+        Rectangle {
+            id: handleGraphics
+            anchors.fill: parent
+            color: fakeContextMenuButton.pressed ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
+            radius: 1
+            Kirigami.Icon {
+                anchors.centerIn: parent
+                width: button.mediumIconSizing
+                selected: fakeContextMenuButton.pressed
+                height: width
+                source: "overflow-menu"
+            }
+            Behavior on color {
+                ColorAnimation {
+                    duration: Kirigami.Units.shortDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+
+        onPressed: mouse => {
+            mouseArea.onPressed(mouse)
+        }
+        onReleased: mouse => {
+            const pos = root.mapFromItem(fakeContextMenuButton, mouse.x, mouse.y);
+
+            if ((typeof contextDrawer !== "undefined") && contextDrawer) {
+                contextDrawer.peeking = false;
+
+                if (pos.x < root.width/2) {
+                    contextDrawer.open();
+                } else if (contextDrawer.drawerOpen && mouse.x > 0 && mouse.x < width) {
+                    contextDrawer.close();
+                }
+            }
+
+            if ((typeof globalDrawer !== "undefined") && globalDrawer) {
+                globalDrawer.peeking = false;
+
+                if (globalDrawer.position > 0.5) {
+                    globalDrawer.open();
+                } else {
+                    globalDrawer.close();
+                }
+            }
+            if (containsMouse && ((typeof globalDrawer === "undefined") || !globalDrawer || !globalDrawer.drawerOpen || !globalDrawer.modal) &&
+                ((typeof contextDrawer === "undefined") || !contextDrawer || !contextDrawer.drawerOpen || !contextDrawer.modal)) {
+                contextMenu.visible = !contextMenu.visible;
+            }
+        }
+        ActionsMenu {
+            id: contextMenu
+            x: parent.width - width
+            y: -height
+            actions: root.page.actions.contextualActions
+            submenuComponent: Component {
+                ActionsMenu {}
+            }
+        }
+    }
+
+    MouseArea {
+        id: edgeMouseArea
+        z:99
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: parent.bottom
+        }
+        drag {
+            target: button
+            //filterChildren: true
+            axis: Drag.XAxis
+            minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2
+            maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2
+        }
+        height: Kirigami.Units.smallSpacing * 3
+
+        onPressed: mouse => mouseArea.onPressed(mouse)
+        onPositionChanged: mouse => mouseArea.positionChanged(mouse)
+        onReleased: mouse => mouseArea.released(mouse)
+    }
+}
diff --git a/src/controls/private/ActionIconGroup.qml b/src/controls/private/ActionIconGroup.qml
new file mode 100644 (file)
index 0000000..d4c6229
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQml 2.1
+
+/**
+ * @brief A grouped property with properties that describe an action icon.
+ *
+ * @note Depending on the implementation, if a Freedesktop standard icon with the
+ * specified name is not found, the ``source`` property will be used instead.
+ */
+QtObject {
+    /**
+     * @brief This property holds a Freedesktop standard icon name.
+     *
+     * The icon will be loaded from the selected icon theme, which can be set
+     * by the platform or included with the app.
+     *
+     * @see kirigami::Icon::source
+     */
+    property string name
+
+    /**
+     * @brief This property holds the icon source.
+     *
+     * The icon will be loaded as a regular image.
+     *
+     * @see kirigami::Icon::source
+     */
+    property string source
+
+    /**
+     * @brief This property holds the icon tint color.
+     *
+     * The icon is tinted with the specified color, unless the color is set to "transparent".
+     *
+     * default: ``transparent``
+     *
+     * @see kirigami::Icon::color
+     */
+    property color color: "transparent"
+
+    property int width
+    property int height
+}
+
diff --git a/src/controls/private/ActionMenuItem.qml b/src/controls/private/ActionMenuItem.qml
new file mode 100644 (file)
index 0000000..e4d49fc
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.3
+import QtQuick.Controls 2.3 as QQC2
+import org.kde.kirigami 2.4 as Kirigami
+
+QQC2.MenuItem {
+    id: menuItem
+
+    visible: action.visible === undefined || action.visible
+    height: visible ? implicitHeight : 0
+    autoExclusive: {
+        const g = action.QQC2.ActionGroup.group;
+        return g && g.exclusive;
+    }
+
+    QQC2.ToolTip.text: action.tooltip || ""
+    QQC2.ToolTip.visible: menuItem.hovered && QQC2.ToolTip.text.length > 0
+    QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+}
diff --git a/src/controls/private/ActionsMenu.qml b/src/controls/private/ActionsMenu.qml
new file mode 100644 (file)
index 0000000..6d3dff9
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.Menu
+{
+    id: theMenu
+    z: 999999999
+    property alias actions: actionsInstantiator.model
+    property Component submenuComponent
+    // renamed to work on both Qt 5.9 and 5.10
+    property Component itemDelegate: ActionMenuItem {}
+    property Component separatorDelegate: QQC2.MenuSeparator { property var action }
+    property Component loaderDelegate: Loader { property var action }
+    property QQC2.Action parentAction
+    property QQC2.MenuItem parentItem
+
+    Instantiator {
+        id: actionsInstantiator
+
+        active: theMenu.visible
+        delegate: QtObject {
+            readonly property QQC2.Action action: modelData
+            property QtObject item: null
+            property bool isSubMenu: false
+
+            function create() {
+                if (!action.hasOwnProperty("children") && !action.children || action.children.length === 0) {
+                    if (action.hasOwnProperty("separator") && action.separator) {
+                        item = theMenu.separatorDelegate.createObject(null, { action: action });
+                    }
+                    else if (action.displayComponent) {
+                        item = theMenu.loaderDelegate.createObject(null,
+                                { action: action, sourceComponent: action.displayComponent });
+                    }
+                    else {
+                        item = theMenu.itemDelegate.createObject(null, { action: action });
+                    }
+                    theMenu.addItem(item)
+                } else if (theMenu.submenuComponent) {
+                    item = theMenu.submenuComponent.createObject(null, { parentAction: action, title: action.text, actions: action.children });
+
+                    theMenu.insertMenu(theMenu.count, item)
+                    item.parentItem = theMenu.contentData[theMenu.contentData.length-1]
+                    item.parentItem.icon = action.icon
+                    isSubMenu = true
+                }
+            }
+            function remove() {
+                if (isSubMenu) {
+                    theMenu.removeMenu(item)
+                } else {
+                    theMenu.removeItem(item)
+                }
+                item.destroy()
+            }
+        }
+
+        onObjectAdded: object.create()
+        onObjectRemoved: object.remove()
+    }
+}
diff --git a/src/controls/private/BannerImage.qml b/src/controls/private/BannerImage.qml
new file mode 100644 (file)
index 0000000..8a3d863
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.12 as Kirigami
+
+/**
+ * This Component is used as the header of GlobalDrawer and as the header
+ * of Card, It can be accessed there as a grouped property but can never
+ * be instantiated directly.
+ * \private
+ */
+Kirigami.ShadowedImage {
+    id: root
+
+//BEGIN properties
+    /*
+     * FIXME: compatibility
+     *
+     * @deprecated Please use `source` property instead!
+     */
+    property alias imageSource: root.source
+
+    /*
+     * FIXME: compatibility
+     *
+     * @deprecated Please use `titleIcon` property instead!
+     */
+    property alias iconSource: root.titleIcon
+
+    /**
+     * @brief This property holds an icon to be displayed alongside the title.
+     *
+     * It can be a QIcon, a FreeDesktop-compatible icon name, or any URL understood by QtQuick.Image.
+     *
+     * @property var titleIcon
+     */
+    property alias titleIcon: headingIcon.source
+
+    /**
+     * @brief This property holds the title's text which is to be displayed on top.
+     * of the image.
+     * @see QtQuick.Text::text
+     * @property string title
+     */
+    property alias title: heading.text
+
+    /**
+     * @brief This property holds the title's position.
+     *
+     * default: ``Qt.AlignTop | Qt.AlignLeft``
+     *
+     * @property Qt::Alignment titleAlignment
+     */
+    property int titleAlignment: Qt.AlignTop | Qt.AlignLeft
+
+    /**
+     * @brief This property holds the title's level.
+     *
+     * Available text size values range from 1 (largest) to 5 (smallest).
+     *
+     * default: ``1``
+     *
+     * @see kirigami::Heading::level
+     * @property int titleLevel
+     */
+    property alias titleLevel: heading.level
+
+    /**
+     * @brief This property holds the title's wrap mode.
+     *
+     * default: ``Text.NoWrap``
+     *
+     * @see QtQuick.Text.wrapMode
+     * @property int titleWrapMode
+     */
+    property alias titleWrapMode: heading.wrapMode
+
+    property int leftPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
+    property int topPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
+    property int rightPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
+    property int bottomPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
+
+    property int implicitWidth: Layout.preferredWidth
+
+    readonly property bool empty: title.length === 0 &&             // string
+                                  source.toString().length === 0 && // QUrl
+                                  !titleIcon                        // QVariant hanled by Kirigami.Icon
+//END properties
+
+    Layout.fillWidth: true
+
+    Layout.preferredWidth: titleLayout.implicitWidth || sourceSize.width
+    Layout.preferredHeight: titleLayout.completed && source.toString() !== "" ? width/(sourceSize.width / sourceSize.height) : Layout.minimumHeight
+    Layout.minimumHeight: titleLayout.implicitHeight > 0 ? titleLayout.implicitHeight + Kirigami.Units.smallSpacing * 2 : 0
+
+    onTitleAlignmentChanged: {
+        Qt.callLater(titleLayout.implicitWidthChanged)
+        Qt.callLater(titleLayout.implicitHeightChanged)
+    }
+    fillMode: Image.PreserveAspectCrop
+    asynchronous: true
+
+    color: "transparent"
+
+    Component.onCompleted: {
+        titleLayout.completed = true;
+    }
+
+    Kirigami.ShadowedRectangle {
+        anchors {
+            left: parent.left
+            right: parent.right
+            top: (root.titleAlignment & Qt.AlignTop) ? parent.top : undefined
+            bottom: (root.titleAlignment & Qt.AlignBottom) ? parent.bottom : undefined
+            verticalCenter: (root.titleAlignment & Qt.AlignVCenter) ? parent.verticalCenter : undefined
+        }
+        height: Math.min(parent.height, titleLayout.height * 1.5)
+
+        opacity: 0.5
+        color: "black"
+
+        visible: root.source.toString().length !== 0 && root.title.length !== 0 && ((root.titleAlignment & Qt.AlignTop) || (root.titleAlignment & Qt.AlignVCenter) || (root.titleAlignment & Qt.AlignBottom))
+
+        corners.topLeftRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topLeftRadius : 0
+        corners.topRightRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topRightRadius : 0
+        corners.bottomLeftRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomLeftRadius : 0
+        corners.bottomRightRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomRightRadius : 0
+    }
+
+    RowLayout {
+        id: titleLayout
+        property bool completed: false
+        anchors {
+            left: root.titleAlignment & Qt.AlignLeft ? parent.left : undefined
+            top: root.titleAlignment & Qt.AlignTop ? parent.top : undefined
+            right: root.titleAlignment & Qt.AlignRight ? parent.right : undefined
+            bottom: root.titleAlignment & Qt.AlignBottom ? parent.bottom : undefined
+            horizontalCenter: root.titleAlignment & Qt.AlignHCenter ? parent.horizontalCenter : undefined
+            verticalCenter: root.titleAlignment & Qt.AlignVCenter ? parent.verticalCenter : undefined
+
+            leftMargin: root.leftPadding
+            topMargin: root.topPadding
+            rightMargin: root.rightPadding
+            bottomMargin: root.bottomPadding
+        }
+        width: Math.min(implicitWidth, parent.width -root.leftPadding - root.rightPadding)
+        height: Math.min(implicitHeight, parent.height - root.topPadding - root.bottomPadding)
+        Kirigami.Icon {
+            id: headingIcon
+            Layout.minimumWidth: Kirigami.Units.iconSizes.large
+            Layout.minimumHeight: width
+            visible: valid
+            isMask: false
+        }
+        Kirigami.Heading {
+            id: heading
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            verticalAlignment: Text.AlignVCenter
+            visible: text.length > 0
+            level: 1
+            color: root.source.toString() !== "" ? "white" : Kirigami.Theme.textColor
+            wrapMode: Text.NoWrap
+            elide: Text.ElideRight
+        }
+    }
+}
diff --git a/src/controls/private/CardsGridViewPrivate.qml b/src/controls/private/CardsGridViewPrivate.qml
new file mode 100644 (file)
index 0000000..f240339
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.10
+import org.kde.kirigami 2.4 as Kirigami
+
+
+GridView {
+    id: root
+
+    property Component _delegateComponent
+
+
+    QtObject {
+        id: calculations
+
+        // initialize array so length property can be read
+        property var leftMargins: []
+        readonly property int delegateWidth: Math.min(cellWidth, maximumColumnWidth) - Kirigami.Units.largeSpacing * 2 - ((Kirigami.Units.largeSpacing * 2) / root.columns)
+        // We need to subtract ((Kirigami.Units.largeSpacing * 2) / root.columns) to consider space on the left and on the right spreaded trough all columns
+    }
+
+    delegate: Kirigami.DelegateRecycler {
+        width: calculations.delegateWidth
+
+        anchors.left: GridView.view.contentItem.left
+
+        sourceComponent: root._delegateComponent
+        onWidthChanged: {
+            const columnIndex = index % root.columns
+            if (index < root.columns) {
+                // calulate left margin per column
+                calculations.leftMargins[columnIndex] = Kirigami.Units.largeSpacing + (width + Kirigami.Units.largeSpacing * 2 )
+                        * (columnIndex) + root.width / 2
+                        - (root.columns * (width + Kirigami.Units.largeSpacing * 2)) / 2;
+            }
+            anchors.leftMargin = calculations.leftMargins[columnIndex];
+        }
+    }
+    onWidthChanged: {
+        if (calculations.leftMargins.length !== root.columns) {
+            calculations.leftMargins = new Array(root.columns);
+        }
+    }
+}
diff --git a/src/controls/private/ContextDrawerActionItem.qml b/src/controls/private/ContextDrawerActionItem.qml
new file mode 100644 (file)
index 0000000..430ff87
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.5 as Kirigami
+
+Kirigami.BasicListItem {
+    id: listItem
+
+    readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator
+
+    readonly property bool isExpandible: modelData && modelData.hasOwnProperty("expandible") && modelData.expandible
+
+    checked: modelData.checked
+    icon: modelData.icon
+    separatorVisible: false
+    reserveSpaceForIcon: !isSeparator
+    reserveSpaceForLabel: !isSeparator
+
+    label: model ? (model.text ? model.text : model.tooltip) : (modelData.text ? modelData.text : modelData.tooltip)
+    hoverEnabled: (!isExpandible || root.collapsed) && !Kirigami.Settings.tabletMode
+    sectionDelegate: isExpandible
+    font.pointSize: isExpandible ? Kirigami.Theme.defaultFont.pointSize * 1.30 : Kirigami.Theme.defaultFont.pointSize
+
+    enabled: !isExpandible && !isSeparator && (model ? model.enabled : modelData.enabled)
+    visible: model ? model.visible : modelData.visible
+    opacity: enabled || isExpandible ? 1.0 : 0.6
+
+    Kirigami.Separator {
+        id: separatorAction
+
+        visible: listItem.isSeparator
+        Layout.fillWidth: true
+    }
+
+    ActionsMenu {
+        id: actionsMenu
+        y: Kirigami.Settings.isMobile ? -height : listItem.height
+        z: 99999999
+        actions: modelData.children
+        submenuComponent: Component {
+            ActionsMenu {}
+        }
+    }
+
+    Loader {
+        Layout.fillWidth: true
+        Layout.fillHeight: true
+        sourceComponent: modelData.displayComponent
+        onStatusChanged: {
+            for (const i in parent.children) {
+                const child = parent.children[i];
+                if (child === this) {
+                    child.visible = status === Loader.Ready;
+                    break;
+                } else {
+                    child.visible = status !== Loader.Ready;
+                }
+            }
+        }
+        Component.onCompleted: statusChanged()
+    }
+
+    Kirigami.Icon {
+        isMask: true
+        Layout.alignment: Qt.AlignVCenter
+        Layout.preferredHeight: Kirigami.Units.iconSizes.small/2
+        selected: listItem.checked || listItem.down
+        Layout.preferredWidth: Layout.preferredHeight
+        source: "go-up-symbolic"
+        visible: !isExpandible  && !listItem.isSeparator && modelData.children!== undefined && modelData.children.length > 0
+    }
+
+    onPressed: {
+        if (modelData.children.length > 0) {
+            actionsMenu.open();
+        }
+    }
+    onClicked: {
+        if (modelData.children.length === 0) {
+            root.drawerOpen = false;
+        }
+
+        if (modelData && modelData.trigger !== undefined) {
+            modelData.trigger();
+        // assume the model is a list of QAction or Action
+        } else if (menu.model.length > index) {
+            menu.model[index].trigger();
+        } else {
+            console.warning("Don't know how to trigger the action")
+        }
+    }
+}
diff --git a/src/controls/private/CornerShadow.qml b/src/controls/private/CornerShadow.qml
new file mode 100644 (file)
index 0000000..fbcb861
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.4 as Kirigami
+
+GE.RadialGradient {
+    id: shadow
+    /**
+     * @brief This property holds the corner of the shadow that will determine
+     * the direction of the gradient.
+     *
+     * The acceptable values are:
+     * Qt.TopLeftCorner, TopRightCorner, BottomLeftCorner, BottomRightCorner
+     *
+     * default: ``Qt.TopRightCorner``
+     *
+     * @see Qt::Corner
+     */
+    property int corner: Qt.TopRightCorner
+
+    readonly property real margin: -Math.floor(radius/3)
+
+    property int radius: Kirigami.Units.gridUnit
+
+    width: radius - margin
+    height: radius - margin
+
+    horizontalRadius: width
+    verticalRadius: height
+    horizontalOffset: {
+        switch (corner) {
+            case Qt.TopLeftCorner:
+            case Qt.BottomLeftCorner:
+                return -width/2;
+            default:
+                return width/2;
+        }
+    }
+    verticalOffset: {
+        switch (corner) {
+            case Qt.TopLeftCorner:
+            case Qt.TopRightCorner:
+                return -width/2;
+            default:
+                return width/2;
+        }
+    }
+
+    gradient: Gradient {
+        GradientStop {
+            position: 0.0
+            color: Qt.rgba(0, 0, 0, 0.25)
+        }
+        GradientStop {
+            position: 1 - radius/(radius - margin)
+            color: Qt.rgba(0, 0, 0, 0.25)
+        }
+        GradientStop {
+            position: 1 - radius/(radius - margin) + radius/(radius - margin) * 0.2
+            color: Qt.rgba(0, 0, 0, 0.1)
+        }
+        GradientStop {
+            position: 1 - radius/(radius - margin) + radius/(radius - margin) * 0.35
+            color: Qt.rgba(0, 0, 0, 0.02)
+        }
+        GradientStop {
+            position: 1.0
+            color:  "transparent"
+        }
+    }
+}
+
diff --git a/src/controls/private/DefaultCardBackground.qml b/src/controls/private/DefaultCardBackground.qml
new file mode 100644 (file)
index 0000000..73aad96
--- /dev/null
@@ -0,0 +1,107 @@
+
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+import QtQuick 2.15
+import org.kde.kirigami 2.15 as Kirigami
+
+/**
+ * @brief This is the default background for Cards.
+ *
+ * It provides background feedback on hover and click events, border customizability, and the ability to change the radius of each individual corner.
+ *
+ * @inherit kirigami::ShadowedRectangle
+ */
+Kirigami.ShadowedRectangle {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property sets whether there should be a background change on a click event.
+     *
+     * default: ``false``
+     */
+    property bool clickFeedback: false
+
+    /**
+     * @brief This property sets whether there should be a background change on a click event.
+     *
+     * default: ``false``
+     */
+    property bool hoverFeedback: false
+
+    /**
+     * @brief This property holds the card's normal background color.
+     *
+     * default: @link Kirigami.PlatformTheme.backgroundColor Kirigami.Theme.backgroundColor @endlink
+     */
+    property color defaultColor: Kirigami.Theme.backgroundColor
+
+    /**
+     * @brief This property holds the color displayed when a click event is triggered.
+     * @see ::clickFeedback
+     */
+    property color pressedColor: Kirigami.ColorUtils.tintWithAlpha(
+                                     defaultColor,
+                                     Kirigami.Theme.highlightColor, 0.3)
+
+    /**
+     * @brief This property holds the color displayed when a hover event is triggered.
+     * @see ::hoverFeedback
+     */
+    property color hoverColor: Kirigami.ColorUtils.tintWithAlpha(
+                                   defaultColor,
+                                   Kirigami.Theme.highlightColor, 0.1)
+
+    /**
+     * @brief This property holds the border width which is displayed at the edge of DefaultCardBackground.
+     *
+     * default: ``1``
+     */
+    property int borderWidth: 1
+
+    /**
+     * @brief This property holds the border color which is displayed at the edge of DefaultCardBackground.
+     */
+    property color borderColor: Kirigami.ColorUtils.tintWithAlpha(
+                                    color, Kirigami.Theme.textColor, 0.2)
+    
+//END properties
+    
+    color: {
+        if (root.parent.checked || (root.clickFeedback && (root.parent.down || root.parent.highlighted)))
+            return root.pressedColor
+        else if (root.hoverFeedback && root.parent.hovered)
+            return root.hoverColor
+        return root.defaultColor
+    }
+
+    radius: Kirigami.Units.smallSpacing
+    
+    border {
+        width: root.borderWidth
+        color: root.borderColor
+    }
+    shadow {
+        size: Kirigami.Units.gridUnit
+        color: Qt.rgba(0, 0, 0, 0.05)
+        yOffset: 2
+    }
+    
+    // basic drop shadow
+    Rectangle {
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.top: parent.top
+        anchors.topMargin: Math.round(Kirigami.Units.smallSpacing / 4)
+        
+        radius: root.radius
+        height: root.height
+        color: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.6), 1.1)
+        visible: !root.clickFeedback || !root.parent.down
+        
+        z: -1
+    }
+}
diff --git a/src/controls/private/DefaultChipBackground.qml b/src/controls/private/DefaultChipBackground.qml
new file mode 100644 (file)
index 0000000..591fbec
--- /dev/null
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+
+Rectangle {
+    /**
+     * @brief This property holds the color of the Chip's background when it is being pressed.
+     * @see QtQuick.AbstractButton.down
+     */
+    property color pressedColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.3)
+
+    /**
+     * @brief This property holds the color of the Chip's background when it is checked.
+     * @see QtQuick.AbstractButton.checked
+     */
+    property color hoverSelectColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.2)
+
+    /**
+     * @brief This property holds the color of the Chip's border when it is checked.
+     * @see QtQuick.AbstractButton.checked
+     */
+    property color checkedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.7)
+
+    /**
+     * @brief This property holds the color of the Chip's border when it is being pressed.
+     * @see QtQuick.AbstractButton.down
+     */
+    property color pressedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.9)
+
+    Kirigami.Theme.colorSet:Kirigami.Theme.Header
+    Kirigami.Theme.inherit: false
+
+    color: parent.down ? pressedColor : (parent.checked ? hoverSelectColor : Kirigami.Theme.backgroundColor)
+    border.color: parent.down ? checkedBorderColor : (parent.checked ? pressedBorderColor : Qt.darker(Kirigami.Theme.backgroundColor, 1.1))
+    border.width: 1
+    radius: 3
+}
diff --git a/src/controls/private/DefaultListItemBackground.qml b/src/controls/private/DefaultListItemBackground.qml
new file mode 100644 (file)
index 0000000..951d027
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.12 as Kirigami
+
+Rectangle {
+    id: background
+    color: {
+        if (listItem.alternatingBackground && index % 2)
+            return listItem.alternateBackgroundColor
+        else if (listItem.checked || listItem.highlighted || (listItem.down && !listItem.checked && !listItem.sectionDelegate))
+            return listItem.activeBackgroundColor
+        return listItem.backgroundColor
+    }
+
+    visible: listItem.ListView.view === null || listItem.ListView.view.highlight === null
+    Rectangle {
+        id: internal
+        anchors.fill: parent
+        visible: !Kirigami.Settings.tabletMode && listItem.hoverEnabled
+        color: listItem.activeBackgroundColor
+        opacity: {
+            if ((listItem.highlighted || listItem.ListView.isCurrentItem) && !listItem.down) {
+                return .6
+            } else if (listItem.hovered && !listItem.down) {
+                return .3
+            } else {
+                return 0
+            }
+        }
+    }
+
+    Kirigami.Separator {
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: parent.bottom
+            leftMargin: Kirigami.Units.largeSpacing
+            rightMargin: Kirigami.Units.largeSpacing
+        }
+        visible: {
+            // Whether there is visual feedback (do not show the separator)
+            const visualFeedback = listItem.highlighted || listItem.down || listItem.checked || listItem.ListView.isCurrentItem
+
+            // Show the separator when activeBackgroundColor is set to "transparent",
+            // when the item is hovered. Check commit 344aec26.
+            const bgTransparent = !listItem.hovered || listItem.activeBackgroundColor.a === 0
+
+            // Whether the next item is a section delegate or is from another section (do not show the separator)
+            const anotherSection = listItem.ListView.view === null || listItem.ListView.nextSection === listItem.ListView.section
+
+            // Whether this item is the last item in the view (do not show the separator)
+            const lastItem = listItem.ListView.view === null || listItem.ListView.count - 1 !== index
+
+            return listItem.separatorVisible && !visualFeedback && bgTransparent
+                && !listItem.sectionDelegate && anotherSection && lastItem
+        }
+        weight: Kirigami.Separator.Weight.Light
+    }
+}
+
diff --git a/src/controls/private/DefaultPageTitleDelegate.qml b/src/controls/private/DefaultPageTitleDelegate.qml
new file mode 100644 (file)
index 0000000..8c84d1b
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * This component is used as a default representation for a page title within
+ * page's header/toolbar. It is just a Heading item with shrinking + eliding
+ * behavior.
+ *
+ * \private
+ */
+Item {
+    property alias text: heading.text
+
+    Layout.fillWidth: true
+    Layout.minimumWidth: 0
+    Layout.maximumWidth: implicitWidth
+
+    implicitWidth: Math.ceil(metrics.advanceWidth)
+    implicitHeight: metrics.height
+
+    Kirigami.Heading {
+        id: heading
+
+        anchors.fill: parent
+        maximumLineCount: 1
+        elide: Text.ElideRight
+        textFormat: Text.PlainText
+    }
+
+    TextMetrics {
+        id: metrics
+
+        font: heading.font
+        text: heading.text
+    }
+}
diff --git a/src/controls/private/EdgeShadow.qml b/src/controls/private/EdgeShadow.qml
new file mode 100644 (file)
index 0000000..1014bbd
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.4 as Kirigami
+
+GE.LinearGradient {
+    id: shadow
+    /**
+     * @brief This property holds the edge of the shadow that will determine the direction of the gradient.
+     * The following values are allowed:
+     * * ``Qt.TopEdge``: the top edge of the content item.
+     * * ``Qt.LeftEdge``: the left edge of the content item
+     * * ``Qt.RightEdge``: the right edge of the content item.
+     * * ``Qt.BottomEdge``: the bottom edge of the content item.
+     *
+     * @see Qt::Edges
+     */
+    property int edge: Qt.LeftEdge
+
+    property int radius: Kirigami.Units.gridUnit
+    implicitWidth: radius
+    implicitHeight: radius
+
+    start: Qt.point((edge !== Qt.RightEdge ? 0 : width), (edge !== Qt.BottomEdge ? 0 : height))
+    end: Qt.point((edge !== Qt.LeftEdge ? 0 : width), (edge !== Qt.TopEdge ? 0 : height))
+    gradient: Gradient {
+        GradientStop {
+            position: 0.0
+            color: Qt.rgba(0, 0, 0, 0.25)
+        }
+        GradientStop {
+            position: 0.20
+            color: Qt.rgba(0, 0, 0, 0.1)
+        }
+        GradientStop {
+            position: 0.35
+            color: Qt.rgba(0, 0, 0, 0.02)
+        }
+        GradientStop {
+            position: 1.0
+            color:  "transparent"
+        }
+    }
+}
+
diff --git a/src/controls/private/GlobalDrawerActionItem.qml b/src/controls/private/GlobalDrawerActionItem.qml
new file mode 100644 (file)
index 0000000..07e63fa
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Window 2.6
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Controls.impl 2.3 as QQC2Impl
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.5 as Kirigami
+
+Kirigami.AbstractListItem {
+    id: listItem
+
+    readonly property bool wideMode: width > height * 2
+    readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator
+
+    readonly property bool isExpandible: modelData && modelData.hasOwnProperty("expandible") && modelData.expandible
+
+    checked: modelData.checked || (actionsMenu && actionsMenu.visible)
+    width: parent.width
+
+    contentItem: RowLayout {
+        spacing: Kirigami.Units.largeSpacing
+
+        Kirigami.Icon {
+            id: iconItem
+            color: modelData.icon.color
+            source: modelData.icon.name || modelData.icon.source
+
+            readonly property int size: Kirigami.Units.iconSizes.smallMedium
+            Layout.minimumHeight: size
+            Layout.maximumHeight: size
+            Layout.minimumWidth: size
+            Layout.maximumWidth: size
+            selected: (listItem.highlighted || listItem.checked || listItem.down)
+            visible: source !== undefined
+        }
+
+        QQC2Impl.MnemonicLabel {
+            id: labelItem
+            text:  width > height * 2 ? listItem.Kirigami.MnemonicData.mnemonicLabel : ""
+            Layout.fillWidth: true
+            mnemonicVisible: listItem.Kirigami.MnemonicData.active
+            color: (listItem.highlighted || listItem.checked || listItem.down) ? listItem.activeTextColor : listItem.textColor
+            elide: Text.ElideRight
+            font: listItem.font
+            opacity: {
+                if (root.collapsed) {
+                    return 0;
+                } else if (!listItem.enabled) {
+                    return 0.6;
+                } else {
+                    return 1.0;
+                }
+            }
+            Behavior on opacity {
+                NumberAnimation {
+                    duration: Kirigami.Units.longDuration/2
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+
+        Kirigami.Separator {
+            id: separatorAction
+
+            visible: listItem.isSeparator
+            Layout.fillWidth: true
+        }
+
+        Kirigami.Icon {
+            Shortcut {
+                sequence: listItem.Kirigami.MnemonicData.sequence
+                onActivated: listItem.clicked()
+            }
+            isMask: true
+            Layout.alignment: Qt.AlignVCenter
+            Layout.leftMargin: !root.collapsed ? 0 : -width
+            Layout.preferredHeight: !root.collapsed ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.small/2
+            opacity: 0.7
+            selected: listItem.checked || listItem.down
+            Layout.preferredWidth: Layout.preferredHeight
+            source: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
+            visible: (!isExpandible || root.collapsed) && !listItem.isSeparator && modelData.hasOwnProperty("children") && modelData.children!==undefined && modelData.children.length > 0
+        }
+    }
+    Kirigami.MnemonicData.enabled: listItem.enabled && listItem.visible
+    Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
+    Kirigami.MnemonicData.label: modelData.text
+    property ActionsMenu actionsMenu: ActionsMenu {
+        x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
+        actions: modelData.hasOwnProperty("children") ? modelData.children : null
+        submenuComponent: Component {
+            ActionsMenu {}
+        }
+        onVisibleChanged: {
+            if (visible) {
+                stackView.openSubMenu = listItem.actionsMenu;
+            } else if (stackView.openSubMenu === listItem.actionsMenu) {
+                stackView.openSubMenu = null;
+            }
+        }
+    }
+
+    // TODO: animate the hide by collapse
+    visible: (model ? model.visible || model.visible===undefined : modelData.visible) && opacity > 0
+    opacity: !root.collapsed || iconItem.source.length > 0
+    Behavior on opacity {
+        NumberAnimation {
+            duration: Kirigami.Units.longDuration/2
+            easing.type: Easing.InOutQuad
+        }
+    }
+    enabled: !isSeparator && ( (model && model.enabled !== undefined) ? model.enabled : modelData.enabled)
+
+    hoverEnabled: (!isExpandible || root.collapsed) && !Kirigami.Settings.tabletMode
+    sectionDelegate: isExpandible
+    font.pointSize: isExpandible ? Kirigami.Theme.defaultFont.pointSize * 1.30 : Kirigami.Theme.defaultFont.pointSize
+    height: implicitHeight * opacity
+
+    data: [
+        QQC2.ToolTip {
+            visible: !listItem.isSeparator && (modelData.hasOwnProperty("tooltip") && modelData.tooltip.length || root.collapsed) && (!actionsMenu || !actionsMenu.visible) &&  listItem.hovered && text.length > 0
+            text: modelData.hasOwnProperty("tooltip") && modelData.tooltip.length ? modelData.tooltip : modelData.text
+            delay: Kirigami.Units.toolTipDelay
+            timeout: 5000
+            y: listItem.height/2 - height/2
+            x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
+        }
+    ]
+
+    onHoveredChanged: {
+        if (!hovered) {
+            return;
+        }
+        if (stackView.openSubMenu) {
+            stackView.openSubMenu.visible = false;
+
+            if (!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) {
+                if (listItem.actionsMenu.hasOwnProperty("popup")) {
+                    listItem.actionsMenu.popup(listItem, listItem.width, 0)
+                } else {
+                    listItem.actionsMenu.visible = true;
+                }
+            }
+        }
+    }
+
+    onClicked: trigger()
+    Keys.onEnterPressed: event => trigger()
+    Keys.onReturnPressed: event => trigger()
+
+    function trigger() {
+        modelData.trigger();
+        if (modelData.hasOwnProperty("children") && modelData.children!==undefined && modelData.children.length > 0) {
+            if (root.collapsed) {
+                // fallbacks needed for Qt 5.9
+                if ((!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) && !listItem.actionsMenu.visible) {
+                    stackView.openSubMenu = listItem.actionsMenu;
+                    if (listItem.actionsMenu.hasOwnProperty("popup")) {
+                        listItem.actionsMenu.popup(listItem, listItem.width, 0)
+                    } else {
+                        listItem.actionsMenu.visible = true;
+                    }
+                }
+            } else {
+                stackView.push(menuComponent, {model: modelData.children, level: level + 1, current: modelData });
+            }
+        } else if (root.resetMenuOnTriggered) {
+            root.resetMenu();
+        }
+        checked = Qt.binding(function() { return modelData.checked || (actionsMenu && actionsMenu.visible) });
+    }
+
+    Keys.onDownPressed: event => nextItemInFocusChain().focus = true
+    Keys.onUpPressed: event => nextItemInFocusChain(false).focus = true
+}
diff --git a/src/controls/private/PageActionPropertyGroup.qml b/src/controls/private/PageActionPropertyGroup.qml
new file mode 100644 (file)
index 0000000..caa0038
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQml 2.1
+
+
+/**
+ * @brief This is an action group that defines actions in a kirigami page.
+ */
+QtObject {
+    /**
+     * @brief This is the main action of a page.
+     *
+     * This action will be displayed horizontally centered at the bottom of the
+     * page when page stack's global toolbar style is set to breadcrumb mode.
+     *
+     * @see kirigami::PageRow::globalToolBar
+     */
+    property QtObject main
+
+    /**
+     * @brief This is the left action in a page.
+     *
+     * This action will be left of the main action when page stack's global
+     * toolbar style is set to breadcrumb mode.
+     *
+     * @see kirigami::PageRow::globalToolBar
+     */
+    property QtObject left
+
+    /**
+     * @brief This is the right action in a page.
+     *
+     * This action will be right of the main action when page stack's global
+     * toolbar style is set to breadcrumb mode.
+     *
+     * @see kirigami::PageRow::globalToolBar
+     */
+    property QtObject right
+
+    /**
+     * @brief These actions are inside a ContextDrawer which will be shown
+     * by swiping from the right side.
+     *
+     * @see kirigami::ContextDrawer
+     */
+    property list<QtObject> contextualActions
+}
+
diff --git a/src/controls/private/PrivateActionToolButton.qml b/src/controls/private/PrivateActionToolButton.qml
new file mode 100644 (file)
index 0000000..3202738
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as Controls
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Controls.ToolButton {
+    id: control
+
+    signal menuAboutToShow()
+
+    hoverEnabled: true
+
+    display: Controls.ToolButton.TextBesideIcon
+
+    property bool showMenuArrow: !Kirigami.DisplayHint.displayHintSet(action, Kirigami.DisplayHint.HideChildIndicator)
+
+    property var menuActions: {
+        if (action && action.hasOwnProperty("children")) {
+            return Array.prototype.slice.call(action.children)
+        }
+        return []
+    }
+
+    property Component menuComponent: ActionsMenu {
+        submenuComponent: ActionsMenu { }
+    }
+
+    property QtObject menu: null
+
+    // We create the menu instance only when there are any actual menu items.
+    // This also happens in the background, avoiding slowdowns due to menu item
+    // creation on the main thread.
+    onMenuActionsChanged: {
+        if (menuComponent && menuActions.length > 0) {
+            if (!menu) {
+                const setupIncubatedMenu = incubatedMenu => {
+                    menu = incubatedMenu
+                    // Important: We handle the press on parent in the parent, so ignore it here.
+                    menu.closePolicy = Controls.Popup.CloseOnEscape | Controls.Popup.CloseOnPressOutsideParent
+                    menu.closed.connect(() => control.checked = false)
+                    menu.actions = control.menuActions
+                }
+                const incubator = menuComponent.incubateObject(control, {"actions": menuActions})
+                if (incubator.status !== Component.Ready) {
+                    incubator.onStatusChanged = status => {
+                        if (status === Component.Ready) {
+                            setupIncubatedMenu(incubator.object)
+                        }
+                    }
+                } else {
+                    setupIncubatedMenu(incubator.object);
+                }
+            } else {
+                menu.actions = menuActions
+            }
+        }
+    }
+
+    visible: (action && action.hasOwnProperty("visible")) ? action.visible : true
+
+    // Workaround for QTBUG-85941
+    Binding {
+        target: control
+        property: "checkable"
+        value: (control.action && control.action.checkable) || (control.menuActions && control.menuActions.length > 0)
+        restoreMode: Binding.RestoreBinding
+    }
+
+    onToggled: {
+        if (menuActions.length > 0 && menu) {
+            if (checked) {
+                control.menuAboutToShow();
+                menu.popup(control, 0, control.height)
+            } else {
+                menu.dismiss()
+            }
+        }
+    }
+
+    Controls.ToolTip {
+        visible: control.hovered && text.length > 0 && !(control.menu && control.menu.visible) && !control.pressed
+        text: {
+            const a = control.action;
+            if (a) {
+                if (a.tooltip) {
+                    return a.tooltip;
+                } else if (control.display === Controls.Button.IconOnly) {
+                    return a.text;
+                }
+            }
+            return "";
+        }
+    }
+
+    // This will set showMenuArrow when using qqc2-desktop-style.
+    Accessible.role: (control.showMenuArrow && control.menuActions.length > 0) ? Accessible.ButtonMenu : Accessible.Button
+    Accessible.ignored: !visible
+}
diff --git a/src/controls/private/SwipeItemEventFilter.qml b/src/controls/private/SwipeItemEventFilter.qml
new file mode 100644 (file)
index 0000000..071a848
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.4 as Kirigami
+
+
+MouseArea {
+    id: swipeFilter
+    anchors {
+        right: parent.right
+        top: parent.top
+        bottom: parent.bottom
+    }
+
+    z: 99999
+    property Item currentItem
+    property real peek
+
+    preventStealing: true
+    width: Kirigami.Units.gridUnit
+    onPressed: mouse => {
+        const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
+        currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
+    }
+    onPositionChanged: mouse => {
+        const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
+        currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
+        peek = 1 - mapped.x / parent.flickableItem.contentItem.width;
+    }
+}
diff --git a/src/controls/private/globaltoolbar/AbstractPageHeader.qml b/src/controls/private/globaltoolbar/AbstractPageHeader.qml
new file mode 100644 (file)
index 0000000..f406c2b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.19 as Kirigami
+
+Kirigami.AbstractApplicationHeader {
+    id: root
+    // anchors.fill: parent
+    property Item container
+    property bool current
+
+    minimumHeight: pageRow ? pageRow.globalToolBar.minimumHeight : Kirigami.Units.iconSizes.medium + Kirigami.Units.smallSpacing * 2
+    maximumHeight: pageRow ? pageRow.globalToolBar.maximumHeight : minimumHeight
+    preferredHeight: pageRow ? pageRow.globalToolBar.preferredHeight : minimumHeight
+
+    separatorVisible: pageRow ? pageRow.globalToolBar.separatorVisible : true
+
+    Kirigami.Theme.colorSet: pageRow ? pageRow.globalToolBar.colorSet : Kirigami.Theme.Header
+
+    leftPadding: pageRow
+        ? Math.min(
+            width / 2,
+            Math.max(
+                (page.title.length > 0 ? pageRow.globalToolBar.titleLeftPadding : 0),
+                Qt.application.layoutDirection === Qt.LeftToRight
+                    ? (pageRow.Kirigami.ScenePosition.x
+                        - page.Kirigami.ScenePosition.x
+                        + pageRow.globalToolBar.leftReservedSpace
+                        + Kirigami.Units.smallSpacing)
+                    : (-pageRow.width
+                        + pageRow.Kirigami.ScenePosition.x
+                        + page.Kirigami.ScenePosition.x
+                        + page.width
+                        + pageRow.globalToolBar.leftReservedSpace
+                        + Kirigami.Units.smallSpacing)))
+        : Kirigami.Units.smallSpacing
+
+    rightPadding: pageRow
+        ? Math.max(0,
+            Qt.application.layoutDirection === Qt.LeftToRight
+            ? (-pageRow.width
+                - pageRow.Kirigami.ScenePosition.x
+                + page.width
+                + page.Kirigami.ScenePosition.x
+                + pageRow.globalToolBar.rightReservedSpace)
+            : (pageRow.Kirigami.ScenePosition.x
+                - page.Kirigami.ScenePosition.x
+                + pageRow.globalToolBar.rightReservedSpace))
+        : 0
+}
diff --git a/src/controls/private/globaltoolbar/BreadcrumbControl.qml b/src/controls/private/globaltoolbar/BreadcrumbControl.qml
new file mode 100644 (file)
index 0000000..a4e87a1
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+
+Flickable {
+    id: root
+
+    property Kirigami.PageRow pageRow: parent.pageRow
+
+    readonly property Item currentItem: mainLayout.children[pageRow.currentIndex] || null
+
+    contentHeight: height
+    contentWidth: mainLayout.width
+    clip: true
+    boundsBehavior: Flickable.StopAtBounds
+    interactive: Kirigami.Settings.hasTransientTouchInput
+
+    contentX: currentItem
+        ? Math.max(0,
+            Math.min(currentItem.x + currentItem.width/2 - root.width/2,
+                     root.contentWidth - root.width))
+        : 0
+
+    RowLayout {
+        id: mainLayout
+        height: parent.height
+        spacing: 0
+        Repeater {
+            id: mainRepeater
+            readonly property bool useLayers: pageRow.layers.depth > 1
+            model: useLayers ? pageRow.layers.depth - 1 : pageRow.depth
+            delegate: MouseArea {
+                Layout.preferredWidth: delegateLayout.implicitWidth
+                Layout.fillHeight: true
+                onClicked: mouse => {
+                    if (mainRepeater.useLayers) {
+                        while (pageRow.layers.depth > modelData + 1) {
+                            pageRow.layers.pop();
+                        }
+                    } else {
+                        pageRow.currentIndex = modelData;
+                    }
+                }
+                hoverEnabled: !Kirigami.Settings.tabletMode
+                Rectangle {
+                    color: Kirigami.Theme.highlightColor
+                    anchors.fill: parent
+                    radius: 3
+                    opacity: mainRepeater.count > 1 && parent.containsMouse ? 0.1 : 0
+                }
+                RowLayout {
+                    id: delegateLayout
+                    anchors.fill: parent
+                    // We can't use Kirigami.Page here instead of Item since we now accept pushing PageRow to a new layer
+                    readonly property Item page: mainRepeater.useLayers ? pageRow.layers.get(modelData + 1) : pageRow.get(modelData)
+                    spacing: 0
+
+                    Kirigami.Icon {
+                        visible: modelData > 0
+                        Layout.alignment: Qt.AlignVCenter
+                        Layout.preferredHeight: Kirigami.Units.iconSizes.small
+                        Layout.preferredWidth: Layout.preferredHeight
+                        isMask: true
+                        color: Kirigami.Theme.textColor
+                        source: LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic"
+                    }
+                    Kirigami.Heading {
+                        Layout.leftMargin: Kirigami.Units.largeSpacing
+                        Layout.rightMargin: Kirigami.Units.largeSpacing
+                        color: Kirigami.Theme.textColor
+                        verticalAlignment: Text.AlignVCenter
+                        wrapMode: Text.NoWrap
+                        text: delegateLayout.page ? delegateLayout.page.title : ""
+                        opacity: modelData === pageRow.currentIndex ? 1 : 0.4
+                    }
+                }
+            }
+        }
+    }
+
+    Behavior on contentX {
+        NumberAnimation {
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutQuad
+        }
+    }
+}
diff --git a/src/controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml b/src/controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml
new file mode 100644 (file)
index 0000000..a2b5ab9
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import org.kde.kirigami 2.19 as Kirigami
+
+QtObject {
+    id: globalToolBar
+    property int style: Kirigami.ApplicationHeaderStyle.None
+    onStyleChanged: if (style === Kirigami.ApplicationHeaderStyle.TabBar) {
+        console.warn("TabBar header style is deprecated.")
+    }
+    readonly property int actualStyle: {
+        if (style === Kirigami.ApplicationHeaderStyle.Auto) {
+            // TODO KF6
+            // Legacy: if ApplicationHeader or ToolbarApplicationHeader are in the header or footer, disable the toolbar here
+            if (typeof applicationWindow !== "undefined" && applicationWindow().header && applicationWindow().header.toString().indexOf("ApplicationHeader") !== -1) {
+                return Kirigami.ApplicationHeaderStyle.None
+            }
+
+            //non legacy logic
+            return (Kirigami.Settings.isMobile
+                    ? (root.wideMode ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.Breadcrumb)
+                    : Kirigami.ApplicationHeaderStyle.ToolBar)
+        }
+        return style;
+    }
+
+    // TODO KF6: remove bool support.
+    // Until then, `true` is considered as both `ShowBackButton | ShowForwardButton` together.
+    property var/*flags<NavigationButtons> | bool*/ showNavigationButtons: (style !== Kirigami.ApplicationHeaderStyle.TabBar && (!Kirigami.Settings.isMobile || Qt.platform.os === "ios"))
+        ? (Kirigami.ApplicationHeaderStyle.ShowBackButton | Kirigami.ApplicationHeaderStyle.ShowForwardButton)
+        : Kirigami.ApplicationHeaderStyle.NoNavigationButtons
+    property bool separatorVisible: true
+    //Unfortunately we can't access pageRow.globalToolbar.Kirigami.Theme directly in a declarative way
+    property int colorSet: Kirigami.Theme.Header
+    // whether or not the header should be
+    // "pushed" back when scrolling using the
+    // touch screen
+    property bool hideWhenTouchScrolling: false
+    /**
+     * If true, when any kind of toolbar is shown, the drawer handles will be shown inside the toolbar, if they're present
+     */
+    property bool canContainHandles: true
+    property int toolbarActionAlignment: Qt.AlignRight
+    property int toolbarActionHeightMode: Kirigami.ToolBarLayout.ConstrainIfLarger
+
+    property int minimumHeight: 0
+    // FIXME: Figure out the exact standard size of a Toolbar
+    property int preferredHeight: (actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar
+                    ? Kirigami.Units.iconSizes.medium
+                    : Kirigami.Units.gridUnit * 1.8) + Kirigami.Units.smallSpacing * 2
+    property int maximumHeight: preferredHeight
+
+    // Sets the minimum leading padding for the title in a page header
+    property int titleLeftPadding: Kirigami.Units.gridUnit
+}
diff --git a/src/controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml b/src/controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml
new file mode 100644 (file)
index 0000000..e2956d5
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+import "../../templates/private" as TP
+import "../" as P
+
+Kirigami.AbstractApplicationHeader {
+    id: header
+    readonly property int leftReservedSpace: (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0 ? buttonsLayout.width + Kirigami.Units.smallSpacing : 0) // Take into account the layout margins the nav buttons have
+        + (leftHandleAnchor.visible ? leftHandleAnchor.width : 0)
+        + (menuButton.visible ? menuButton.width : 0)
+    readonly property int rightReservedSpace: rightHandleAnchor.visible ? backButton.background.implicitHeight : 0
+
+    readonly property alias leftHandleAnchor: leftHandleAnchor
+    readonly property alias rightHandleAnchor: rightHandleAnchor
+
+    readonly property bool breadcrumbVisible: layerIsMainRow && breadcrumbLoader.active
+    readonly property bool layerIsMainRow: (root.layers.currentItem.hasOwnProperty("columnView")) ? root.layers.currentItem.columnView === root.columnView : false
+    readonly property Item currentItem: layerIsMainRow ? root.columnView : root.layers.currentItem
+
+    height: visible ? implicitHeight : 0
+    minimumHeight: globalToolBar.minimumHeight
+    preferredHeight: globalToolBar.preferredHeight
+    maximumHeight: globalToolBar.maximumHeight
+    separatorVisible: globalToolBar.separatorVisible
+
+    Kirigami.Theme.colorSet: globalToolBar.colorSet
+    Kirigami.Theme.textColor: currentItem ? currentItem.Kirigami.Theme.textColor : parent.Kirigami.Theme.textColor
+
+    RowLayout {
+        anchors.fill: parent
+        spacing: 0
+
+        Item {
+            Layout.preferredWidth: applicationWindow().pageStack.globalToolBar.leftReservedSpace
+            visible: applicationWindow().pageStack !== root
+        }
+
+        Item {
+            id: leftHandleAnchor
+            visible: (typeof applicationWindow() !== "undefined" && applicationWindow().globalDrawer && applicationWindow().globalDrawer.enabled && applicationWindow().globalDrawer.handleVisible &&
+            applicationWindow().globalDrawer.handle.handleAnchor === leftHandleAnchor) &&
+            (globalToolBar.canContainHandles || (breadcrumbLoader.pageRow.firstVisibleItem &&
+            breadcrumbLoader.pageRow.firstVisibleItem.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar))
+
+
+            Layout.preferredHeight: Math.min(backButton.implicitHeight, parent.height)
+            Layout.preferredWidth: height
+        }
+
+        P.PrivateActionToolButton {
+            id: menuButton
+            visible: !Kirigami.Settings.isMobile && applicationWindow().globalDrawer && "isMenu" in applicationWindow().globalDrawer && applicationWindow().globalDrawer.isMenu
+            icon.name: "open-menu-symbolic"
+            showMenuArrow: false
+
+            Layout.preferredHeight: Math.min(backButton.implicitHeight, parent.height)
+            Layout.preferredWidth: height
+            Layout.leftMargin: Kirigami.Units.smallSpacing
+
+            action: Kirigami.Action {
+                children: applicationWindow().globalDrawer && applicationWindow().globalDrawer.actions ? applicationWindow().globalDrawer.actions : []
+                tooltip: checked ? qsTr("Close menu") : qsTr("Open menu")
+            }
+            Accessible.name: action.tooltip
+        }
+
+        RowLayout {
+            id: buttonsLayout
+            Layout.fillHeight: true
+            Layout.preferredHeight: Math.max(backButton.visible ? backButton.implicitHeight : 0, forwardButton.visible ? forwardButton.implicitHeight : 0)
+
+            Layout.leftMargin: leftHandleAnchor.visible ? Kirigami.Units.smallSpacing : 0
+
+            // TODO KF6: make showNavigationButtons an int, and replace with strict === equality
+            visible: (globalToolBar.showNavigationButtons !== Kirigami.ApplicationHeaderStyle.NoNavigationButtons || applicationWindow().pageStack.layers.depth > 1 && !(applicationWindow().pageStack.layers.currentItem instanceof Kirigami.PageRow || header.layerIsMainRow))
+                && globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None
+
+            Layout.maximumWidth: visibleChildren.length > 0 ? Layout.preferredWidth : 0
+
+            TP.BackButton {
+                id: backButton
+                Layout.leftMargin: leftHandleAnchor.visible ? 0 : Kirigami.Units.smallSpacing
+                Layout.minimumWidth: implicitHeight
+                Layout.minimumHeight: implicitHeight
+                Layout.maximumHeight: buttonsLayout.height
+            }
+            TP.ForwardButton {
+                id: forwardButton
+                Layout.minimumWidth: implicitHeight
+                Layout.minimumHeight: implicitHeight
+                Layout.maximumHeight: buttonsLayout.height
+            }
+        }
+
+        QQC2.ToolSeparator {
+            visible: (menuButton.visible || (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0)) && breadcrumbVisible && pageRow.depth > 1
+        }
+
+        Loader {
+            id: breadcrumbLoader
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.minimumHeight: -1
+            Layout.preferredHeight: -1
+            property Kirigami.PageRow pageRow: root
+
+            asynchronous: true
+
+            active: (globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.TabBar || globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.Breadcrumb) && currentItem && currentItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.None
+
+            // TODO: different implementation?
+            source: globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.TabBar ? Qt.resolvedUrl("TabBarControl.qml") : Qt.resolvedUrl("BreadcrumbControl.qml")
+        }
+
+        Item {
+            id: rightHandleAnchor
+            visible: (typeof applicationWindow() !== "undefined" &&
+                    applicationWindow().contextDrawer &&
+                    applicationWindow().contextDrawer.enabled &&
+                    applicationWindow().contextDrawer.handleVisible &&
+                    applicationWindow().contextDrawer.handle.handleAnchor === rightHandleAnchor &&
+                    (globalToolBar.canContainHandles || (breadcrumbLoader.pageRow && breadcrumbLoader.pageRow.lastVisibleItem &&
+                        breadcrumbLoader.pageRow.lastVisibleItem.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar)))
+            Layout.fillHeight: true
+            Layout.preferredWidth: height
+        }
+    }
+    background.opacity: breadcrumbLoader.active ? 1 : 0
+}
diff --git a/src/controls/private/globaltoolbar/TabBarControl.qml b/src/controls/private/globaltoolbar/TabBarControl.qml
new file mode 100644 (file)
index 0000000..e6643d6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.19 as Kirigami
+
+/**
+ * @warning This will probably be deprecated in KF6.
+ */
+// TODO KF6 deprecated
+QQC2.TabBar {
+    id: root
+    property Kirigami.PageRow pageRow: parent.pageRow
+
+    Repeater {
+        id: mainRepeater
+        model: pageRow.depth
+        delegate: QQC2.TabButton {
+            anchors {
+                top:parent.top
+                bottom:parent.bottom
+            }
+            width: mainRepeater.count === 1 ? implicitWidth : Math.max(implicitWidth, Math.round(root.width/mainRepeater.count))
+            height: root.height
+            readonly property Kirigami.Page page: pageRow.get(modelData)
+            text: page ? page.title : ""
+            checked: modelData === pageRow.currentIndex
+            onClicked: pageRow.currentIndex = modelData;
+        }
+    }
+}
diff --git a/src/controls/private/globaltoolbar/TitlesPageHeader.qml b/src/controls/private/globaltoolbar/TitlesPageHeader.qml
new file mode 100644 (file)
index 0000000..cccec53
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+
+AbstractPageHeader {
+    id: root
+
+    Loader {
+        id: titleLoader
+
+        anchors {
+            verticalCenter: parent.verticalCenter
+            left: parent.left
+            right: parent.right
+        }
+        height: Math.min(root.height, item
+            ? (item.Layout.preferredHeight > 0 ? item.Layout.preferredHeight : item.implicitHeight)
+            : 0)
+
+        // Don't load async to prevent jumpy behaviour on slower devices as it loads in.
+        // If the title delegate really needs to load async, it should be its responsibility to do it itself.
+        asynchronous: false
+        sourceComponent: page ? page.titleDelegate : null
+    }
+}
diff --git a/src/controls/private/globaltoolbar/ToolBarPageHeader.qml b/src/controls/private/globaltoolbar/ToolBarPageHeader.qml
new file mode 100644 (file)
index 0000000..b2cba09
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+
+AbstractPageHeader {
+    id: root
+
+    implicitWidth: layout.implicitWidth + Kirigami.Units.smallSpacing * 2
+    implicitHeight: Math.max(titleLoader.implicitHeight, toolBar.implicitHeight) + Kirigami.Units.smallSpacing * 2
+
+    MouseArea {
+        anchors.fill: parent
+        onPressed: mouse => {
+            page.forceActiveFocus()
+            mouse.accepted = false
+        }
+    }
+
+    RowLayout {
+        id: layout
+        anchors.fill: parent
+        anchors.rightMargin: Kirigami.Units.smallSpacing
+        spacing: Kirigami.Units.smallSpacing
+
+        Loader {
+            id: titleLoader
+
+            Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+            Layout.fillWidth: item ? item.Layout.fillWidth : false
+            Layout.minimumWidth: item ? item.Layout.minimumWidth : -1
+            Layout.preferredWidth: item ? item.Layout.preferredWidth : -1
+            Layout.maximumWidth: item ? item.Layout.maximumWidth : -1
+
+            // Don't load async to prevent jumpy behaviour on slower devices as it loads in.
+            // If the title delegate really needs to load async, it should be its responsibility to do it itself.
+            asynchronous: false
+            sourceComponent: page ? page.titleDelegate : null
+        }
+
+        Kirigami.ActionToolBar {
+            id: toolBar
+
+            Layout.alignment: Qt.AlignVCenter
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            visible: actions.length > 0
+            alignment: pageRow ? pageRow.globalToolBar.toolbarActionAlignment : Qt.AlignRight
+            heightMode: pageRow ? pageRow.globalToolBar.toolbarActionHeightMode : Kirigami.ToolBarLayout.ConstrainIfLarger
+
+            actions: {
+                if (!page) {
+                    return []
+                }
+
+                const result = []
+
+                if (page.actions.main) {
+                    result.push(page.actions.main)
+                }
+                if (page.actions.left) {
+                    result.push(page.actions.left)
+                }
+                if (page.actions.right) {
+                    result.push(page.actions.right)
+                }
+                if (page.actions.contextualActions.length > 0) {
+                    return result.concat(Array.prototype.map.call(page.actions.contextualActions, function(item) { return item }))
+                }
+                return result
+            }
+
+            Binding {
+                target: page.actions.main
+                property: "displayHint"
+                value: page.actions.main ? (page.actions.main.displayHint | Kirigami.DisplayHint.KeepVisible) : null
+                restoreMode: Binding.RestoreBinding
+            }
+            Binding {
+                target: page.actions.left
+                property: "displayHint"
+                value: page.actions.left ? (page.actions.left.displayHint | Kirigami.DisplayHint.KeepVisible) : null
+                restoreMode: Binding.RestoreBinding
+            }
+            Binding {
+                target: page.actions.right
+                property: "displayHint"
+                value: page.actions.right ? (page.actions.right.displayHint | Kirigami.DisplayHint.KeepVisible) : null
+                restoreMode: Binding.RestoreBinding
+            }
+        }
+    }
+}
diff --git a/src/controls/qmldir b/src/controls/qmldir
new file mode 100644 (file)
index 0000000..1afcd72
--- /dev/null
@@ -0,0 +1,6 @@
+module org.kde.kirigami
+plugin kirigamiplugin
+classname KirigamiPlugin
+depends QtQuick.Controls 2.0
+depends QtGraphicalEffects 1.0
+designersupported
diff --git a/src/controls/settingscomponents/CategorizedSettings.qml b/src/controls/settingscomponents/CategorizedSettings.qml
new file mode 100644 (file)
index 0000000..2da22f1
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
+ *  SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
+ *  SPDX-FileCopyrightText: 2021 Felipe Kinoshita <kinofhek@gmail.com>
+ *  SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.11 as Kirigami
+
+/**
+ * @brief This is a container for to show a list of actions in a list view and displaying
+ * the actual page next to it.
+ *
+ * @see kirigami::SettingAction
+ * @since KDE Frameworks 5.86
+ * @since org.kde.kirigami 2.18
+ * @inherit kirigami::PageRow
+ */
+Kirigami.PageRow {
+    id: pageSettingStack
+
+    /**
+     * @brief The default page that will be shown when opened.
+     *
+     * This only applies when the categorized settings object is wide enough to
+     * show multiple pages.
+     *
+     * @see @link actions @endlink
+     */
+    property string defaultPage
+
+    property list<Kirigami.PagePoolAction> actions
+    property alias stack: pageSettingStack
+    property Kirigami.PagePool pool: Kirigami.PagePool {}
+
+    readonly property string title: pageSettingStack.depth < 2 ? qsTr("Settings") : qsTr("Settings — %1").arg(pageSettingStack.get(1).title)
+
+    property bool completed: false
+
+    bottomPadding: 0
+    leftPadding: 0
+    rightPadding: 0
+    topPadding: 0
+
+    // With this, we get the longest word's width
+    TextMetrics {
+        id: maxWordMetrics
+    }
+    columnView.columnWidth: {
+        if(!pageSettingStack.completed || actions.length === 0) {
+            return Kirigami.Units.gridUnit * 6  // we return the min width if the component isn't completed
+        }
+        let maxWordWidth = 0;
+        for (let i = 0; i < actions.length; i++) {
+            const words = actions[i].text.split(" ");
+
+            for (let j = 0; j < words.length; j++) {
+                maxWordMetrics.text = words[j]
+                const currWordWidth = Math.ceil(maxWordMetrics.advanceWidth)
+                if (currWordWidth > maxWordWidth) {
+                    maxWordWidth = currWordWidth
+                }
+            }
+        }
+
+        // fix words getting wrapped weirdly when the vertical scrollbar is shown
+        const vScrollBarWidth = initialPage.contentItem.QQC2.ScrollBar.vertical.width;
+
+        // sum maximum word width, ListView's delegate spacing, and vertical scrollbar width
+        const calcWidth = maxWordWidth + Kirigami.Units.smallSpacing * 6 + vScrollBarWidth;
+        const minWidth = Kirigami.Units.gridUnit * 6;
+        const maxWidth = Kirigami.Units.gridUnit * 8.5;
+
+        return Math.max(minWidth, Math.min(calcWidth, maxWidth));
+    }
+    globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.NoNavigationButtons
+    globalToolBar.style: Kirigami.Settings.isMobile ? Kirigami.ApplicationHeaderStyle.Breadcrumb : Kirigami.ApplicationHeaderStyle.None
+
+    signal backRequested(var event)
+    onBackRequested: event => {
+        if (pageSettingStack.depth > 1 && !pageSettingStack.wideMode && pageSettingStack.currentIndex !== 0) {
+            event.accepted = true;
+            pageSettingStack.pop();
+        }
+    }
+    onWidthChanged: {
+        if (pageSettingStack.depth < 2 && pageSettingStack.width >= Kirigami.Units.gridUnit * 40) {
+            let defaultAction = getActionByName(defaultPage);
+            if (defaultAction) {
+                defaultAction.trigger();
+                listview.currentIndex = indexOfAction(defaultAction);
+            } else {
+                actions[0].trigger();
+                listview.currentIndex = 0;
+            }
+        }
+    }
+
+    initialPage: Kirigami.ScrollablePage {
+        title: qsTr("Settings")
+        bottomPadding: 0
+        leftPadding: 0
+        rightPadding: 0
+        topPadding: 0
+        Kirigami.Theme.colorSet: Kirigami.Theme.View
+        ListView {
+            id: listview
+            Component.onCompleted: if (pageSettingStack.width >= Kirigami.Units.gridUnit * 40) {
+                let defaultAction = getActionByName(defaultPage);
+                if (defaultAction) {
+                    defaultAction.trigger();
+                    listview.currentIndex = indexOfAction(defaultAction);
+                } else {
+                    actions[0].trigger();
+                    listview.currentIndex = 0;
+                }
+            } else {
+                if (count > 0) {
+                    listview.currentIndex = 0;
+                } else {
+                    listview.currentIndex = -1;
+                }
+            }
+            model: pageSettingStack.actions
+            delegate: pageSettingStack.wideMode ? desktopStyle : mobileStyle
+        }
+    }
+
+    Component {
+        id: desktopStyle
+
+        QQC2.ItemDelegate {
+            width: parent && parent.width > 0 ? parent.width : implicitWidth
+            implicitWidth: contentItem.implicitWidth + Kirigami.Units.smallSpacing * 4
+            implicitHeight: contentItem.implicitHeight + Kirigami.Units.smallSpacing * 2
+
+            padding: Kirigami.Units.smallSpacing
+
+            action: modelData
+            highlighted: listview.currentIndex === index
+            onClicked: listview.currentIndex = index
+            contentItem: ColumnLayout {
+                spacing: Kirigami.Units.smallSpacing
+
+                Kirigami.Icon {
+                    Layout.alignment: Qt.AlignHCenter
+                    Layout.preferredWidth: Kirigami.Units.iconSizes.medium
+                    Layout.preferredHeight: width
+                    source: modelData.icon.name
+                }
+
+                QQC2.Label {
+                    Layout.fillWidth: true
+                    Layout.leftMargin: Kirigami.Units.smallSpacing
+                    Layout.rightMargin: Kirigami.Units.smallSpacing
+                    text: modelData.text
+                    wrapMode: Text.Wrap
+                    color: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
+                    horizontalAlignment: Text.AlignHCenter
+                }
+            }
+
+        }
+    }
+
+    Component {
+        id: mobileStyle
+
+        Kirigami.BasicListItem {
+            action: modelData
+            onClicked: {
+                listview.currentIndex = index;
+            }
+        }
+    }
+
+    Component.onCompleted: {
+        pageSettingStack.completed = true;
+    }
+
+    function getActionByName(actionName) {
+        for (let i = 0; i < actions.length; i++) {
+            if (actions[i].actionName == actionName) {
+                return actions[i];
+            }
+        }
+    }
+
+    function indexOfAction(action) {
+        for (let i = 0; i < actions.length; i++) {
+            if (actions[i] == action) {
+                return i;
+            }
+        }
+    }
+}
+
diff --git a/src/controls/settingscomponents/SettingAction.qml b/src/controls/settingscomponents/SettingAction.qml
new file mode 100644 (file)
index 0000000..8abb058
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Felipe Kinoshita <kinofhek@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import org.kde.kirigami 2.11 as Kirigami
+
+/**
+ * @brief SettingAction defines a settings page, and is typically used by a CategorizedSettings object.
+ * @since KDE Frameworks 5.86
+ * @since org.kde.kirigami 2.18
+ * @inherit kirigami::PagePoolAction
+ */
+Kirigami.PagePoolAction {
+
+    /**
+     * @brief The name of the action for when it needs to be referenced.
+     *
+     * Primary use case if for setting a default page in CategorizedSettings.
+     *
+     * @warning This property will be required in KF6 but isn't for the KF5 backport
+     *          to avoid randomly breaking everyone's exisitng settings.
+     */
+    property string actionName
+
+    pageStack: stack
+    pagePool: pool
+    basePage: stack.initialPage
+
+    checkable: false
+}
diff --git a/src/controls/swipenavigator/PageTab.qml b/src/controls/swipenavigator/PageTab.qml
new file mode 100644 (file)
index 0000000..a046b95
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import "templates" as T
+
+T.PageTab {
+    id: control
+
+    implicitWidth: vertical ? verticalTitleRow.implicitWidth : horizontalTitleRow.implicitWidth
+    implicitHeight: vertical ? verticalTitleRow.implicitHeight : horizontalTitleRow.implicitHeight
+
+    background: Rectangle {
+        border {
+            width: activeFocus ? 2 : 0
+            color: Kirigami.Theme.textColor
+        }
+        color: {
+            if (control.active) {
+                return Kirigami.ColorUtils.adjustColor(Kirigami.Theme.activeTextColor, {"alpha": 0.2*255})
+            } else if (control.needsAttention) {
+                return Kirigami.ColorUtils.adjustColor(Kirigami.Theme.negativeTextColor, {"alpha": 0.2*255})
+            } else {
+                return "transparent"
+            }
+        }
+    }
+
+    PrivateSwipeHighlight {
+        states: [
+            State { name: "highlighted"; when: control.active },
+            State { name: "requestingAttention"; when: control.needsAttention }
+        ]
+    }
+
+    PrivateSwipeProgress {
+        anchors.fill: parent
+        visible: control.progress !== undefined
+        progress: control.progress
+    }
+
+    RowLayout {
+        id: verticalTitleRow
+        anchors.fill: parent
+        Accessible.ignored: true
+        visible: vertical
+
+        ColumnLayout {
+            Layout.margins: Kirigami.Settings.isMobile ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
+            Layout.alignment: Qt.AlignCenter
+
+            Kirigami.Icon {
+                visible: !!control.icon.name
+                source: control.icon.name
+
+                Layout.preferredHeight: (control.presentation === T.PageTab.Presentation.Large)
+                    ? Kirigami.Units.iconSizes.medium
+                    : (Kirigami.Settings.isMobile ? Kirigami.Units.iconSizes.smallMedium : Kirigami.Units.iconSizes.small)
+                Layout.preferredWidth: Layout.preferredHeight
+
+                Layout.alignment: (Qt.AlignHCenter | Qt.AlignBottom)
+            }
+            Kirigami.Heading {
+                level: (control.presentation === T.PageTab.Presentation.Large) ? 2 : 5
+                text: control.title
+                horizontalAlignment: Text.AlignHCenter
+                elide: Text.ElideRight
+
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignCenter
+            }
+        }
+    }
+
+    RowLayout {
+        id: horizontalTitleRow
+        anchors.fill: parent
+        Accessible.ignored: true
+        visible: !vertical
+
+        RowLayout {
+            Layout.margins: (control.presentation === T.PageTab.Presentation.Large) ? Kirigami.Units.largeSpacing*2 : Kirigami.Units.largeSpacing
+            Layout.alignment: Qt.AlignVCenter
+
+            Kirigami.Icon {
+                visible: !!control.icon.name
+                source: control.icon.name
+
+                Layout.preferredHeight: (control.presentation === T.PageTab.Presentation.Large)
+                    ? Kirigami.Units.iconSizes.medium
+                    : (Kirigami.Settings.isMobile ? Kirigami.Units.iconSizes.smallMedium : Kirigami.Units.iconSizes.small)
+                Layout.preferredWidth: Layout.preferredHeight
+
+                Layout.alignment: (Qt.AlignLeft | Qt.AlignVCenter)
+            }
+            Kirigami.Heading {
+                level: (control.presentation === T.PageTab.Presentation.Large) ? 1 : 2
+                text: control.title
+
+                Layout.fillWidth: true
+                Layout.alignment: (Qt.AlignLeft | Qt.AlignVCenter)
+            }
+        }
+    }
+
+    Layout.fillHeight: true
+    Layout.alignment: Qt.AlignHCenter
+}
diff --git a/src/controls/swipenavigator/PrivateSwipeHighlight.qml b/src/controls/swipenavigator/PrivateSwipeHighlight.qml
new file mode 100644 (file)
index 0000000..201acc8
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import org.kde.kirigami 2.12 as Kirigami
+
+Rectangle {
+    Accessible.ignored: true
+
+    anchors {
+        bottom: Kirigami.Settings.isMobile ? undefined : parent.bottom
+        top: Kirigami.Settings.isMobile ? parent.top : undefined
+        left: parent.left
+        right: parent.right
+    }
+
+    color: {
+        if (state === "highlighted") {
+            return Kirigami.Theme.activeTextColor
+        } else if (state === "requestingAttention") {
+            return Kirigami.Theme.negativeTextColor
+        }
+        return "transparent"
+    }
+
+    // Unlike most things, we don't want to scale with the em grid, so we don't use a Unit.
+    height: 2
+}
diff --git a/src/controls/swipenavigator/PrivateSwipeProgress.qml b/src/controls/swipenavigator/PrivateSwipeProgress.qml
new file mode 100644 (file)
index 0000000..7b32ab3
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import org.kde.kirigami 2.12 as Kirigami
+
+Item {
+    id: __progressRoot
+    property var progress
+
+    Rectangle {
+        Accessible.ignored: true
+
+        anchors {
+            top: parent.top
+            bottom: parent.bottom
+            left: parent.left
+        }
+        width: parent.width * __progressRoot.progress
+        color: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.positiveTextColor, {"alpha": 0.2*255})
+
+        Rectangle {
+            anchors {
+                bottom: Kirigami.Settings.isMobile ? undefined : parent.bottom
+                top: Kirigami.Settings.isMobile ? parent.top : undefined
+                left: parent.left
+                right: parent.right
+            }
+
+            color: Kirigami.Theme.positiveTextColor
+
+            // Unlike most things, we don't want to scale with the em grid, so we don't use a Unit.
+            height: 2
+        }
+    }
+
+
+    Rectangle {
+        Accessible.ignored: true
+
+        anchors {
+            top: parent.top
+            bottom: parent.bottom
+            right: parent.right
+        }
+        width: parent.width - (parent.width * __progressRoot.progress)
+        color: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.textColor, {"alpha": 0.1*255})
+
+        Rectangle {
+            anchors {
+                bottom: Kirigami.Settings.isMobile ? undefined : parent.bottom
+                top: Kirigami.Settings.isMobile ? parent.top : undefined
+                left: parent.left
+                right: parent.right
+            }
+
+            color: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.textColor, {"alpha": 0.1*255})
+
+            // Unlike most things, we don't want to scale with the em grid, so we don't use a Unit.
+            height: 2
+        }
+    }
+}
diff --git a/src/controls/swipenavigator/PrivateSwipeStack.qml b/src/controls/swipenavigator/PrivateSwipeStack.qml
new file mode 100644 (file)
index 0000000..6761312
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+
+QQC2.StackView {
+    popEnter: Transition {
+        OpacityAnimator {
+            from: 0
+            to: 1
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutCubic
+        }
+    }
+    popExit: Transition {
+        ParallelAnimation {
+            OpacityAnimator {
+                from: 1
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+            YAnimator {
+                from: 0
+                to: height/2
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InCubic
+            }
+        }
+    }
+
+    pushEnter: Transition {
+        ParallelAnimation {
+            // NOTE: It's a PropertyAnimation instead of an Animator because with an animator the item will be visible for an instant before starting to fade
+            PropertyAnimation {
+                property: "opacity"
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+            YAnimator {
+                from: height/2
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+    }
+
+
+    pushExit: Transition {
+        OpacityAnimator {
+            from: 1
+            to: 0
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutCubic
+        }
+    }
+
+    replaceEnter: Transition {
+        ParallelAnimation {
+            OpacityAnimator {
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+            YAnimator {
+                from: height/2
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+    }
+
+    replaceExit: Transition {
+        ParallelAnimation {
+            OpacityAnimator {
+                from: 1
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InCubic
+            }
+            YAnimator {
+                from: 0
+                to: -height/2
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+        }
+    }
+}
diff --git a/src/controls/swipenavigator/PrivateSwipeTab.qml b/src/controls/swipenavigator/PrivateSwipeTab.qml
new file mode 100644 (file)
index 0000000..56e9b11
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import org.kde.kirigami 2.17 as Kirigami
+
+Kirigami.PageTab {
+    id: tabRoot
+
+    active: index === columnView.currentIndex
+    title: modelData.title
+    progress: modelData.progress
+    needsAttention: modelData.needsAttention
+    icon: modelData.icon
+
+    signal indexChanged(real xPos, real tabWidth)
+
+    onActiveFocusChanged: {
+        if (activeFocus) {
+            tabRoot.indexChanged(tabRoot.x, tabRoot.width)
+        }
+    }
+    TapHandler {
+        onTapped: eventPoint => {
+            columnView.currentIndex = index;
+        }
+    }
+    Connections {
+        target: columnView
+        function onCurrentIndexChanged() {
+            if (index === columnView.currentIndex) {
+                tabRoot.indexChanged(tabRoot.x, tabRoot.width)
+            }
+        }
+    }
+}
diff --git a/src/controls/swipenavigator/PrivateSwipeTabBar.qml b/src/controls/swipenavigator/PrivateSwipeTabBar.qml
new file mode 100644 (file)
index 0000000..28f4451
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+
+QQC2.ScrollView {
+    id: view
+    implicitWidth: bar.implicitWidth
+    QQC2.ScrollBar.horizontal.visible: false
+
+    Item {
+        height: view.height
+        implicitHeight: bar.implicitHeight
+        implicitWidth: bar.implicitWidth
+        width: Math.max(view.width, bar.implicitWidth)
+
+        RowLayout {
+            id: bar
+            spacing: 0
+            signal indexChanged(real xPos, real tabWidth)
+
+            anchors.centerIn: parent
+            width: Kirigami.Settings.isMobile && swipeNavigatorRoot.height > swipeNavigatorRoot.width ? parent.width : implicitWidth
+            property real targetDestination
+            NumberAnimation {
+                id: scrollAni
+                target: view.QQC2.ScrollBar.horizontal
+                property: "position"
+                to: bar.targetDestination
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutExpo
+            }
+            onIndexChanged: (xPos, tabWidth) => {
+                if (xPos > (bar.width)/2) {
+                    bar.targetDestination = (1-view.QQC2.ScrollBar.horizontal.size) * ((xPos+tabWidth) / bar.width)
+                    scrollAni.restart()
+                } else {
+                    bar.targetDestination = (1-view.QQC2.ScrollBar.horizontal.size) * ((xPos) / bar.width)
+                    scrollAni.restart()
+                }
+            }
+
+            property Item layouter: Item {
+                Row {
+                    id: expandedLayouter
+                    Repeater {
+                        model: swipeNavigatorRoot.pages
+                        delegate: PrivateSwipeTab { vertical: false }
+                    }
+                }
+            }
+
+            Repeater {
+                model: swipeNavigatorRoot.pages
+                delegate: PrivateSwipeTab {
+                    Layout.fillHeight: true
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignHCenter
+                    vertical: Kirigami.Settings.isMobile
+                        ? (swipeNavigatorRoot.width < swipeNavigatorRoot.height ? true : expandedLayouter.width > swipeNavigatorRoot.width)
+                        : expandedLayouter.width > swipeNavigatorRoot.width
+                    onIndexChanged: (xPos, tabWidth) => bar.indexChanged(xPos, tabWidth)
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/swipenavigator/SwipeNavigator.qml b/src/controls/swipenavigator/SwipeNavigator.qml
new file mode 100644 (file)
index 0000000..21b12eb
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.13 as Kirigami
+import "." as SN
+
+
+//TODO KF6: remove all of this?
+/**
+ * @brief SwipeNavigator is a control providing for lateral navigation.
+ * @include swipenavigator/main.qml
+ * @warning This might be removed in KF6.
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: swipeNavigatorRoot
+
+//BEGIN properties
+    /**
+     * @brief This property holds the pages to swipe between.
+     */
+    default property list<Kirigami.Page> pages
+
+    /**
+     * @brief This property holds the StackView that is holding the core item,
+     * which allows users of SwipeNavigator to push pages on top of it.
+     *
+     * @property QtQuick.Controls.StackView stackView
+     */
+    property alias layers: stackView
+
+    /**
+     * @brief This property sets whether SwipeNavigator should be presented in large format,
+     * which is suitable for televisions.
+     *
+     * default: ``false``
+     */
+    property bool big: false
+
+    /**
+     * @brief This property holds the item that will be displayed before the tabs.
+     * @property Item header
+     */
+    property Component header: Item {visible: false}
+
+    /**
+     * @brief This property holds the item that will be displayed after the tabs.
+     * @property Item footer
+     */
+    property Component footer: Item {visible: false}
+
+    /**
+     * @brief This property holds the initial tab index of the SwipeNavigator.
+     *
+     * default: ``0``
+     */
+    property int initialIndex: 0
+
+    /**
+     * @brief This property holds the currently displayed page in the SwipeNavigator.
+     * @property int currentIndex
+     */
+    property alias currentIndex: columnView.currentIndex
+//END properties
+
+    /**
+     * @brief Pushes a page as a new dialog on desktop and as a layer on mobile.
+     * @param page The page can be defined as a component, item or string. If an item is
+     *             used then the page will get re-parented. If a string is used then it
+     *             is interpreted as a url that is used to load a page component.
+     * @param properties The properties given when initializing the page.
+     * @param windowProperties The properties given to the initialized window on desktop.
+     * @return The newly created page
+     */
+    function pushDialogLayer(page, properties = {}, windowProperties = {}) {
+        let item;
+        if (Kirigami.Settings.isMobile) {
+            item = layers.push(page, properties);
+        } else {
+            const windowComponent = Qt.createComponent(Qt.resolvedUrl("./ApplicationWindow.qml"));
+            if (!windowProperties.modality) {
+                windowProperties.modality = Qt.WindowModal;
+            }
+            if (!windowProperties.height) {
+                windowProperties.height = Kirigami.Units.gridUnit * 30;
+            }
+            if (!windowProperties.width) {
+                windowProperties.width = Kirigami.Units.gridUnit * 50;
+            }
+            if (!windowProperties.minimumWidth) {
+                windowProperties.minimumWidth = Kirigami.Units.gridUnit * 20;
+            }
+            if (!windowProperties.minimumHeight) {
+                windowProperties.minimumHeight = Kirigami.Units.gridUnit * 15;
+            }
+            if (!windowProperties.flags) {
+                windowProperties.flags = Qt.Dialog | Qt.WindowCloseButtonHint;
+            }
+            const window = windowComponent.createObject(swipeNavigatorRoot, windowProperties);
+            windowComponent.destroy();
+            item = window.pageStack.push(page, properties);
+        }
+        item.Keys.escapePressed.connect(event => item.closeDialog());
+        return item;
+    }
+
+    implicitWidth: stackView.implicitWidth
+    implicitHeight: stackView.implicitHeight
+
+    QtObject {
+        id: _gridManager
+        readonly property bool tall: (_header.width + __main.implicitWidth + Math.abs(__main.offset) + _footer.width) > swipeNavigatorRoot.width
+        readonly property int rowOne: Kirigami.Settings.isMobile ? 1 : 0
+        readonly property int rowTwo: Kirigami.Settings.isMobile ? 0 : 1
+        readonly property int rowDirection: Kirigami.Settings.isMobile ? 1 : -1
+        property Item item: Item {
+            states: [
+                State {
+                    name: "small"
+                    when: !_gridManager.tall
+                },
+                State {
+                    name: "tall"
+                    when: _gridManager.tall
+                }
+            ]
+            transitions: [
+                Transition {
+                    to: "tall"
+                    ScriptAction {
+                        script: {
+                            // Let's take these out of the layout first...
+                            _dummyOne.visible = false
+                            _dummyTwo.visible = false
+                            // Now we move the header and footer up
+                            _header.Layout.row += _gridManager.rowDirection
+                            _footer.Layout.row += _gridManager.rowDirection
+                            // Now that the header and footer are out of the way,
+                            // let's expand the tabs
+                            __main.Layout.column--
+                            __main.Layout.columnSpan = 3
+                        }
+                    }
+                },
+                Transition {
+                    to: "small"
+                    ScriptAction {
+                        script: {
+                            // Let's move the tabs back to where they belong
+                            __main.Layout.columnSpan = 1
+                            __main.Layout.column++
+                            // Move the header and footer down into the empty space
+                            _header.Layout.row -= _gridManager.rowDirection
+                            _footer.Layout.row -= _gridManager.rowDirection
+
+                            // Now we can bring these guys back in
+                            _dummyOne.visible = false
+                            _dummyTwo.visible = false
+                        }
+                    }
+                }
+            ]
+        }
+    }
+
+
+    QQC2.StackView {
+        id: stackView
+
+        anchors.fill: parent
+
+        function clear() {
+            // don't let it kill the main page row
+            const d = stackView.depth;
+            for (let i = 1; i < d; ++i) {
+                pop();
+            }
+        }
+
+        initialItem: SN.TabViewLayout {
+            bar: QQC2.ToolBar {
+                id: topToolBar
+
+                padding: 0
+                bottomPadding: 1
+
+                GridLayout {
+                    id: _grid
+
+                    rowSpacing: 0
+                    columnSpacing: 0
+                    anchors.fill: parent
+                    rows: 2
+                    columns: 3
+
+                    // Row one
+                    Item {
+                        id: _spacer
+                        Layout.row: 0
+                        Layout.column: 1
+                        Layout.fillWidth: true
+                    }
+                    Item {
+                        id: _dummyOne
+                        Layout.row: 0
+                        Layout.column: 0
+                    }
+                    Item {
+                        id: _dummyTwo
+                        Layout.row: 0
+                        Layout.column: 2
+                    }
+
+                    // Row two
+                    Loader {
+                        id: _header
+                        sourceComponent: swipeNavigatorRoot.header
+                        Layout.row: 1
+                        Layout.column: 0
+                    }
+                    SN.PrivateSwipeTabBar {
+                        id: __main
+                        readonly property int offset: _header.width - _footer.width
+                        readonly property int effectiveOffset: _gridManager.tall ? 0 : offset
+                        Layout.rightMargin: effectiveOffset > 0 ? effectiveOffset : 0
+                        Layout.leftMargin: effectiveOffset < 0 ? -effectiveOffset : 0
+                        Layout.fillHeight: true
+                        Layout.fillWidth: true//Kirigami.Settings.isMobile && swipeNavigatorRoot.height > swipeNavigatorRoot.width
+                        Layout.alignment: Qt.AlignHCenter
+                        Layout.row: 1
+                        Layout.column: 1
+
+                    }
+                    Loader {
+                        id: _footer
+                        sourceComponent: swipeNavigatorRoot.footer
+                        Layout.row: 1
+                        Layout.column: 2
+                    }
+                }
+
+                Accessible.role: Accessible.PageTabList
+            }
+            contentItem: Kirigami.ColumnView {
+                id: columnView
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+                Layout.row: Kirigami.Settings.isMobile ? 0 : 1
+
+                columnResizeMode: Kirigami.ColumnView.SingleColumn
+
+                contentChildren: swipeNavigatorRoot.pages
+
+                Component.onCompleted: {
+                    columnView.currentIndex = swipeNavigatorRoot.initialIndex
+                }
+                // We only want the current page to be focusable, so we
+                // disable the inactive pages.
+                onCurrentIndexChanged: {
+                    Array.from(swipeNavigatorRoot.pages).forEach(item => item.enabled = false)
+                    swipeNavigatorRoot.pages[currentIndex].enabled = true
+                }
+            }
+        }
+        popEnter: Transition {
+            OpacityAnimator {
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+        }
+        popExit: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: 0
+                    to: height/2
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+            }
+        }
+
+        pushEnter: Transition {
+            ParallelAnimation {
+                // NOTE: It's a PropertyAnimation instead of an Animator because with an animator the item will be visible for an instant before starting to fade
+                PropertyAnimation {
+                    property: "opacity"
+                    from: 0
+                    to: 1
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: height/2
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+            }
+        }
+
+
+        pushExit: Transition {
+            OpacityAnimator {
+                from: 1
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutCubic
+            }
+        }
+
+        replaceEnter: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 0
+                    to: 1
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                YAnimator {
+                    from: height/2
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+            }
+        }
+
+        replaceExit: Transition {
+            ParallelAnimation {
+                OpacityAnimator {
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+                YAnimator {
+                    from: 0
+                    to: -height/2
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/swipenavigator/TabViewLayout.qml b/src/controls/swipenavigator/TabViewLayout.qml
new file mode 100644 (file)
index 0000000..3a4f169
--- /dev/null
@@ -0,0 +1,62 @@
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+
+/**
+ * Control for dynamically moving a bar above or below a content item,
+ * e.g. to move tabs to the bottom on mobile.
+ *
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: __root
+
+    /**
+     * @brief Position options for TabViewLayout.
+     */
+    enum Position {
+        Top,
+        Bottom
+    }
+
+    /**
+     * @brief The position of the bar in relation to the contentItem.
+     *
+     * default: `Position.Bottom on mobile, and Position.Top otherwise`
+     *
+     * @see ::Position
+     */
+    property int position: Kirigami.Settings.isMobile ? TabViewLayout.Position.Bottom : TabViewLayout.Position.Top
+
+    required property Item bar
+    onBarChanged: {
+        bar.parent = __grid
+        bar.Layout.row = Qt.binding(() => (__root.position === TabViewLayout.Position.Bottom) ? 1 : 0)
+        bar.Layout.fillWidth = true
+        if (bar instanceof QQC2.ToolBar) {
+            bar.position = Qt.binding(() => (__root.position === TabViewLayout.Position.Bottom) ? QQC2.ToolBar.Footer : QQC2.ToolBar.Header)
+        }
+    }
+
+    required property Item contentItem
+    onContentItemChanged: {
+        contentItem.parent = __grid
+        contentItem.Layout.row = Qt.binding(() => (__root.position === TabViewLayout.Position.Bottom) ? 0 : 1)
+        contentItem.Layout.fillWidth = true
+        contentItem.Layout.fillHeight = true
+    }
+
+    implicitWidth: __grid.implicitWidth
+    implicitHeight: __grid.implicitHeight
+
+    GridLayout {
+        id: __grid
+        children: [__root.bar, __root.contentItem]
+
+        rowSpacing: 0
+        columns: 1
+
+        anchors.fill: parent
+    }
+}
diff --git a/src/controls/swipenavigator/templates/PageTab.qml b/src/controls/swipenavigator/templates/PageTab.qml
new file mode 100644 (file)
index 0000000..9679620
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12 as QQC2
+import org.kde.kirigami 2.12 as Kirigami
+import "../../private" as Private
+
+// TODO?: refactor into abstractbutton
+QQC2.Control {
+    id: control
+
+    enum Presentation {
+        Normal,
+        Large
+    }
+
+    property string title
+    property bool active
+
+    property Private.ActionIconGroup icon: Private.ActionIconGroup {}
+    property int presentation: PageTab.Presentation.Normal
+    property bool vertical: false
+    property var progress // type: real?
+    property bool needsAttention: false
+
+    activeFocusOnTab: true
+
+    Accessible.name: control.title
+    Accessible.description: {
+        if (!!control.progress) {
+            if (control.active) {
+                //: Accessibility text for a page tab. Keep the text as concise as possible and don't use a percent sign.
+                return qsTr("Current page. Progress: %1 percent.").arg(Math.round(control.progress*100))
+            } else {
+                //: Accessibility text for a page tab. Keep the text as concise as possible.
+                return qsTr("Navigate to %1. Progress: %2 percent.").arg(control.title).arg(Math.round(control.progress*100))
+            }
+        } else {
+            if (control.active) {
+                //: Accessibility text for a page tab. Keep the text as concise as possible.
+                return qsTr("Current page.")
+            } else if (control.needsAttention) {
+                //: Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+                return qsTr("Navigate to %1. Demanding attention.", control.title)
+            } else {
+                //: Accessibility text for a page tab that's requesting the user's attention. Keep the text as concise as possible.
+                return qsTr("Navigate to %1.", control.title)
+            }
+        }
+    }
+    Accessible.role: Accessible.PageTab
+    Accessible.focusable: true
+    Accessible.onPressAction: control.clicked()
+
+    Keys.onPressed: event => {
+        if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
+            control.clicked()
+        }
+    }
+}
diff --git a/src/controls/templates/AbstractApplicationHeader.qml b/src/controls/templates/AbstractApplicationHeader.qml
new file mode 100644 (file)
index 0000000..27f8536
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.4 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+import "private"
+
+/**
+ * @brief An item that can be used as a title for the application.
+ *
+ * Scrolling the main page will make it taller or shorter (through the point of going away)
+ * It's a behavior similar to the typical mobile web browser addressbar
+ * the minimum, preferred and maximum heights of the item can be controlled with
+ * * minimumHeight: default is 0, i.e. hidden
+ * * preferredHeight: default is Units.gridUnit * 1.6
+ * * preferredHeight: default is Units.gridUnit * 3
+ *
+ * To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
+ *
+ * @inherit QtQuick.Item
+ */
+Item {
+    id: root
+    z: 90
+    property int minimumHeight: 0
+    property int preferredHeight: Math.max(...(Array.from(mainItem.children).map(elm => elm.implicitHeight))) + topPadding + bottomPadding
+    property int maximumHeight: Kirigami.Units.gridUnit * 3
+
+    property int position: QQC2.ToolBar.Header
+
+    property Kirigami.PageRow pageRow: __appWindow ? __appWindow.pageStack: null
+    property Kirigami.Page page: pageRow ? pageRow.currentItem : null
+
+    default property alias contentItem: mainItem.data
+    readonly property int paintedHeight: headerItem.y + headerItem.height - 1
+
+    property int leftPadding: 0
+    property int topPadding: 0
+    property int rightPadding: 0
+    property int bottomPadding: 0
+    property bool separatorVisible: true
+    // whether or not the header should be
+    // "pushed" back when scrolling using the
+    // touch screen
+    property bool hideWhenTouchScrolling: root.pageRow ? root.pageRow.globalToolBar.hideWhenTouchScrolling : false
+
+    LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
+    LayoutMirroring.childrenInherit: true
+
+    Kirigami.Theme.inherit: true
+
+    // FIXME: remove
+    /** @internal */
+    property QtObject __appWindow: typeof applicationWindow !== "undefined" ? applicationWindow() : null;
+    implicitHeight: preferredHeight
+    height: Layout.preferredHeight
+
+    /**
+     * @brief This property holds the background item.
+     * @note the background will be automatically sized to fill the whole control
+     */
+    property Item background
+
+    onBackgroundChanged: {
+        background.z = -1;
+        background.parent = headerItem;
+        background.anchors.fill = headerItem;
+    }
+
+    Component.onCompleted: AppHeaderSizeGroup.items.push(this)
+
+    onMinimumHeightChanged: implicitHeight = preferredHeight;
+    onPreferredHeightChanged: implicitHeight = preferredHeight;
+
+    opacity: height > 0 ? 1 : 0
+
+    onPageChanged: {
+        // NOTE: The Connections object doesn't work with attached properties signals, so we have to do this by hand
+        if (headerItem.oldPage) {
+            headerItem.oldPage.Kirigami.ColumnView.scrollIntention.disconnect(headerItem.scrollIntentHandler);
+        }
+        if (root.page) {
+            root.page.Kirigami.ColumnView.scrollIntention.connect(headerItem.scrollIntentHandler);
+        }
+        headerItem.oldPage = root.page;
+    }
+    Component.onDestruction: {
+        if (root.page) {
+            root.page.Kirigami.ColumnView.scrollIntention.disconnect(headerItem.scrollIntentHandler);
+        }
+    }
+
+    NumberAnimation {
+        id: heightAnim
+        target: root
+        property: "implicitHeight"
+        duration: Kirigami.Units.longDuration
+        easing.type: Easing.InOutQuad
+    }
+    Connections {
+        target: __appWindow
+        function onControlsVisibleChanged() {
+            heightAnim.from = root.implicitHeight
+            heightAnim.to = __appWindow.controlsVisible ? root.preferredHeight : 0;
+            heightAnim.restart();
+        }
+    }
+
+    Item {
+        id: headerItem
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Header ? parent.bottom : undefined
+            top: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Footer ? parent.top : undefined
+        }
+
+        height: __appWindow && __appWindow.reachableMode && __appWindow.reachableModeEnabled ? root.maximumHeight : (root.minimumHeight > 0 ? Math.max(root.height, root.minimumHeight) : Math.max(root.height, root.preferredHeight))
+
+        function scrollIntentHandler(event) {
+            if (!root.hideWhenTouchScrolling) {
+                return
+            }
+
+            if (root.pageRow
+                && root.pageRow.globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.TabBar
+                && root.pageRow.globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.Breadcrumb) {
+                return;
+            }
+            if (!root.page.flickable || (root.page.flickable.atYBeginning && root.page.flickable.atYEnd)) {
+                return;
+            }
+
+            root.implicitHeight = Math.max(0, Math.min(root.preferredHeight, root.implicitHeight + event.delta.y))
+            event.accepted = root.implicitHeight > 0 && root.implicitHeight < root.preferredHeight;
+            slideResetTimer.restart();
+            if ((root.page.flickable instanceof ListView) && root.page.flickable.verticalLayoutDirection === ListView.BottomToTop) {
+                root.page.flickable.contentY -= event.delta.y;
+            }
+        }
+
+        property Kirigami.Page oldPage
+
+        Connections {
+            target: root.page ? root.page.globalToolBarItem : null
+            enabled: target
+            function onImplicitHeightChanged() {
+                root.implicitHeight = root.page.globalToolBarItem.implicitHeight;
+            }
+        }
+
+        Timer {
+           id: slideResetTimer
+           interval: 500
+           onTriggered: {
+                if ((root.pageRow ? root.pageRow.wideMode : (__appWindow && __appWindow.wideScreen)) || !Kirigami.Settings.isMobile) {
+                    return;
+                }
+                if (root.height > root.minimumHeight + (root.preferredHeight - root.minimumHeight)/2 ) {
+                    heightAnim.to = root.preferredHeight;
+                } else {
+                    heightAnim.to = root.minimumHeight;
+                }
+                heightAnim.from = root.implicitHeight
+                heightAnim.restart();
+            }
+        }
+
+        Connections {
+            target: pageRow
+            function onCurrentItemChanged() {
+                if (!root.page) {
+                    return;
+                }
+
+                heightAnim.from = root.implicitHeight;
+                heightAnim.to = root.preferredHeight;
+
+                heightAnim.restart();
+            }
+        }
+
+        Item {
+            id: mainItem
+            clip: childrenRect.width > width
+            onChildrenChanged: {
+                Array.from(children).forEach(item => {
+                    item.anchors.verticalCenter = this.verticalCenter;
+                })
+            }
+            anchors {
+                fill: parent
+                leftMargin: root.leftPadding
+                topMargin: root.topPadding
+                rightMargin: root.rightPadding
+                bottomMargin: root.bottomPadding
+            }
+        }
+    }
+}
diff --git a/src/controls/templates/AbstractCard.qml b/src/controls/templates/AbstractCard.qml
new file mode 100644 (file)
index 0000000..9a982ef
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import QtQuick.Templates 2.0 as T
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * AbstractCard is the base type for cards. A Card is a visual object that serves
+ * as an entry point for more detailed information. An AbstractCard is empty,
+ * providing just the look and the base properties and signals for a QtQuick.Controls.ItemDelegate.
+ * It can be filled with any custom layout of items, its content is organized
+ * into 3 properties: ::header, contentItem and ::footer.
+ * Use this only when you need custom contents.
+ * For cards with a standard layout, use the Card component.
+ *
+ * @see kirigami::Card
+ * @since org.kde.kirigami 2.4
+ * @inherit QtQuick.Controls.ItemDelegate
+ */
+T.ItemDelegate {
+    id: root
+
+//BEGIN properties
+    /**
+     * @brief This property holds an item that serves as a header.
+     *
+     * This item will be positioned on top if headerOrientation is
+     * ``Qt.Vertical`` or on the left if it is ``Qt.Horizontal``.
+     */
+    property Item header
+
+    /**
+     * @brief This property sets the card's orientation.
+     *
+     * The following values are allowed:
+     * * ``Qt.Vertical``: the header will be positioned on top.
+     * * ``Qt.Horizontal``: the header will be positioned on the
+     * left (or right if an RTL layout is used).
+     *
+     * default: ``Qt.Vertical``
+     *
+     * @property Qt::Orientation headerOrientation
+     */
+    property int headerOrientation: Qt.Vertical
+
+    /**
+     * @brief This property holds an Item that serves as a footer.
+     *
+     * This item will be positioned at the bottom if headerOrientation
+     * is ``Qt.Vertical`` or on the right if it is ``Qt.Horizontal``.
+     */
+    property Item footer
+
+    /**
+     * @brief This property sets whether clicking or tapping on the
+     * card area shows a visual click feedback.
+     *
+     * Use this if you want to do an action in the onClicked signal
+     * handler of the card.
+     *
+     * default: ``false``
+     */
+    property bool showClickFeedback: false
+//END properties
+
+    Layout.fillWidth: true
+
+    implicitWidth: Math.max(background.implicitWidth, mainLayout.implicitWidth) + leftPadding + rightPadding
+    implicitHeight: mainLayout.implicitHeight + topPadding + bottomPadding
+
+    hoverEnabled: !Kirigami.Settings.tabletMode && showClickFeedback
+    // if it's in a CardLayout, try to expand horizontal cards to both columns
+    Layout.columnSpan: headerOrientation === Qt.Horizontal && parent.hasOwnProperty("columns") ? parent.columns : 1
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: Kirigami.Theme.View
+
+    topPadding: contentItemParent.children.length > 0 ? Kirigami.Units.largeSpacing : 0
+    leftPadding: Kirigami.Units.largeSpacing
+    bottomPadding: contentItemParent.children.length > 0 ? Kirigami.Units.largeSpacing : 0
+    rightPadding: Kirigami.Units.largeSpacing
+
+    width: ListView.view ? ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin : undefined
+
+    GridLayout {
+        id: mainLayout
+        rowSpacing: root.topPadding
+        columnSpacing: root.leftPadding
+        anchors {
+            top: parent.top
+            left: parent.left
+            right: parent.right
+            leftMargin: root.leftPadding
+            topMargin: root.topPadding
+            rightMargin: root.rightPadding
+            bottom: parent.bottom
+            bottomMargin: root.bottomPadding
+        }
+        columns: headerOrientation === Qt.Vertical ? 1 : 2
+        function preferredHeight(item) {
+            if (!item) {
+                return 0;
+            }
+            if (item.Layout.preferredHeight > 0) {
+                return item.Layout.preferredHeight;
+            }
+            return item.implicitHeight
+        }
+        Item {
+            id: headerParent
+            Layout.fillWidth: true
+            Layout.fillHeight: root.headerOrientation === Qt.Horizontal
+            Layout.rowSpan: root.headerOrientation === Qt.Vertical ? 1 : 2
+            Layout.preferredWidth: header ? header.implicitWidth : 0
+            Layout.preferredHeight: root.headerOrientation === Qt.Vertical ? mainLayout.preferredHeight(header) : -1
+            visible: children.length > 0
+        }
+        Item {
+            id: contentItemParent
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.topMargin: root.topPadding
+            Layout.bottomMargin: root.bottomPadding
+            Layout.preferredWidth: contentItem ? contentItem.implicitWidth : 0
+            Layout.preferredHeight: mainLayout.preferredHeight(contentItem)
+            visible: children.length > 0
+        }
+        Item {
+            id: footerParent
+            Layout.fillWidth: true
+            Layout.preferredWidth: footer ? footer.implicitWidth : 0
+            Layout.preferredHeight: mainLayout.preferredHeight(footer)
+            visible: children.length > 0
+        }
+    }
+
+//BEGIN signal handlers
+    onContentItemChanged: {
+        if (!contentItem) {
+            return;
+        }
+
+        contentItem.parent = contentItemParent;
+        contentItem.anchors.fill = contentItemParent;
+    }
+    onHeaderChanged: {
+        if (!header) {
+            return;
+        }
+
+        header.parent = headerParent;
+        header.anchors.fill = headerParent;
+    }
+    onFooterChanged: {
+        if (!footer) {
+            return;
+        }
+
+        //make the footer always looking it's at the bottom of the card
+        footer.parent = footerParent;
+        footer.anchors.left = footerParent.left;
+        footer.anchors.top = footerParent.top;
+        footer.anchors.right = footerParent.right;
+        footer.anchors.topMargin = Qt.binding(() =>
+            (root.height - root.bottomPadding - root.topPadding) - (footerParent.y + footerParent.height));
+    }
+    Component.onCompleted: {
+        contentItemChanged();
+    }
+//END signal handlers
+}
diff --git a/src/controls/templates/AbstractChip.qml b/src/controls/templates/AbstractChip.qml
new file mode 100644 (file)
index 0000000..e6fd6c8
--- /dev/null
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Templates 2.15 as T
+
+/**
+ * @brief AbstractChip is a visual object based on QtQuick.Controls.AbstractButton
+ * that provides a friendly way to display predetermined elements
+ * with the visual styling of "tags" or "tokens."
+ *
+ * @see kirigami::Chip
+ * @since org.kde.kirigami 2.19
+ * @inherit QtQuick.Controls.AbstractButton
+ */
+T.AbstractButton {
+    id: chip
+
+    /**
+     * @brief This property holds whether or not to display a close button.
+     *
+     * default: ``true``
+     */
+    property bool closable: true
+
+    /**
+     * @brief This signal is emitted when the close button has been clicked.
+     */
+    signal removed()
+}
diff --git a/src/controls/templates/AbstractListItem.qml b/src/controls/templates/AbstractListItem.qml
new file mode 100644 (file)
index 0000000..e4a9c80
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Layouts 1.0
+import org.kde.kirigami 2.4 as Kirigami
+// TODO KF6: This must stay at 2.2 until KF6 due to retrocompatibility of the "icon" property
+import QtQuick.Templates 2.2 as T2
+import QtQuick.Templates 2.4 as QQC2
+
+/**
+ * @brief An item delegate for the primitive ListView component.
+ *
+ * It's intended to make all listviews look coherent.
+ *
+ * @inherit QtQuick.Controls.ItemDelegate
+ */
+T2.ItemDelegate {
+    id: listItem
+
+//BEGIN properties
+    /**
+     * @brief This property sets whether the item should emit signals related to mouse interaction.
+     *
+     * default: ``true``
+     *
+     * @deprecated This will be removed in KF6.
+     */
+    property bool supportsMouseEvents: hoverEnabled
+
+    /**
+     * @brief This property specifies whether the cursor is currently hovering over the item.
+     *
+     * On mobile touch devices, this will be @c true only when pressed.
+     *
+     * @see QtQuick.Templates.ItemDelegate.hovered
+     * @deprecated This will be removed in KF6; use the ``hovered``  property instead.
+     * @property bool containsMouse
+     */
+    property alias containsMouse: listItem.hovered
+
+    /**
+     * @brief This property sets whether instances of this list item will alternate
+     * between two colors, helping readability.
+     *
+     * It is suggested to use this only when implementing a view with multiple columns.
+     *
+     * default: ``false``
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    property bool alternatingBackground: false
+
+    /**
+     * @brief This property sets whether this item is a section delegate.
+     *
+     * Setting this to @c true will make the list item look like a "title" for items under it.
+     *
+     * default: ``false``
+     *
+     * @see kirigami::ListSectionHeader
+     */
+    property bool sectionDelegate: false
+
+    /**
+     * @brief This property sets whether the separator is visible.
+     *
+     * The separator is a line between this and the item under it.
+     *
+     * default: ``false``
+     */
+    property bool separatorVisible: false
+
+    /**
+     * @brief This property holds list item's background color.
+     *
+     * It is advised to use the default value.
+     *
+     * default: ``"transparent"``
+     */
+    property color backgroundColor: "transparent"
+
+    /**
+     * @brief This property holds the background color to be used when
+     * background alternating is enabled.
+     *
+     * It is advised to use the default value.
+     *
+     * default: @link Kirigami.PlatformTheme.alternateBackgroundColor Kirigami.Theme.alternateBackgroundColor @endlink
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    property color alternateBackgroundColor: Kirigami.Theme.alternateBackgroundColor
+
+    /**
+     * @brief This property holds the color of the background
+     * when the item is pressed or selected.
+     *
+     * It is advised to use the default value.
+     *
+     * default: @link Kirigami.PlatformTheme.highlightColor Kirigami.Theme.highlightColor @endlink
+     */
+    property color activeBackgroundColor: Kirigami.Theme.highlightColor
+
+    /**
+     * @brief This property holds the color of the text in the item.
+     *
+     * It is advised to use the default value.
+     *
+     * default: @link Kirigami.PlatformTheme.textColor Kirigami.Theme.textColor @endlink
+     *
+     * If custom text elements are inserted in an AbstractListItem,
+     * their color will have to be manually set with this property.
+     */
+    property color textColor: Kirigami.Theme.textColor
+
+    /**
+     * @brief This property holds the color of the text when the item is pressed or selected.
+     *
+     * It is advised to use the default value.
+     *
+     * default: @link Kirigami.PlatformTheme.highlightedTextColor Kirigami.Theme.highlightedTextColor @endlink
+     *
+     * If custom text elements are inserted in an AbstractListItem,
+     * their color will have to be manually set with this property.
+     */
+    property color activeTextColor: Kirigami.Theme.highlightedTextColor
+
+    /** @internal */
+    default property alias _default: listItem.contentItem
+
+    // NOTE: Overrides action property of newer import versions which we can't use
+    /**
+     * @brief This property holds the item action.
+     * @property QtQuick.Controls.Action action
+     */
+    property QQC2.Action action
+//END properties
+
+    activeFocusOnTab: ListView.view ? false : true
+
+    text: action ? action.text : undefined
+    checked: action ? action.checked : false
+    checkable: action ? action.checkable : false
+    onClicked: {
+        if (ListView.view && typeof index !== "undefined") {
+            ListView.view.currentIndex = index;
+        }
+        if (!action) {
+            return;
+        }
+
+        action.trigger();
+        checked = Qt.binding(function() { return action.checked });
+    }
+    //Theme.inherit: false
+    //Theme.colorSet: Kirigami.Theme.View
+
+    padding: Kirigami.Settings.tabletMode ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
+
+    leftPadding: padding * 2
+    topPadding: padding
+
+    rightPadding: padding * 2
+    bottomPadding: padding
+
+    implicitWidth: contentItem ? contentItem.implicitWidth + leftPadding + rightPadding : Kirigami.Units.gridUnit * 12
+
+    implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
+
+    width: parent && parent.width > 0 ? parent.width : implicitWidth
+    Layout.fillWidth: true
+
+    opacity: enabled ? 1 : 0.6
+
+    height: implicitHeight
+
+    onVisibleChanged: {
+        if (visible) {
+            height = Qt.binding(() => implicitHeight);
+        } else {
+            if (ListView.view && ListView.view.visible) {
+                height = 0;
+            }
+        }
+    }
+
+    hoverEnabled: true
+
+    Accessible.role: Accessible.ListItem
+    highlighted: focus && ListView.isCurrentItem && ListView.view && ListView.view.keyNavigationEnabled
+}
diff --git a/src/controls/templates/ApplicationHeader.qml b/src/controls/templates/ApplicationHeader.qml
new file mode 100644 (file)
index 0000000..8627fb9
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+import "private"
+
+
+/**
+ * @brief An item that can be used as a title for the application.
+ *
+ * Scrolling the main page will make it taller or shorter (through the point of going away)
+ * It's a behavior similar to the typical mobile web browser addressbar
+ * the minimum, preferred and maximum heights of the item can be controlled with
+ * * ``minimumHeight``: Default is 0, i.e. hidden
+ * * ``preferredHeight``: Default is Kirigami.Units.gridUnit * 1.6
+ * * ``maximumHeight``: Default is Kirigami.Units.gridUnit * 3
+ *
+ * To achieve a titlebar that stays completely fixed just set the 3 sizes to the same value.
+ *
+ * @deprecated This will be removed in KF6.
+ */
+AbstractApplicationHeader {
+    id: header
+
+//BEGIN properties
+    /**
+     * @brief This property sets the way the separator between pages should be drawn in the header.
+     *
+     * The following values are allowed:
+     * * ``Kirigami.ApplicationHeaderStyle.Breadcrumb``: The pages are hierarchical, separated by an arrow.
+     * * ``Kirigami.ApplicationHeaderStyle.TabBar``: The pages are intended to behave like pages of a tabbed view.
+     * and the separator will look limke a dot.
+     *
+     * When the header is in wide screen mode, no separator will be drawn.
+     *
+     * default: ``ApplicationHeaderStyle.Auto``
+     */
+    property int headerStyle: Kirigami.ApplicationHeaderStyle.Auto
+
+    /**
+     * @brief This property sets whether the back button is enabled.
+     *
+     * default: `when true, there will be a back button present that will make the pagerow scroll back when clicked`
+     */
+    property bool backButtonEnabled: (!titleList.isTabBar && (!Kirigami.Settings.isMobile || Qt.platform.os === "ios"))
+
+    property Component pageDelegate: Component {
+        Row {
+            height: parent.height
+
+            spacing: Kirigami.Units.smallSpacing
+
+            x: Kirigami.Units.smallSpacing
+
+            Kirigami.Icon {
+                // in tabbar mode this is just a spacer
+                visible: !titleList.wideMode && ((typeof modelData !== "undefined" && modelData > 0) || titleList.internalHeaderStyle === Kirigami.ApplicationHeaderStyle.TabBar)
+                anchors.verticalCenter: parent.verticalCenter
+                height: Kirigami.Units.iconSizes.small
+                width: height
+                selected: header.background && header.background.color && header.background.color === Kirigami.Theme.highlightColor
+                source: titleList.isTabBar ? "" : (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
+            }
+
+            Kirigami.Heading {
+                id: title
+                width: Math.min(parent.width, Math.min(titleList.width, implicitWidth)) + Kirigami.Units.smallSpacing
+                anchors.verticalCenter: parent.verticalCenter
+                opacity: current ? 1 : 0.4
+                // Scaling animate NativeRendering is too slow
+                renderType: Text.QtRendering
+                color: header.background && header.background.color && header.background.color === Kirigami.Theme.highlightColor ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
+                elide: Text.ElideRight
+                text: page ? page.title : ""
+                font.pointSize: -1
+                font.pixelSize: Math.max(1, titleList.height * 0.7)
+                verticalAlignment: Text.AlignVCenter
+                wrapMode: Text.NoWrap
+                Rectangle {
+                    anchors {
+                        bottom: parent.bottom
+                        left: parent.left
+                        right: parent.right
+                    }
+                    height: Kirigami.Units.smallSpacing
+                    color: title.color
+                    opacity: 0.6
+                    visible: titleList.isTabBar && current
+                }
+            }
+        }
+    }
+//END properties
+
+//BEGIN signal handlers
+    onBackButtonEnabledChanged: {
+        if (backButtonEnabled && !titleList.backButton) {
+            let component = Qt.createComponent(Qt.resolvedUrl("private/BackButton.qml"));
+            titleList.backButton = component.createObject(navButtons);
+            component.destroy();
+            component = Qt.createComponent(Qt.resolvedUrl("private/ForwardButton.qml"));
+            titleList.forwardButton = component.createObject(navButtons);
+            component.destroy();
+        } else if (titleList.backButton) {
+            titleList.backButton.destroy();
+            titleList.forwardButton.destroy();
+        }
+    }
+
+    Component.onCompleted: print("Warning: ApplicationHeader is deprecated, remove and use the automatic internal toolbar instead.")
+//END signal handlers
+
+    Rectangle {
+        anchors {
+            verticalCenter: parent.verticalCenter
+        }
+        visible: titleList.x > 0 && !titleList.atXBeginning
+        height: parent.height * 0.7
+        color: Kirigami.Theme.highlightedTextColor
+        width: Math.ceil(Kirigami.Units.smallSpacing / 6)
+        opacity: 0.4
+    }
+
+    QQC2.StackView {
+        id: stack
+        anchors {
+            fill: parent
+            leftMargin: navButtons.width
+            rightMargin: __appWindow.contextDrawer && __appWindow.contextDrawer.handleVisible && __appWindow.contextDrawer.handle && __appWindow.contextDrawer.handle.y === 0 ? __appWindow.contextDrawer.handle.width : 0
+        }
+        initialItem: titleList
+
+        popEnter: Transition {
+            YAnimator {
+                from: -height
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+        popExit: Transition {
+            YAnimator {
+                from: 0
+                to: height
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+
+        pushEnter: Transition {
+            YAnimator {
+                from: height
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+
+        pushExit: Transition {
+            YAnimator {
+                from: 0
+                to: -height
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+
+        replaceEnter: Transition {
+            YAnimator {
+                from: height
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+
+        replaceExit: Transition {
+            YAnimator {
+                from: 0
+                to: -height
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutCubic
+            }
+        }
+    }
+    Kirigami.Separator {
+        id: separator
+        height: parent.height * 0.6
+        visible: navButtons.width > 0
+        anchors {
+            verticalCenter: parent.verticalCenter
+            left: navButtons.right
+        }
+    }
+    Kirigami.Separator {
+        height: parent.height * 0.6
+        visible: stack.anchors.rightMargin > 0
+        anchors {
+            verticalCenter: parent.verticalCenter
+            right: parent.right
+            rightMargin: stack.anchors.rightMargin
+        }
+    }
+    Repeater {
+        model: pageRow.layers.depth -1
+        delegate: Loader {
+            // Don't load async to prevent jumpy behaviour on slower devices as it loads in.
+            // If the title delegate really needs to load async, it should be its responsibility to do it itself.
+            asynchronous: false
+            sourceComponent: header.pageDelegate
+            readonly property Kirigami.Page page: pageRow.layers.get(modelData+1)
+            readonly property bool current: true;
+            Component.onCompleted: stack.push(this)
+            Component.onDestruction: stack.pop()
+        }
+    }
+
+    Row {
+        id: navButtons
+        anchors {
+            left: parent.left
+            top: parent.top
+            bottom: parent.bottom
+            topMargin: Kirigami.Units.smallSpacing
+            bottomMargin: Kirigami.Units.smallSpacing
+        }
+        Item {
+            height: parent.height
+            width: (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0) && __appWindow.globalDrawer && __appWindow.globalDrawer.handleVisible && __appWindow.globalDrawer.handle && __appWindow.globalDrawer.handle.y === 0 ? __appWindow.globalDrawer.handle.width : 0
+        }
+    }
+
+    Flickable {
+        id: titleList
+        readonly property bool wideMode: pageRow.hasOwnProperty("wideMode") ? pageRow.wideMode : __appWindow.wideScreen
+        property int internalHeaderStyle: header.headerStyle === Kirigami.ApplicationHeaderStyle.Auto ? (titleList.wideMode ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.Breadcrumb) : header.headerStyle
+        // if scrolling the titlebar should scroll also the pages and vice versa
+        property bool scrollingLocked: (header.headerStyle === Kirigami.ApplicationHeaderStyle.Titles || titleList.wideMode)
+        //uses this to have less strings comparisons
+        property bool scrollMutex
+        property bool isTabBar: header.headerStyle === Kirigami.ApplicationHeaderStyle.TabBar
+
+        property Item backButton
+        property Item forwardButton
+        clip: true
+
+
+        boundsBehavior: Flickable.StopAtBounds
+        readonly property alias model: mainRepeater.model
+        contentWidth: contentItem.width
+        contentHeight: height
+
+        readonly property int currentIndex: pageRow && pageRow.currentIndex !== undefined ? pageRow.currentIndex : 0
+        readonly property int count: mainRepeater.count
+
+        function gotoIndex(idx) {
+            // don't actually scroll in widescreen mode
+            if (titleList.wideMode || contentItem.children.length < 2) {
+                return;
+            }
+            listScrollAnim.running = false
+            const pos = titleList.contentX;
+            let destPos;
+            titleList.contentX = Math.max(((contentItem.children[idx] || {x: 0}).x + (contentItem.children[idx] || {width: 0}).width) - titleList.width, Math.min(titleList.contentX, (contentItem.children[idx] || {x: 0}).x));
+            destPos = titleList.contentX;
+            listScrollAnim.from = pos;
+            listScrollAnim.to = destPos;
+            listScrollAnim.running = true;
+        }
+
+        NumberAnimation {
+            id: listScrollAnim
+            target: titleList
+            property: "contentX"
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutQuad
+        }
+        Timer {
+            id: contentXSyncTimer
+            interval: 0
+            onTriggered: {
+                titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX;
+            }
+        }
+        onCountChanged: contentXSyncTimer.restart();
+        onCurrentIndexChanged: gotoIndex(currentIndex);
+        onModelChanged: gotoIndex(currentIndex);
+        onContentWidthChanged: gotoIndex(currentIndex);
+
+        onContentXChanged: {
+            if (movingHorizontally && !titleList.scrollMutex && titleList.scrollingLocked && !pageRow.contentItem.moving) {
+                titleList.scrollMutex = true;
+                pageRow.contentItem.contentX = titleList.contentX - titleList.originX + pageRow.contentItem.originX;
+                titleList.scrollMutex = false;
+            }
+        }
+        onHeightChanged: {
+            titleList.returnToBounds()
+        }
+        onMovementEnded: {
+            if (titleList.scrollingLocked) {
+                //this will trigger snap as well
+                pageRow.contentItem.flick(0,0);
+            }
+        }
+        onFlickEnded: movementEnded();
+
+        NumberAnimation {
+            id: scrollTopAnimation
+            target: pageRow.currentItem && pageRow.currentItem.flickable ? pageRow.currentItem.flickable : null
+            property: "contentY"
+            to: 0
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.InOutQuad
+        }
+
+        Row {
+            id: contentItem
+            spacing: 0
+            Repeater {
+                id: mainRepeater
+                model: pageRow.depth
+                delegate: MouseArea {
+                    id: delegate
+                    readonly property int currentIndex: index
+                    readonly property var currentModelData: modelData
+                    clip: true
+
+                    width: {
+                        // more columns shown?
+                        if (titleList.scrollingLocked && delegateLoader.page) {
+                            return delegateLoader.page.width - (index === 0 ? navButtons.width : 0) - (index === pageRow.depth-1  ? stack.anchors.rightMargin : 0);
+                        } else {
+                            return Math.min(titleList.width, delegateLoader.implicitWidth + Kirigami.Units.smallSpacing);
+                        }
+                    }
+
+                    height: titleList.height
+                    onClicked: mouse => {
+                        if (pageRow.currentIndex === modelData) {
+                            // scroll up if current otherwise make current
+                            if (!pageRow.currentItem.flickable) {
+                                return;
+                            }
+                            if (pageRow.currentItem.flickable.contentY > -__appWindow.header.height) {
+                                scrollTopAnimation.to = -pageRow.currentItem.flickable.topMargin;
+                                scrollTopAnimation.running = true;
+                            }
+
+                        } else {
+                            pageRow.currentIndex = modelData;
+                        }
+                    }
+
+                    Loader {
+                        id: delegateLoader
+                        height: parent.height
+                        x: titleList.wideMode || headerStyle === Kirigami.ApplicationHeaderStyle.Titles ? (Math.min(delegate.width - implicitWidth, Math.max(0, titleList.contentX - delegate.x))) : 0
+                        width: parent.width - x
+
+                        Connections {
+                            target: delegateLoader.page.Component
+                            function onDestruction() {
+                                delegateLoader.sourceComponent = null;
+                            }
+                        }
+
+                        sourceComponent: header.pageDelegate
+
+                        readonly property Kirigami.Page page: pageRow.get(modelData)
+                        // NOTE: why not use ListViewCurrentIndex? because listview itself resets
+                        // currentIndex in some situations (since here we are using an int as a model,
+                        // even more often) so the property binding gets broken
+                        readonly property bool current: pageRow.currentIndex === index
+                        readonly property int index: parent.currentIndex
+                        readonly property var modelData: parent.currentModelData
+                    }
+                }
+            }
+        }
+        Connections {
+            target: titleList.scrollingLocked ? pageRow.contentItem : null
+            function onContentXChanged() {
+                if (!titleList.dragging && !titleList.movingHorizontally && !titleList.scrollMutex) {
+                    titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX;
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/templates/InlineMessage.qml b/src/controls/templates/InlineMessage.qml
new file mode 100644 (file)
index 0000000..a8bfae2
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
+ *  SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 2.0 as QQC2
+import QtQuick.Templates 2.0 as T2
+import org.kde.kirigami 2.20 as Kirigami
+import "private"
+
+/**
+ * @brief An inline message Item with support for informational, positive,
+ * warning and error types, and with support for associated actions.
+ *
+ * InlineMessage can be used to inform or interact with the user
+ * without requiring the use of a dialog.
+ *
+ * The InlineMessage is hidden by default. It also manages its
+ * height (and implicitHeight) during an animated reveal when shown.
+ * You should avoid setting height on an InlineMessage unless it is
+ * already visible.
+ *
+ * Optionally an icon can be set, defaulting to an icon appropriate
+ * to the message type otherwise.
+ *
+ * Optionally, actions can be added which are shown alongside an
+ * optional close button on the right side of the Item. If more
+ * actions are set than can fit, an overflow menu is provided.
+ *
+ * Example usage:
+ * @code{.qml}
+ * InlineMessage {
+ *     type: Kirigami.MessageType.Error
+ *
+ *     text: "My error message"
+ *
+ *     actions: [
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         },
+ *         Kirigami.Action {
+ *             icon.name: "edit"
+ *             text: "Action text"
+ *             onTriggered: {
+ *                 // do stuff
+ *             }
+ *         }
+ *     ]
+ * }
+ * @endcode
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/components-inlinemessages">Inline Messages in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/components/assistance/inline">KDE Human Interface Guidelines on Inline Messages</a>
+ * @since KDE Frameworks 5.45
+ * @inherit QtQuick.Controls.Control
+ */
+T2.Control {
+    id: root
+
+    visible: false
+
+    /**
+     * @brief This signal is emitted when a link is hovered in the message text.
+     * @param The hovered link.
+     */
+    signal linkHovered(string link)
+
+    /**
+     * @brief This signal is emitted when a link is clicked or tapped in the message text.
+     * @param The clicked or tapped link.
+     */
+    signal linkActivated(string link)
+
+    /**
+     * @brief This property holds the link embedded in the message text that the user is hovering over.
+     */
+    readonly property string hoveredLink: label.hoveredLink
+
+    /**
+     * @brief This property holds the message type.
+     *
+     * The following values are allowed:
+     * * ``Kirigami.MessageType.Information``
+     * * ``Kirigami.MessageType.Positive``
+     * * ``Kirigami.MessageType.Warning``
+     * * ``Kirigami.MessageType.Error``
+     *
+     * default: ``Kirigami.MessageType.Information``
+     *
+     * @property Kirigami.MessageType type
+     */
+    property int type: Kirigami.MessageType.Information
+
+    /**
+     * @brief This grouped property holds the description of an optional icon.
+     *
+     * If no custom icon is set, an icon appropriate to the message type
+     * is shown.
+     */
+    property IconPropertiesGroup icon: IconPropertiesGroup {}
+
+    /**
+     * @brief This property holds the message text.
+     */
+    property string text
+
+    /**
+     * @brief This property holds whether the close button is displayed.
+     *
+     * default: ``false``
+     */
+    property bool showCloseButton: false
+
+    /**
+     * This property holds the list of Kirigami Actions to show in the inline
+     * message's internal kirigami::ActionToolBar.
+     *
+     * Actions are added from left to right. If more actions
+     * are set than can fit, an overflow menu is provided.
+     */
+    property list<QtObject> actions
+
+    /**
+     * @brief This property holds whether the current message item is animating.
+     */
+    readonly property bool animating: _animating
+
+    /** @internal */
+    property bool _animating: false
+
+    implicitHeight: visible ? (contentLayout.implicitHeight + topPadding + bottomPadding) : 0
+
+    padding: Kirigami.Units.smallSpacing
+    // base style (such as qqc2-desktop-style) may define unique paddings for Control, reset it to uniform
+    topPadding: undefined
+    leftPadding: undefined
+    rightPadding: undefined
+    bottomPadding: undefined
+
+    Behavior on implicitHeight {
+        enabled: !root.visible
+
+        SequentialAnimation {
+            PropertyAction { targets: root; property: "_animating"; value: true }
+            NumberAnimation { duration: Kirigami.Units.longDuration }
+        }
+    }
+
+    onVisibleChanged: {
+        if (!visible) {
+            contentLayout.opacity = 0;
+        }
+    }
+
+    opacity: visible ? 1 : 0
+
+    Behavior on opacity {
+        enabled: !root.visible
+
+        NumberAnimation { duration: Kirigami.Units.shortDuration }
+    }
+
+    onOpacityChanged: {
+        if (opacity === 0) {
+            contentLayout.opacity = 0;
+        } else if (opacity === 1) {
+            contentLayout.opacity = 1;
+        }
+    }
+
+    onImplicitHeightChanged: {
+        height = implicitHeight;
+    }
+
+    contentItem: Item {
+        id: contentLayout
+
+        // Used to defer opacity animation until we know if InlineMessage was
+        // initialized visible.
+        property bool complete: false
+
+        Behavior on opacity {
+            enabled: root.visible && contentLayout.complete
+
+            SequentialAnimation {
+                NumberAnimation { duration: Kirigami.Units.shortDuration * 2 }
+                PropertyAction { targets: root; property: "_animating"; value: false }
+            }
+        }
+
+        implicitHeight: {
+            if (actionsLayout.atBottom) {
+                return label.implicitHeight + actionsLayout.height + Kirigami.Units.gridUnit
+            } else {
+                return Math.max(icon.implicitHeight, label.implicitHeight, closeButton.implicitHeight, actionsLayout.height)
+            }
+        }
+
+        readonly property real remainingWidth: width - (
+            icon.width
+            + labelArea.anchors.leftMargin + label.implicitWidth + labelArea.anchors.rightMargin
+            + (root.showCloseButton ? closeButton.width : 0)
+        )
+        readonly property bool multiline: remainingWidth <= 0 || actionsLayout.atBottom
+
+        Kirigami.Icon {
+            id: icon
+
+            width: Kirigami.Units.iconSizes.smallMedium
+            height: Kirigami.Units.iconSizes.smallMedium
+
+            anchors {
+                left: parent.left
+                top: actionsLayout.atBottom ? parent.top : undefined
+                verticalCenter: actionsLayout.atBottom ? undefined : parent.verticalCenter
+            }
+
+            source: {
+                if (root.icon.name) {
+                    return root.icon.name;
+                } else if (root.icon.source) {
+                    return root.icon.source;
+                }
+
+                if (root.type === Kirigami.MessageType.Positive) {
+                    return "dialog-positive";
+                } else if (root.type === Kirigami.MessageType.Warning) {
+                    return "dialog-warning";
+                } else if (root.type === Kirigami.MessageType.Error) {
+                    return "dialog-error";
+                }
+
+                return "dialog-information";
+            }
+
+            color: root.icon.color
+        }
+
+        MouseArea {
+            id: labelArea
+
+            anchors {
+                left: icon.right
+                leftMargin: Kirigami.Units.smallSpacing
+                right: root.showCloseButton ? closeButton.left : parent.right
+                rightMargin: root.showCloseButton ? Kirigami.Units.smallSpacing : 0
+                top: parent.top
+                bottom: contentLayout.multiline ? undefined : parent.bottom
+            }
+
+            acceptedButtons: Qt.NoButton
+            cursorShape: label.hoveredLink.length > 0 ? Qt.PointingHandCursor : undefined
+            propagateComposedEvents: true
+
+            implicitWidth: label.implicitWidth
+            height: contentLayout.multiline ? label.implicitHeight : implicitHeight
+
+            Kirigami.SelectableLabel {
+                id: label
+
+                width: parent.width
+                height: parent.height
+
+                color: Kirigami.Theme.textColor
+                wrapMode: Text.WordWrap
+
+                text: root.text
+
+                verticalAlignment: Text.AlignVCenter
+
+                onLinkHovered: link => root.linkHovered(link)
+                onLinkActivated: link => root.linkActivated(link)
+            }
+        }
+
+        Kirigami.ActionToolBar {
+            id: actionsLayout
+
+            flat: false
+            actions: root.actions
+            visible: root.actions.length > 0
+            alignment: Qt.AlignRight
+
+            readonly property bool atBottom: (root.actions.length > 0) && (label.lineCount > 1 || implicitWidth > contentLayout.remainingWidth)
+
+            anchors {
+                left: parent.left
+                top: atBottom ? labelArea.bottom : parent.top
+                topMargin: atBottom ? Kirigami.Units.gridUnit : 0
+                right: (!atBottom && root.showCloseButton) ? closeButton.left : parent.right
+                rightMargin: !atBottom && root.showCloseButton ? Kirigami.Units.smallSpacing : 0
+            }
+        }
+
+        QQC2.ToolButton {
+            id: closeButton
+
+            visible: root.showCloseButton
+
+            anchors {
+                right: parent.right
+                top: actionsLayout.atBottom ? parent.top : undefined
+                verticalCenter: actionsLayout.atBottom ? undefined : parent.verticalCenter
+            }
+
+            height: actionsLayout.atBottom ? implicitHeight : implicitHeight
+
+            icon.name: "dialog-close"
+
+            onClicked: root.visible = false
+        }
+
+        Component.onCompleted: complete = true
+    }
+}
diff --git a/src/controls/templates/OverlayDrawer.qml b/src/controls/templates/OverlayDrawer.qml
new file mode 100644 (file)
index 0000000..56a8302
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ *  SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Templates 2.15 as T2
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.11 as Kirigami
+import "private" as P
+
+/**
+ * @brief Overlay drawers are used to expose additional UI elements needed for
+ * small secondary tasks for which the main UI elements are not needed.
+ *
+ * Overlay drawers can be used to create two kinds of components, a modal drawer
+ * and an inline drawer. A modal drawer darkens the rest of the application and
+ * grabs focus until confirmed, whereas an inline drawer does not.
+ *
+ * Unlike an OverlaySheet that appears in the center of the application, an OverlayDrawer
+ * can be attached to an edge of the application, usually the top or the bottom edges.
+ *
+ * @see Visit https://develop.kde.org/docs/getting-started/kirigami/components-drawers to read more
+ * about modal and non-modal drawers.
+ *
+ * Example usage:
+ * @include overlaydrawer.qml
+ *
+ * @inherit QtQuick.Controls.Drawer
+ */
+T2.Drawer {
+    id: root
+
+    z: modal ? 0 : 1
+
+//BEGIN properties
+    /**
+     * @brief This property specifies whether the drawer is open and visible.
+     *
+     * default: ``false``
+     */
+    property bool drawerOpen: false
+
+    /**
+     * @brief This property specifies whether the drawer is in a state between open
+     * and closed.
+     *
+     * The drawer is visible but not completely open. This is usually the case when
+     * the user is dragging the drawer from a screen edge, so the user is "peeking"
+     * at what's in the drawer.
+     *
+     * default: ``false``
+     */
+    property bool peeking: false
+
+    /**
+     * @brief This property specifies whether the drawer is currently opening or closing itself.
+     */
+    readonly property bool animating : enterAnimation.animating || exitAnimation.animating || positionResetAnim.running
+
+    /**
+     * @brief This property holds whether the drawer can be collapsed to a
+     * very thin, usually icon only sidebar.
+     *
+     * Only modal drawers are collapsible. Collapsible is not supported in
+     * the mobile mode.
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property bool collapsible: false
+
+    /**
+     * @brief This property specifies whether the drawer is collapsed to a
+     * very thin sidebar, usually icon only.
+     *
+     * When true, the drawer will be collapsed to a very thin sidebar,
+     * usually icon only.
+     *
+     * default: ``false``
+     *
+     * @see collapsible
+     */
+    property bool collapsed: false
+
+    /**
+     * @brief This property holds the size of the collapsed drawer.
+     *
+     * For vertical drawers this will be the width of the drawer and for horizontal
+     * drawers this will be the height of the drawer.
+     *
+     * default: @link Kirigami.Units.iconSizes Kirigami.Units.iconSizes.medium @endlink,
+     * just enough to accommodate medium sized icons
+     */
+    property int collapsedSize: Kirigami.Units.iconSizes.medium
+
+    /**
+     * @brief This property holds the options for handle's open icon.
+     *
+     * This grouped property has the following sub-properties:
+     * * ``source: var``: A Freedesktop standard icon name. The icon is pulled from system's icon theme.
+     * * ``color: color``: An optional tint color for the icon.
+     *
+     * If no custom icon is set, a menu icon is shown for the
+     * @link ApplicationWindow::globalDrawer globalDrawer @endlink
+     * and an overflow menu icon is shown for the @link ApplicationWindow::contextDrawer contextDrawer @endlink.
+     * That's the default for the GlobalDrawer and ContextDrawer components respectively.
+     *
+     * For OverlayDrawer the default is view-right-close or view-left-close depending on
+     * the drawer location.
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    readonly property P.IconPropertiesGroup handleOpenIcon: P.IconPropertiesGroup {
+        source: root.edge === Qt.RightEdge ? "view-right-close" : "view-left-close"
+    }
+
+    /**
+     * @brief This property holds the options for the handle's close icon.
+     *
+     * This grouped property has the following sub-properties:
+     * * ``source: var``: A Freedesktop standard icon name. The icon is pulled from system's icon theme.
+     * * ``color: color``: An optional tint color for the icon.
+     *
+     * If no custom icon is set, an X icon is shown,
+     * which will morph into the Menu or overflow icons.
+     *
+     * For OverlayDrawer the default is view-right-new or view-left-new depending on
+     * the drawer location.
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    property P.IconPropertiesGroup handleClosedIcon: P.IconPropertiesGroup {
+        source: root.edge === Qt.RightEdge ? "view-right-new" : "view-left-new"
+    }
+
+    /**
+     * @brief This property holds the tooltip displayed when the drawer is open.
+     * @since org.kde.kirigami 2.15
+     */
+    property string handleOpenToolTip: qsTr("Close drawer")
+
+    /**
+     * @brief This property holds the tooltip displayed when the drawer is closed.
+     * @since org.kde.kirigami 2.15
+     */
+    property string handleClosedToolTip: qsTr("Open drawer")
+
+    /**
+     * @brief This property holds whether the handle is visible, to make opening the
+     * drawer easier.
+     *
+     * Currently supported only on left and right drawers.
+     */
+    property bool handleVisible: typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true
+
+    /**
+     * @brief Readonly property that points to the item that will act as a physical
+     * handle for the drawer.
+     * @property MouseArea handle
+     **/
+    readonly property Item handle: MouseArea {
+        id: drawerHandle
+
+        /*
+         * This property is used to set when the tooltip is visible.
+         * It exists because the text is changed while the tooltip is still visible.
+         */
+        property bool displayToolTip: true
+
+        z: root.modal ? applicationWindow().overlay.z + (root.position > 0 ? +1 : -1) : root.background.parent.z + 1
+        preventStealing: true
+        hoverEnabled: handleAnchor && handleAnchor.visible
+        parent: applicationWindow().overlay.parent
+
+        QQC2.ToolButton {
+            anchors.centerIn: parent
+            width: parent.height - Kirigami.Units.smallSpacing * 1.5
+            height: parent.height - Kirigami.Units.smallSpacing * 1.5
+            onClicked: {
+                drawerHandle.displayToolTip = false
+                Qt.callLater(() => root.drawerOpen = !root.drawerOpen)
+            }
+            Accessible.name: root.drawerOpen ? root.handleOpenToolTip : root.handleClosedToolTip
+            visible: !Kirigami.SettingstabletMode && !Kirigami.SettingshasTransientTouchInput
+        }
+        T2.ToolTip.visible: drawerHandle.displayToolTip && containsMouse
+        T2.ToolTip.text: root.drawerOpen ? handleOpenToolTip : handleClosedToolTip
+        T2.ToolTip.delay: Kirigami.Units.toolTipDelay
+
+        property Item handleAnchor: (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar)
+                ? ((root.edge === Qt.LeftEdge && Qt.application.layoutDirection === Qt.LeftToRight)
+                   || (root.edge === Qt.RightEdge && Qt.application.layoutDirection === Qt.RightToLeft)
+                   ? applicationWindow().pageStack.globalToolBar.leftHandleAnchor
+                   : applicationWindow().pageStack.globalToolBar.rightHandleAnchor)
+                : (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1 ? applicationWindow().header : null)
+
+        property int startX
+        property int mappedStartX
+
+        enabled: root.handleVisible
+
+        onPressed: mouse => {
+            root.peeking = true;
+            startX = mouse.x;
+            mappedStartX = mapToItem(parent, startX, 0).x
+        }
+        onPositionChanged: mouse => {
+            if (!pressed) {
+                return;
+            }
+            const pos = mapToItem(parent, mouse.x - startX, mouse.y);
+            switch(root.edge) {
+            case Qt.LeftEdge:
+                root.position = pos.x/root.contentItem.width;
+                break;
+            case Qt.RightEdge:
+                root.position = (root.parent.width - pos.x - width)/root.contentItem.width;
+                break;
+            default:
+            }
+        }
+        onReleased: mouse => {
+            root.peeking = false;
+            if (Math.abs(mapToItem(parent, mouse.x, 0).x - mappedStartX) < Qt.styleHints.startDragDistance) {
+                if (!root.drawerOpen) {
+                    root.close();
+                }
+                root.drawerOpen = !root.drawerOpen;
+            }
+        }
+        onCanceled: {
+            root.peeking = false
+        }
+        x: {
+            switch(root.edge) {
+            case Qt.LeftEdge:
+                return root.background.width * root.position + Kirigami.Units.smallSpacing;
+            case Qt.RightEdge:
+                return drawerHandle.parent.width - (root.background.width * root.position) - width - Kirigami.Units.smallSpacing;
+            default:
+                return 0;
+            }
+        }
+        y: handleAnchor && anchors.bottom ? handleAnchor.Kirigami.ScenePosition.y : 0
+
+        anchors {
+            bottom: drawerHandle.handleAnchor && drawerHandle.handleAnchor.visible ? undefined : parent.bottom
+            bottomMargin: {
+                if (typeof applicationWindow === "undefined") {
+                    return;
+                }
+
+                let margin = Kirigami.Units.smallSpacing;
+                if (applicationWindow().footer) {
+                    margin = applicationWindow().footer.height + Kirigami.Units.smallSpacing;
+                }
+
+                if(root.parent && root.height < root.parent.height) {
+                    margin = root.parent.height - root.height - root.y + Kirigami.Units.smallSpacing;
+                }
+
+                if (!applicationWindow() || !applicationWindow().pageStack ||
+                    !applicationWindow().pageStack.contentItem ||
+                    !applicationWindow().pageStack.contentItem.itemAt) {
+                    return margin;
+                }
+
+                let item;
+                if (applicationWindow().pageStack.layers.depth > 1) {
+                    item = applicationWindow().pageStack.layers.currentItem;
+                } else {
+                    item = applicationWindow().pageStack.contentItem.itemAt(applicationWindow().pageStack.contentItem.contentX + drawerHandle.x, 0);
+                }
+
+                // try to take the last item
+                if (!item) {
+                    item = applicationWindow().pageStack.lastItem;
+                }
+
+                let pageFooter = item && item.page ? item.page.footer : (item ? item.footer : undefined);
+                if (pageFooter && root.parent) {
+                    margin = root.height < root.parent.height ? margin : margin + pageFooter.height
+                }
+
+                return margin;
+            }
+            Behavior on bottomMargin {
+                NumberAnimation {
+                    duration: Kirigami.Units.shortDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+
+        visible: root.enabled && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) && opacity > 0
+        width: handleAnchor && handleAnchor.visible ? handleAnchor.width : Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing*2
+        height: handleAnchor && handleAnchor.visible ? handleAnchor.height : width
+        opacity: root.handleVisible ? 1 : 0
+        Behavior on opacity {
+            NumberAnimation {
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+        transform: Translate {
+            id: translateTransform
+            x: root.handleVisible ? 0 : (root.edge === Qt.LeftEdge ? -drawerHandle.width : drawerHandle.width)
+            Behavior on x {
+                NumberAnimation {
+                    duration: Kirigami.Units.longDuration
+                    easing.type: !root.handleVisible ? Easing.OutQuad : Easing.InQuad
+                }
+            }
+        }
+    }
+//END properties
+
+    interactive: modal
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: modal ? Kirigami.Theme.View : Kirigami.Theme.Window
+    Kirigami.Theme.onColorSetChanged: {
+        contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
+        background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
+    }
+
+//BEGIN reassign properties
+    //default paddings
+    leftPadding: Kirigami.Units.smallSpacing
+    topPadding: Kirigami.Units.smallSpacing
+    rightPadding: Kirigami.Units.smallSpacing
+    bottomPadding: Kirigami.Units.smallSpacing
+
+    y: modal ? 0 : ((T2.ApplicationWindow.menuBar ? T2.ApplicationWindow.menuBar.height : 0) + (T2.ApplicationWindow.header ? T2.ApplicationWindow.header.height : 0))
+
+    height: parent && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) ? (modal ? parent.height : (parent.height - y - (T2.ApplicationWindow.footer ? T2.ApplicationWindow.footer.height : 0))) : implicitHeight
+
+    parent: modal || edge === Qt.LeftEdge || edge === Qt.RightEdge ? T2.ApplicationWindow.overlay : T2.ApplicationWindow.contentItem
+
+    edge: Qt.LeftEdge
+    modal: true
+
+    dragMargin: enabled && (edge === Qt.LeftEdge || edge === Qt.RightEdge) ? Math.min(Kirigami.Units.gridUnit, Qt.styleHints.startDragDistance) : 0
+
+    contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
+    contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
+
+    implicitWidth: contentWidth + leftPadding + rightPadding
+    implicitHeight: contentHeight + topPadding + bottomPadding
+
+    enter: Transition {
+        SequentialAnimation {
+            id: enterAnimation
+            /* NOTE: why this? the running status of the enter transition is not relaible and
+             * the SmoothedAnimation is always marked as non running,
+             * so the only way to get to a reliable animating status is with this
+             */
+            property bool animating
+            ScriptAction {
+                script: {
+                    enterAnimation.animating = true
+                    // on non modal dialog we don't want drawers in the overlay
+                    if (!root.modal) {
+                        root.background.parent.parent = applicationWindow().overlay.parent
+                    }
+                }
+            }
+            SmoothedAnimation {
+                velocity: 5
+            }
+            ScriptAction {
+                script: enterAnimation.animating = false
+            }
+        }
+    }
+
+    exit: Transition {
+        SequentialAnimation {
+            id: exitAnimation
+            property bool animating
+            ScriptAction {
+                script: exitAnimation.animating = true
+            }
+            SmoothedAnimation {
+                velocity: 5
+            }
+            ScriptAction {
+                script: exitAnimation.animating = false
+            }
+        }
+    }
+//END reassign properties
+
+
+//BEGIN signal handlers
+    onCollapsedChanged: {
+        if (Kirigami.Settings.isMobile) {
+            collapsed = false;
+        }
+        if (!__internal.completed) {
+            return;
+        }
+        if ((!collapsible || modal) && collapsed) {
+            collapsed = true;
+        }
+    }
+    onCollapsibleChanged: {
+        if (Kirigami.Settings.isMobile) {
+            collapsible = false;
+        }
+        if (!__internal.completed) {
+            return;
+        }
+        if (!collapsible) {
+            collapsed = false;
+        } else if (modal) {
+            collapsible = false;
+        }
+    }
+    onModalChanged: {
+        if (!__internal.completed) {
+            return;
+        }
+        if (modal) {
+            collapsible = false;
+        }
+    }
+
+    onPositionChanged: {
+        if (peeking) {
+            visible = true
+        }
+    }
+    onVisibleChanged: {
+        if (peeking) {
+            visible = true
+        } else {
+            drawerOpen = visible;
+        }
+    }
+    onPeekingChanged:  {
+        if (peeking) {
+            root.enter.enabled = false;
+            root.exit.enabled = false;
+        } else {
+            drawerOpen = position > 0.5 ? 1 : 0;
+            positionResetAnim.running = true
+            root.enter.enabled = true;
+            root.exit.enabled = true;
+        }
+    }
+    onDrawerOpenChanged: {
+        // sync this property only when the component is properly loaded
+        if (!__internal.completed) {
+            return;
+        }
+        positionResetAnim.running = false;
+        if (drawerOpen) {
+            open();
+        } else {
+            close();
+        }
+        Qt.callLater(() => drawerHandle.displayToolTip = true)
+    }
+
+    Component.onCompleted: {
+        // if defined as drawerOpen by default in QML, don't animate
+        if (root.drawerOpen) {
+            root.enter.enabled = false;
+            root.visible = true;
+            root.position = 1;
+            root.enter.enabled = true;
+        }
+        __internal.completed = true;
+        contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
+        background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
+    }
+//END signal handlers
+
+    // this is as hidden as it can get here
+    /** @internal */
+    property QtObject __internal: QtObject {
+        //here in order to not be accessible from outside
+        property bool completed: false
+        property SequentialAnimation positionResetAnim: SequentialAnimation {
+            id: positionResetAnim
+            property alias to: internalAnim.to
+            NumberAnimation {
+                id: internalAnim
+                target: root
+                to: drawerOpen ? 1 : 0
+                property: "position"
+                duration: (root.position)*Kirigami.Units.longDuration
+            }
+            ScriptAction {
+                script: {
+                    root.drawerOpen = internalAnim.to !== 0;
+                }
+            }
+        }
+        readonly property Item statesItem: Item {
+            states: [
+                State {
+                    when: root.collapsed
+                    PropertyChanges {
+                        target: root
+                        implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(collapsedSize + leftPadding + rightPadding, Math.round(applicationWindow().width*0.8))
+
+                        implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(collapsedSize + topPadding + bottomPadding, Math.round(applicationWindow().height*0.8))
+                    }
+                },
+                State {
+                    when: !root.collapsed
+                    PropertyChanges {
+                        target: root
+                        implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8))
+
+                        implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(contentHeight + topPadding + bottomPadding, Math.round(applicationWindow().height*0.4))
+
+                        contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
+                        contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
+                    }
+                }
+            ]
+            transitions: Transition {
+                reversible: true
+                NumberAnimation {
+                    properties: root.edge === Qt.TopEdge || root.edge === Qt.BottomEdge ? "implicitHeight" : "implicitWidth"
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutQuad
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/templates/OverlaySheet.qml b/src/controls/templates/OverlaySheet.qml
new file mode 100644 (file)
index 0000000..2a8f3cf
--- /dev/null
@@ -0,0 +1,826 @@
+/*
+ *  SPDX-FileCopyrightText: 2016-2020 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.14 as Kirigami
+import QtQuick.Templates 2.0 as T2
+import "private" as P
+import "../private" as PP
+
+/**
+ * @brief An overlay sheet that covers the current Page content.
+ *
+ * Its contents can be scrolled up or down, scrolling all the way up or
+ * all the way down, dismisses it.
+ * Use this for big, modal dialogs or information display, that can't be
+ * logically done as a new separate Page, even if potentially
+ * are taller than the screen space.
+ *
+ * @since org.kde.kirigami 2.0
+ * @inherit QtQuick.QtObject
+ */
+QtObject {
+    id: root
+
+    Kirigami.Theme.colorSet: Kirigami.Theme.View
+    Kirigami.Theme.inherit: false
+
+    /**
+     * @brief This property holds the visual content item.
+     * @note The content item is automatically resized to fill the
+     * sheet's view area.
+     *
+     * Conversely, the Sheet will be sized based on the size hints
+     * of the contentItem, so if you need a custom size sheet,
+     * redefine contentWidth and contentHeight of your contentItem
+     */
+    default property Item contentItem
+
+    /**
+     * @brief This property specifies whether the sheet is open and displaying its contents.
+     */
+    property bool sheetOpen
+
+    /**
+     * @brief This property holds the left padding.
+     *
+     * default: ``Kirigami.Units.largeSpacing``
+     */
+    property int leftPadding: Kirigami.Units.largeSpacing
+
+    /**
+     * @brief This property holds the top padding.
+     *
+     * default: ``Kirigami.Units.largeSpacing``
+     */
+    property int topPadding: Kirigami.Units.largeSpacing
+
+    /**
+     * @brief This property holds the right padding.
+     *
+     * default: ``Kirigami.Units.largeSpacing``
+     */
+    property int rightPadding: Kirigami.Units.largeSpacing
+
+    /**
+     * @brief This property holds the bottom padding.
+     *
+     * default: ``Kirigami.Units.largeSpacing``
+     */
+    property int bottomPadding: Kirigami.Units.largeSpacing
+
+    /**
+     * @brief This property holds the left inset for the background.
+     *
+     * The inset gets applied to both the content and the background.
+     *
+     * default: ``0``
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property real leftInset: 0
+
+    /**
+     * @brief This property holds the top inset for the background.
+     *
+     * The inset gets applied to both the content and the background.
+     *
+     * default: ``0``
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property real topInset: 0
+
+    /**
+     * @brief This property holds the right inset for the background.
+     *
+     * The inset gets applied to both the content and the background.
+     *
+     * default: ``0``
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property real rightInset: 0
+
+    /**
+     * @brief This property holds the bottom inset for the background.
+     *
+     * The inset gets applied to both the content and the background.
+     *
+     * default: ``0``
+     *
+     * @since org.kde.kirigami 2.12
+     */
+    property real bottomInset: 0
+
+    /**
+     * @brief This property holds an optional item which will be used as the sheet's header,
+     * and will always be displayed.
+     * @since KDE Frameworks 5.43
+     */
+    property Item header: Kirigami.Heading {
+        level: 2
+        text: root.title
+        elide: Text.ElideRight
+
+        // use tooltip for long text that is elided
+        QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
+        QQC2.ToolTip.text: root.title
+        HoverHandler {
+            id: titleHoverHandler
+        }
+    }
+
+    /**
+     * @brief An optional item which will be used as the sheet's footer,
+     * always kept on screen.
+     * @since KDE Frameworks 5.43
+     */
+    property Item footer
+
+    /**
+     * @brief This property holds the background item.
+     *
+     * @note If the background item has no explicit size specified,
+     * it automatically follows the control's size. In most cases,
+     * there is no need to specify width or height for a background item.
+     */
+    property Item background
+
+    /**
+     * @brief This property sets the visibility of the close button in the top-right corner.
+     *
+     * default: `Only shown in desktop mode`
+     *
+     * @since KDE Frameworks 5.44
+     */
+    property bool showCloseButton: !Kirigami.Settings.isMobile
+
+    /**
+     * @brief This property holds the sheet's title.
+     * @note If the header property is set, this will have no effect as the heading will be replaced by the header.
+     * @since KDE Frameworks 5.84
+     */
+    property string title
+
+    property Item parent
+
+    /**
+     * @brief This function opens the overlay sheet.
+     */
+    function open() {
+        openAnimation.running = true;
+        root.sheetOpen = true;
+        contentLayout.initialHeight = contentLayout.height
+        mainItem.visible = true;
+        mainItem.forceActiveFocus();
+    }
+
+    /**
+     * @brief This function closes the overlay sheet.
+     */
+    function close() {
+        if (root.sheetOpen) {
+            root.sheetOpen = false;
+        }
+    }
+
+    onBackgroundChanged: {
+        background.parent = contentLayout.parent;
+        background.anchors.fill = contentLayout;
+        background.anchors.margins = -1
+        background.z = -1;
+    }
+    onContentItemChanged: {
+        if (contentItem instanceof Flickable) {
+            scrollView.flickableItem = contentItem;
+            contentItem.parent = scrollView;
+            scrollView.contentItem = contentItem;
+            scrollView.viewContent = contentItem.contentItem;
+        } else {
+            contentItem.parent = contentItemParent;
+            flickableContents.parent = scrollView.flickableItem.contentItem;
+            flickableContents.anchors.top = scrollView.flickableItem.contentItem.top;
+            flickableContents.anchors.left = scrollView.flickableItem.contentItem.left;
+            flickableContents.anchors.right = scrollView.flickableItem.contentItem.right;
+            scrollView.viewContent = flickableContents;
+            contentItem.anchors.left = contentItemParent.left;
+            contentItem.anchors.right = contentItemParent.right;
+        }
+        scrollView.flickableItem.interactive = false;
+        scrollView.flickableItem.flickableDirection = Flickable.VerticalFlick;
+    }
+    onSheetOpenChanged: {
+        if (sheetOpen) {
+            open();
+        } else {
+            closeAnimation.restart()
+            Qt.inputMethod.hide();
+            root.parent.forceActiveFocus();
+        }
+    }
+    onHeaderChanged: headerItem.initHeader()
+    onFooterChanged: {
+        footer.parent = footerParent;
+        footer.anchors.fill = footerParent;
+    }
+
+    Component.onCompleted: {
+        // ScrollablePage must do things related to parenting of OverlaySheets in its conCompleted, so this must execute later
+        // TODO KF6: port the root object to Popup template?
+        Qt.callLater(() => {
+            if (!root.parent && typeof applicationWindow !== "undefined") {
+                root.parent = applicationWindow().overlay
+            }
+            headerItem.initHeader();
+        });
+    }
+
+    readonly property Item rootItem: FocusScope {
+        id: mainItem
+        Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
+        Kirigami.Theme.inherit: root.Kirigami.Theme.inherit
+        z: 101
+        // we want to be over any possible OverlayDrawers, including handles
+        parent: {
+            if (root.parent && root.parent.Kirigami.ColumnView.view && (root.parent.Kirigami.ColumnView.view === root.parent || root.parent.Kirigami.ColumnView.view === root.parent.parent)) {
+                return root.parent.Kirigami.ColumnView.view.parent;
+            } else if (root.parent && root.parent.overlay) {
+                return root.parent.overlay;
+            } else {
+                return root.parent;
+            }
+        }
+
+        anchors.fill: parent
+
+        visible: false
+        clip: true
+
+        // differentiate between mouse and touch
+        HoverHandler {
+            id: mouseHover
+            acceptedDevices: PointerDevice.Mouse
+        }
+
+        Keys.onEscapePressed: event => {
+            if (root.sheetOpen) {
+                root.close();
+            } else {
+                event.accepted = false;
+            }
+        }
+
+        readonly property int contentItemPreferredWidth: root.contentItem.Layout.preferredWidth > 0 ? root.contentItem.Layout.preferredWidth : root.contentItem.implicitWidth
+
+        readonly property int absoluteContentItemMaximumWidth: width <= 0 ? contentItemPreferredWidth : Math.round(width - Kirigami.Units.largeSpacing * 2)
+        readonly property int contentItemMaximumWidth: root.contentItem.Layout.maximumWidth > 0 ? Math.min(root.contentItem.Layout.maximumWidth, absoluteContentItemMaximumWidth) : width > Kirigami.Units.gridUnit * 30 ? width * 0.95 : absoluteContentItemMaximumWidth
+
+        onHeightChanged: {
+            const focusItem = Window.activeFocusItem;
+
+            if (!focusItem) {
+                return;
+            }
+
+            // NOTE: there is no function to know if an item is descended from another,
+            // so we have to walk the parent hierarchy by hand
+            let isDescendent = false;
+            let candidate = focusItem.parent;
+            while (candidate) {
+                if (candidate === root) {
+                    isDescendent = true;
+                    break;
+                }
+                candidate = candidate.parent;
+            }
+            if (!isDescendent) {
+                return;
+            }
+
+            let cursorY = 0;
+            if (focusItem.cursorPosition !== undefined) {
+                cursorY = focusItem.positionToRectangle(focusItem.cursorPosition).y;
+            }
+
+
+            const pos = focusItem.mapToItem(flickableContents, 0, cursorY - Units.gridUnit*3);
+            // focused item already visible? add some margin for the space of the action buttons
+            if (pos.y >= scrollView.flickableItem.contentY && pos.y <= scrollView.flickableItem.contentY + scrollView.flickableItem.height - Kirigami.Units.gridUnit * 8) {
+                return;
+            }
+            scrollView.flickableItem.contentY = pos.y;
+        }
+
+        ParallelAnimation {
+            id: openAnimation
+            property int margins: Kirigami.Units.gridUnit * 5
+            NumberAnimation {
+                target: outerFlickable
+                properties: "contentY"
+                from: -outerFlickable.height
+                to: outerFlickable.openPosition
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.OutQuad
+            }
+            OpacityAnimator {
+                target: mainItem
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InQuad
+            }
+        }
+
+        NumberAnimation {
+            id: resetAnimation
+            target: outerFlickable
+            properties: "contentY"
+            from: outerFlickable.contentY
+            to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 || scrollView.flickableItem.contentHeight < outerFlickable.height
+                ? outerFlickable.openPosition
+                : outerFlickable.contentHeight - outerFlickable.height + outerFlickable.topEmptyArea + headerItem.height + footerItem.height
+            duration: Kirigami.Units.longDuration
+            easing.type: Easing.OutQuad
+        }
+
+        SequentialAnimation {
+            id: closeAnimation
+            ParallelAnimation {
+                NumberAnimation {
+                    target: outerFlickable
+                    properties: "contentY"
+                    from: outerFlickable.contentY + (contentLayout.initialHeight - contentLayout.height)
+                    to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 ? -mainItem.height : outerFlickable.contentHeight
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InQuad
+                }
+                OpacityAnimator {
+                    target: mainItem
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InQuad
+                }
+            }
+            ScriptAction {
+                script: {
+                    contentLayout.initialHeight = 0
+                    scrollView.flickableItem.contentY = -mainItem.height;
+                    mainItem.visible = false;
+                }
+            }
+        }
+        Rectangle {
+            anchors.fill: parent
+            color: "black"
+            opacity: 0.3 * Math.min(
+                (Math.min(outerFlickable.contentY + outerFlickable.height, outerFlickable.height) / outerFlickable.height),
+                (2 + (outerFlickable.contentHeight - outerFlickable.contentY - outerFlickable.topMargin - outerFlickable.bottomMargin)/outerFlickable.height))
+        }
+
+        MouseArea {
+            anchors.fill: parent
+            drag.filterChildren: true
+            hoverEnabled: true
+
+            onPressed: mouse => {
+                const pos = mapToItem(contentLayout, mouse.x, mouse.y);
+                if (contentLayout.contains(pos) && mouseHover.hovered) { // only on mouse event, not touch
+                    // disable dragging the sheet with a mouse
+                    outerFlickable.interactive = false
+                }
+            }
+            onReleased: mouse => {
+                const pos = mapToItem(contentLayout, mouse.x, mouse.y);
+                if (!contentLayout.contains(pos)) {
+                    root.close();
+                }
+                // enable dragging of sheet once mouse is not clicked
+                outerFlickable.interactive = true
+            }
+
+
+            Item {
+                id: flickableContents
+
+                readonly property real listHeaderHeight: scrollView.flickableItem ? -scrollView.flickableItem.originY : 0
+
+                y: (scrollView.contentItem !== flickableContents ? -scrollView.flickableItem.contentY - listHeaderHeight  - (headerItem.visible ? headerItem.height : 0): 0)
+
+                width: mainItem.contentItemPreferredWidth <= 0 ? mainItem.width : (mainItem.contentItemMaximumWidth > 0 ? Math.min( mainItem.contentItemMaximumWidth, Math.max( mainItem.width/2, mainItem.contentItemPreferredWidth ) ) : Math.max( mainItem.width / 2, mainItem.contentItemPreferredWidth ) ) + leftPadding + rightPadding
+
+
+                implicitHeight: scrollView.viewContent === flickableContents ? root.contentItem.height + topPadding + bottomPadding : 0
+
+                Connections {
+                    target: enabled ? flickableContents.Window.activeFocusItem : null
+                    enabled: flickableContents.focus && flickableContents.Window.activeFocusItem && flickableContents.Window.activeFocusItem.hasOwnProperty("text")
+                    function onTextChanged() {
+                        if (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height > mainItem.Window.height) {
+                            scrollView.flickableItem.contentY += (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height) - mainItem.Window.height
+                        }
+                    }
+                }
+
+                Item {
+                    id: contentItemParent
+                    anchors {
+                        fill: parent
+                        leftMargin: leftPadding
+                        topMargin: topPadding
+                        rightMargin: rightPadding
+                        bottomMargin: bottomPadding
+                    }
+                }
+            }
+
+            Connections {
+                target: scrollView.flickableItem
+                property real oldContentHeight: 0
+                function onContentHeightChanged() {
+                    if (openAnimation.running) {
+                        openAnimation.running = false;
+                        open();
+                    } else {
+                        // repositioning is relevant only when the content height is less than the viewport height.
+                        // In that case the sheet looks like a dialog and should be centered. there is also a corner case when now is bigger then the viewport but prior to the
+                        // resize event it was smaller, also in this case we need repositioning
+                        if (scrollView.animatedContentHeight < outerFlickable.height
+                            || scrollView.flickableItem.oldContentHeight < outerFlickable.height
+                        ) {
+                            outerFlickable.adjustPosition();
+                        }
+                        oldContentHeight = scrollView.animatedContentHeight
+                    }
+                }
+            }
+
+            Flickable {
+                id: outerFlickable
+                anchors.fill: parent
+                contentWidth: width
+                topMargin: height
+                bottomMargin: height
+                // +1: we need the flickable to be always interactive
+                contentHeight: Math.max(height+1, scrollView.animatedContentHeight + topEmptyArea)
+
+                // readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Kirigami.Units.gridUnit * 3)
+                readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Kirigami.Units.gridUnit * 3)
+
+                readonly property real openPosition: Math.max(0, outerFlickable.height - outerFlickable.contentHeight + headerItem.height + footerItem.height) + height/2 - contentLayout.height/2;
+
+                onOpenPositionChanged: {
+                    if (openAnimation.running) {
+                        openAnimation.running = false;
+                        root.open();
+                    } else if (root.sheetOpen) {
+                        adjustPosition();
+                    }
+                }
+
+                property real oldContentY: NaN
+                property real oldContentHeight: 0
+                property bool lastMovementWasDown: false
+                property real startDraggingPos
+                property bool layoutMovingGuard: false
+                Kirigami.WheelHandler {
+                    target: outerFlickable
+                    scrollFlickableTarget: false
+                }
+
+                function adjustPosition() {
+                    if(layoutMovingGuard) return;
+
+                    if (openAnimation.running) {
+                        openAnimation.running = false;
+                        open()
+                    } else {
+                        resetAnimation.running = false;
+                        contentY = openPosition;
+                    }
+                }
+
+                // disable dragging the sheet with a mouse on header bar
+                MouseArea {
+                    anchors.fill: parent
+                    onPressed: mouse => {
+                        if (mouseHover.hovered) { // only on mouse event, not touch
+                            outerFlickable.interactive = false
+                        }
+                    }
+                    onReleased: mouse => {
+                        outerFlickable.interactive = true
+                    }
+                }
+
+                onContentYChanged: {
+                    if (scrollView.userInteracting) {
+                        return;
+                    }
+
+                    const startPos = -scrollView.flickableItem.topMargin - flickableContents.listHeaderHeight;
+                    const pos = contentY - topEmptyArea - flickableContents.listHeaderHeight;
+                    const endPos = scrollView.animatedContentHeight - scrollView.flickableItem.height + scrollView.flickableItem.bottomMargin - flickableContents.listHeaderHeight;
+
+                    layoutMovingGuard = true;
+                    if (endPos - pos > 0) {
+                        contentLayout.y = Math.round(Math.max(root.topInset, scrollView.flickableItem.topMargin - pos - flickableContents.listHeaderHeight));
+                    } else if (scrollView.flickableItem.topMargin - pos < 0) {
+                        contentLayout.y = Math.round(endPos - pos + root.topInset);
+                    }
+                    layoutMovingGuard = false;
+
+                    scrollView.flickableItem.contentY = Math.max(
+                        startPos, Math.min(pos, endPos));
+
+                    lastMovementWasDown = contentY < oldContentY;
+                    oldContentY = contentY;
+                }
+
+                onFlickEnded: {
+                    if (openAnimation.running || closeAnimation.running) {
+                        return;
+                    }
+                    if (scrollView.flickableItem.atYBeginning ||scrollView.flickableItem.atYEnd) {
+                        resetAnimation.restart();
+                    }
+                }
+
+                onDraggingChanged: {
+                    if (dragging) {
+                        startDraggingPos = contentY;
+                        return;
+                    }
+
+                    let shouldClose = false;
+
+                    // close
+                    if (scrollView.flickableItem.atYBeginning) {
+                        if (startDraggingPos - contentY > Kirigami.Units.gridUnit * 4 &&
+                            contentY < -Kirigami.Units.gridUnit * 4 &&
+                            lastMovementWasDown) {
+                            shouldClose = true;
+                        }
+                    }
+
+                    if (scrollView.flickableItem.atYEnd) {
+                        if (contentY - startDraggingPos > Kirigami.Units.gridUnit * 4 &&
+                            contentY > contentHeight - height + Kirigami.Units.gridUnit * 4  &&
+                            !lastMovementWasDown) {
+                            shouldClose = true;
+                        }
+                    }
+
+                    if (shouldClose) {
+                        root.sheetOpen = false
+                    } else if (scrollView.flickableItem.atYBeginning || scrollView.flickableItem.atYEnd) {
+                        resetAnimation.restart();
+                    }
+                }
+
+                onHeightChanged: {
+                    adjustPosition();
+                }
+
+                onContentHeightChanged: {
+                    // repositioning is relevant only when the content height is less than the viewport height.
+                    // In that case the sheet looks like a dialog and should be centered. there is also a corner case when now is bigger then the viewport but prior to the
+                    // resize event it was smaller, also in this case we need repositioning
+                    if (contentHeight < height || oldContentHeight < height) {
+                        adjustPosition();
+                    }
+                    oldContentHeight = contentHeight;
+                }
+
+                ColumnLayout {
+                    id: contentLayout
+                    spacing: 0
+                    // Its events should be filtered but not scrolled
+                    parent: outerFlickable
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    width: mainItem.contentItemPreferredWidth <= 0 ? mainItem.width : (mainItem.contentItemMaximumWidth > 0 ? Math.min( mainItem.contentItemMaximumWidth, Math.max( mainItem.width/2, mainItem.contentItemPreferredWidth ) ) : Math.max( mainItem.width / 2, mainItem.contentItemPreferredWidth ) ) - root.leftInset - root.rightInset + root.leftPadding + root.rightPadding
+                    height: Math.min(implicitHeight, parent.height) - root.topInset - root.bottomInset
+                    property real initialHeight
+
+                    Behavior on height {
+                        NumberAnimation {
+                            duration: Kirigami.Units.shortDuration
+                            easing.type: Easing.InOutCubic
+                        }
+                    }
+
+                    // Even though we're not actually using any shadows here,
+                    // we're using a ShadowedRectangle instead of a regular
+                    // rectangle because it allows fine-grained control over which
+                    // corners to round, which we need here
+                    Kirigami.ShadowedRectangle {
+                        id: headerItem
+                        Layout.fillWidth: true
+                        Layout.alignment: Qt.AlignTop
+                        //Layout.margins: 1
+                        visible: root.header || root.showCloseButton
+                        implicitHeight: Math.max(headerParent.implicitHeight, closeIcon.height) + Kirigami.Units.smallSpacing * 2
+                        z: 2
+                        corners.topLeftRadius: Kirigami.Units.smallSpacing
+                        corners.topRightRadius: Kirigami.Units.smallSpacing
+                        Kirigami.Theme.colorSet: Kirigami.Theme.Header
+                        Kirigami.Theme.inherit: false
+                        color: Kirigami.Theme.backgroundColor
+
+                        function initHeader() {
+                            if (header) {
+                                header.parent = headerParent;
+                                header.anchors.fill = headerParent;
+
+                                // TODO: special case for actual ListViews
+                            }
+                        }
+
+                        Item {
+                            id: headerParent
+                            implicitHeight: header ? header.implicitHeight : 0
+                            anchors {
+                                fill: parent
+                                leftMargin: Kirigami.Units.largeSpacing
+                                margins: Kirigami.Units.smallSpacing
+                                rightMargin: (root.showCloseButton ? closeIcon.width : 0) + Kirigami.Units.smallSpacing
+                            }
+                        }
+                        Kirigami.Icon {
+                            id: closeIcon
+
+                            readonly property bool tallHeader: headerItem.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
+
+                            anchors {
+                                right: parent.right
+                                rightMargin: Kirigami.Units.largeSpacing
+                                verticalCenter: headerItem.verticalCenter
+                                margins: Kirigami.Units.smallSpacing
+                            }
+
+                            // Apply the changes to the anchors imperatively, to first disable an anchor point
+                            // before setting the new one, so the icon don't grow unexpectedly
+                            onTallHeaderChanged: {
+                                if (tallHeader) {
+                                    // We want to position the close button in the top-right corner if the header is very tall
+                                    anchors.verticalCenter = undefined
+                                    anchors.topMargin = Kirigami.Units.largeSpacing
+                                    anchors.top = headerItem.top
+                                } else {
+                                    // but we want to vertically center it in a short header
+                                    anchors.top = undefined
+                                    anchors.topMargin = undefined
+                                    anchors.verticalCenter = headerItem.verticalCenter
+                                }
+                            }
+                            Component.onCompleted: tallHeaderChanged()
+
+                            z: 3
+                            visible: root.showCloseButton
+                            width: Kirigami.Units.iconSizes.smallMedium
+                            height: width
+                            source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
+                            active: closeMouseArea.containsMouse
+                            MouseArea {
+                                id: closeMouseArea
+                                hoverEnabled: true
+                                anchors.fill: parent
+                                onClicked: mouse => root.close();
+                            }
+                        }
+                        Kirigami.Separator {
+                            anchors {
+                                right: parent.right
+                                left: parent.left
+                                top: parent.bottom
+                            }
+                        }
+                    }
+
+                    QQC2.ScrollView {
+                        id: scrollView
+
+                        // Don't do the automatic interactive enable/disable
+                        // canFlickWithMouse: true
+                        property Item viewContent
+                        property real animatedContentHeight: flickableItem.contentHeight
+                        property bool userInteracting: false
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
+                        property alias flickableItem: scrollView.contentItem
+
+                        focus: false
+
+                        implicitHeight: flickableItem.contentHeight
+                        Layout.maximumHeight: flickableItem.contentHeight
+
+                        Layout.alignment: Qt.AlignTop
+
+                        // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
+                        QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
+
+                        Behavior on animatedContentHeight {
+                            NumberAnimation {
+                                duration: Kirigami.Units.shortDuration
+                                easing.type: Easing.InOutCubic
+                            }
+                        }
+                    }
+
+                    Item { Layout.fillHeight: true }
+
+                    Connections {
+                        target: scrollView.flickableItem
+                        property real oldContentY: 0
+                        function onContentYChanged() {
+                            if (outerFlickable.moving) {
+                                oldContentY = scrollView.flickableItem.contentY;
+                                return;
+                            }
+                            scrollView.userInteracting = true;
+
+                            const diff = scrollView.flickableItem.contentY - oldContentY
+
+                            outerFlickable.contentY = outerFlickable.contentY + diff;
+
+                            if (diff > 0) {
+                                contentLayout.y = Math.max(root.topInset,  contentLayout.y - diff);
+                            } else if (scrollView.flickableItem.contentY < outerFlickable.topEmptyArea + headerItem.height) {
+                                contentLayout.y = Math.min(outerFlickable.topEmptyArea + root.topInset,  contentLayout.y - diff);
+                            }
+                            oldContentY = scrollView.flickableItem.contentY;
+                            scrollView.userInteracting = false;
+                        }
+                    }
+                    Item {
+                        visible: footerItem.visible
+                        implicitHeight: footerItem.height
+                    }
+                }
+
+                // footer item is outside the layout as it should never scroll away
+
+                // Even though we're not actually using any shadows here,
+                // we're using a ShadowedRectangle instead of a regular
+                // rectangle because it allows fine-grained control over which
+                // corners to round, which we need here
+                Kirigami.ShadowedRectangle {
+                    id: footerItem
+                    width: contentLayout.width
+                    corners.bottomLeftRadius: Kirigami.Units.smallSpacing
+                    corners.bottomRightRadius: Kirigami.Units.smallSpacing
+                    parent: outerFlickable
+                    x: contentLayout.x
+                    y: Math.min(parent.height, contentLayout.y + contentLayout.height) - height
+                    visible: root.footer
+                    implicitHeight: footerParent.implicitHeight + Kirigami.Units.smallSpacing * 2 + extraMargin
+                    Kirigami.Theme.colorSet: Kirigami.Theme.Window
+                    Kirigami.Theme.inherit: false
+                    color: Kirigami.Theme.backgroundColor
+
+                    // Show an extra margin when:
+                    // * the application is in mobile mode
+                    // * it doesn't use toolbarapplicationheader
+                    // * the bottom screen controls are visible
+                    // * the sheet is displayed *under* the controls
+                    property int extraMargin: (!root.parent ||
+                        !Kirigami.Settings.isMobile ||
+                        typeof applicationWindow === "undefined" ||
+                        (root.parent === applicationWindow().overlay) ||
+                        !applicationWindow().controlsVisible ||
+                        (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar) ||
+                        (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0))
+                            ? 0 : Kirigami.Units.gridUnit * 3
+
+                    z: 2
+                    Item {
+                        id: footerParent
+                        implicitHeight: footer ? footer.implicitHeight : 0
+                        anchors {
+                            top: parent.top
+                            left: parent.left
+                            right: parent.right
+                            margins: Kirigami.Units.smallSpacing
+                        }
+                    }
+
+                    Kirigami.Separator {
+                        anchors {
+                            right: parent.right
+                            left: parent.left
+                            bottom: parent.top
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/controls/templates/SingletonHeaderSizeGroup.qml b/src/controls/templates/SingletonHeaderSizeGroup.qml
new file mode 100644 (file)
index 0000000..7e90569
--- /dev/null
@@ -0,0 +1,13 @@
+pragma Singleton
+
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import org.kde.kirigami 2.14 as Kirigami
+
+Kirigami.SizeGroup {
+    mode: Kirigami.SizeGroup.Height
+}
\ No newline at end of file
diff --git a/src/controls/templates/SwipeListItem.qml b/src/controls/templates/SwipeListItem.qml
new file mode 100644 (file)
index 0000000..8d38905
--- /dev/null
@@ -0,0 +1,564 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.4
+import QtQuick.Controls 2.4 as QQC2
+import QtQuick.Templates 2.4 as T
+import org.kde.kirigami 2.11 as Kirigami
+import "../private"
+
+/**
+ * @brief An ItemDelegate intended to support extra actions.
+ * 
+ * On mobile, these actions can be uncovered by dragging a handle,
+ * whereas with applications on tablet or desktop mode
+ * these actions are always present on the right side of the delegate.
+ *
+ * Example usage:
+ * @code{.qml}
+ * ListView {
+ *     model: myModel
+ *     delegate: SwipeListItem {
+ *         QQC2.Label {
+ *             text: model.text
+ *         }
+ *         actions: [
+ *              Action {
+ *                  icon.name: "document-decrypt"
+ *                  onTriggered: print("Action 1 clicked")
+ *              },
+ *              Action {
+ *                  icon.name: model.action2Icon
+ *                  onTriggered: //do something
+ *              }
+ *         ]
+ *     }
+ *
+ * }
+ * @endcode
+ * @inherit QtQuick.Templates.SwipeDelegate
+ */
+T.SwipeDelegate {
+    id: listItem
+
+//BEGIN properties
+    /**
+     * @brief This property sets whether the item should emit signals related to mouse interaction.
+     *
+     * default: ``true``
+     *
+     * @deprecated Use hoverEnabled instead.
+     * @property bool supportsMouseEvents
+     */
+    property alias supportsMouseEvents: listItem.hoverEnabled
+
+    /**
+     * @brief This property specifies whether the cursor is currently hovering over the item.
+     *
+     * On mobile touch devices, this will be @c true only when pressed.
+     *
+     * @see QtQuick.Templates.ItemDelegate.hovered
+     * @deprecated This will be removed in KF6; use the ``hovered`` property instead.
+     * @property bool containsMouse
+     */
+    property alias containsMouse: listItem.hovered
+
+    /**
+     * @brief This property sets whether instances of this list item will alternate
+     * between two colors, helping readability.
+     *
+     * It is suggested to use this only when implementing a view with multiple columns.
+     *
+     * default: ``false``
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    property bool alternatingBackground: false
+
+    /**
+     * @brief This property sets whether this Item is a section delegate.
+     *
+     * Setting this to @c true will make the list item look like a "title" for items under it.
+     *
+     * default: ``false``
+     *
+     * @see kirigami::ListSectionHeader
+     */
+    property bool sectionDelegate: false
+
+    /**
+     * @brief This property sets whether the separator is visible.
+     *
+     * The separator is a line between this and the Item under it.
+     *
+     * default: ``false``
+     */
+    property bool separatorVisible: false
+
+    /**
+     * @brief This property holds the background color of the list item.
+     *
+     * It is advised to use the default value.
+     * default: @link Kirigami.PlatformTheme.backgroundColor Kirigami.Theme.backgroundColor @endlink
+     */
+    property color backgroundColor: Kirigami.Theme.backgroundColor
+
+    /**
+     * @brief This property holds the background color to be used when
+     * background alternating is enabled.
+     *
+     * It is advised to use the default value.
+     * default: @link Kirigami.PlatformTheme.alternateBackgroundColor Kirigami.Theme.alternateBackgroundColor @endlink
+     *
+     * @since org.kde.kirigami 2.7
+     */
+    property color alternateBackgroundColor: Kirigami.Theme.alternateBackgroundColor
+
+    /**
+     * @brief This property holds the color of the background
+     * when the item is pressed or selected.
+     *
+     * It is advised to use the default value.
+     * default: @link Kirigami.PlatformTheme.highlightColor Kirigami.Theme.highlightColor @endlink
+     */
+    property color activeBackgroundColor: Kirigami.Theme.highlightColor
+
+    /**
+     * @brief This property holds the color of the text in the item.
+     *
+     * It is advised to use the default value.
+     * default: @link Kirigami.PlatformTheme.textColor Kirigami.Theme.textColor @endlink
+     *
+     * If custom text elements are inserted in an AbstractListItem,
+     * their color will have to be manually set with this property.
+     */
+    property color textColor: Kirigami.Theme.textColor
+
+    /**
+     * @brief This property holds the color of the text when the item is pressed or selected.
+     *
+     * It is advised to use the default value.
+     * default: @link Kirigami.PlatformTheme.highlightedTextColor Kirigami.Theme.highlightedTextColor @endlink
+     *
+     * If custom text elements are inserted in an AbstractListItem,
+     * their color property will have to be manually bound with this property
+     */
+    property color activeTextColor: Kirigami.Theme.highlightedTextColor
+
+    /**
+     * @brief This property specifies whether actions are visible and interactive.
+     *
+     * True if it's possible to see and interact with the item's actions.
+     *
+     * Actions become hidden while editing of an item, for example.
+     *
+     * @since org.kde.kirigami 2.5
+     */
+    readonly property bool actionsVisible: actionsLayout.hasVisibleActions
+
+    /**
+     * @brief This property sets whether actions behind this SwipeListItem will always be visible.
+     *
+     * default: `true in desktop and tablet mode, @c false in mobile mode`
+     *
+     * @since org.kde.kirigami 2.15
+     */
+    property bool alwaysVisibleActions: !Kirigami.Settings.isMobile
+
+    /**
+     * @brief This property holds actions of the list item.
+     *
+     * At most 4 actions can be revealed when sliding away the list item;
+     * others will be shown in the overflow menu.
+     */
+    property list<QQC2.Action> actions
+
+    /**
+     * @brief This property holds the width of the overlay.
+     *
+     * The value can represent the width of the handle component or the action layout.
+     *
+     * @since org.kde.kirigami 2.19
+     * @property real overlayWidth
+     */
+    readonly property alias overlayWidth: overlayLoader.width
+
+    // TODO KF6 remove this super wrong thing
+    /**
+     * @deprecated This property will be removed in KDE Framework 6. Use contentItem instead.
+     * @internal
+     */
+    default property alias _default: listItem.contentItem
+//END properties
+
+    LayoutMirroring.childrenInherit: true
+
+    hoverEnabled: true
+    implicitWidth: contentItem ? contentItem.implicitWidth : Kirigami.Units.gridUnit * 12
+    width: parent ? parent.width : implicitWidth
+    implicitHeight: Math.max(Kirigami.Units.gridUnit * 2, contentItem.implicitHeight) + topPadding + bottomPadding
+
+    padding: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
+
+    leftPadding: padding * 2 + (mirrored ? overlayLoader.paddingOffset : 0)
+    rightPadding: padding * 2 + (mirrored ? 0 : overlayLoader.paddingOffset)
+
+    topPadding: padding
+    bottomPadding: padding
+
+    contentItem: Item {}
+    QtObject {
+        id: internal
+
+        property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || (listItem.parent instanceof Flickable ? listItem.parent : null)) : null)
+
+        function viewHasPropertySwipeFilter(): bool {
+            return view && view.parent && view.parent.parent && "_swipeFilter" in view.parent.parent;
+        }
+
+        readonly property QtObject swipeFilterItem: (viewHasPropertySwipeFilter() && view.parent.parent._swipeFilter) ? view.parent.parent._swipeFilter : null
+
+        readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false
+
+        // install the SwipeItemEventFilter
+        onViewChanged: {
+            if (listItem.alwaysVisibleActions || !Kirigami.Settings.tabletMode) {
+                return;
+            }
+            if (viewHasPropertySwipeFilter() && Kirigami.Settings.tabletMode && !internal.view.parent.parent._swipeFilter) {
+                const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
+                internal.view.parent.parent._swipeFilter = component.createObject(internal.view.parent.parent);
+                component.destroy();
+            }
+        }
+    }
+
+    Connections {
+        target: Kirigami.Settings
+        function onTabletModeChanged() {
+            if (!internal.viewHasPropertySwipeFilter()) {
+                return;
+            }
+            if (Kirigami.Settings.tabletMode) {
+                if (!internal.swipeFilterItem) {
+                    const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
+                    listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent);
+                    component.destroy();
+                }
+            } else {
+                if (listItem.ListView.view.parent.parent._swipeFilter) {
+                    listItem.ListView.view.parent.parent._swipeFilter.destroy();
+                    slideAnim.to = 0;
+                    slideAnim.restart();
+                }
+            }
+        }
+    }
+
+//BEGIN items
+    Loader {
+        id: overlayLoader
+        readonly property int paddingOffset: (visible ? width : 0) + Kirigami.Units.smallSpacing
+        readonly property var theAlias: anchors
+        function validate(want, defaultValue) {
+            const expectedLeftPadding = () => listItem.padding * 2 + (listItem.mirrored ? overlayLoader.paddingOffset : 0)
+            const expectedRightPadding = () => listItem.padding * 2 + (listItem.mirrored ? 0 : overlayLoader.paddingOffset)
+
+            const warningText =
+                `Don't override the leftPadding or rightPadding on a SwipeListItem!\n` +
+                `This makes it impossible for me to adjust my layout as I need to for various usecases.\n` +
+                `I'll try to fix the mistake for you, but you should remove your overrides from your app's code entirely.\n` +
+                `If I can't fix the paddings, I'll fall back to a default layout, but it'll be slightly incorrect and lacks\n` +
+                `adaptations needed for touch screens and right-to-left languages, among other things.`
+
+            if (listItem.leftPadding != expectedLeftPadding() || listItem.rightPadding != expectedRightPadding()) {
+                listItem.leftPadding = Qt.binding(expectedLeftPadding)
+                listItem.rightPadding = Qt.binding(expectedRightPadding)
+                console.warn(warningText)
+                return defaultValue
+            }
+
+            return want
+        }
+        anchors {
+            right: validate((Qt.application.layoutDirection === Qt.RightToLeft) ? undefined : (contentItem ? contentItem.right : undefined), contentItem ? contentItem.right : undefined)
+            rightMargin: validate(-paddingOffset, 0)
+            left: validate((Qt.application.layoutDirection === Qt.LeftToRight) ? undefined : (contentItem ? contentItem.left : undefined), undefined)
+            leftMargin: validate(-paddingOffset, 0)
+            top: parent.top
+            bottom: parent.bottom
+        }
+        LayoutMirroring.enabled: false
+
+        parent: listItem
+        z: contentItem ? contentItem.z + 1 : 0
+        width: item ? item.implicitWidth : actionsLayout.implicitWidth
+        active: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
+        visible: listItem.actionsVisible && opacity > 0
+        asynchronous: true
+        sourceComponent: handleComponent
+        opacity: listItem.alwaysVisibleActions || Kirigami.Settings.tabletMode || listItem.hovered ? 1 : 0
+        Behavior on opacity {
+            OpacityAnimator {
+                id: opacityAnim
+                duration: Kirigami.Units.veryShortDuration
+                easing.type: Easing.InOutQuad
+            }
+        }
+    }
+
+    Component {
+        id: handleComponent
+
+        MouseArea {
+            id: dragButton
+            anchors {
+                right: parent.right
+            }
+            implicitWidth: Kirigami.Units.iconSizes.smallMedium
+
+            preventStealing: true
+            readonly property real openPosition: (listItem.width - width - listItem.leftPadding * 2)/listItem.width
+            property real startX: 0
+            property real lastPosition: 0
+            property bool openIntention
+
+            onPressed: mouse => {
+                startX = mapToItem(listItem, 0, 0).x;
+            }
+            onClicked: mouse => {
+                if (Math.abs(mapToItem(listItem, 0, 0).x - startX) > Qt.styleHints.startDragDistance) {
+                    return;
+                }
+                if (listItem.LayoutMirroring.enabled) {
+                    if (listItem.swipe.position < 0.5) {
+                        slideAnim.to = openPosition
+                    } else {
+                        slideAnim.to = 0
+                    }
+                } else {
+                    if (listItem.swipe.position > -0.5) {
+                        slideAnim.to = -openPosition
+                    } else {
+                        slideAnim.to = 0
+                    }
+                }
+                slideAnim.restart();
+            }
+            onPositionChanged: mouse => {
+                const pos = mapToItem(listItem, mouse.x, mouse.y);
+
+                if (listItem.LayoutMirroring.enabled) {
+                    listItem.swipe.position = Math.max(0, Math.min(openPosition, (pos.x / listItem.width)));
+                    openIntention = listItem.swipe.position > lastPosition;
+                } else {
+                    listItem.swipe.position = Math.min(0, Math.max(-openPosition, (pos.x / (listItem.width -listItem.rightPadding) - 1)));
+                    openIntention = listItem.swipe.position < lastPosition;
+                }
+                lastPosition = listItem.swipe.position;
+            }
+            onReleased: mouse => {
+                if (listItem.LayoutMirroring.enabled) {
+                    if (openIntention) {
+                        slideAnim.to = openPosition
+                    } else {
+                        slideAnim.to = 0
+                    }
+                } else {
+                    if (openIntention) {
+                        slideAnim.to = -openPosition
+                    } else {
+                        slideAnim.to = 0
+                    }
+                }
+                slideAnim.restart();
+            }
+
+            Kirigami.Icon {
+                id: handleIcon
+                anchors.fill: parent
+                selected: listItem.checked || (listItem.down && !listItem.checked && !listItem.sectionDelegate)
+                source: (LayoutMirroring.enabled ? (listItem.background.x < listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left") : (listItem.background.x < -listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left"))
+            }
+
+            Connections {
+                id: swipeFilterConnection
+
+                target: internal.edgeEnabled ? internal.swipeFilterItem : null
+                function onPeekChanged() {
+                    if (!listItem.actionsVisible) {
+                        return;
+                    }
+
+                    if (listItem.LayoutMirroring.enabled) {
+                        listItem.swipe.position = Math.max(0, Math.min(dragButton.openPosition, internal.swipeFilterItem.peek));
+                        dragButton.openIntention = listItem.swipe.position > dragButton.lastPosition;
+
+                    } else {
+                        listItem.swipe.position = Math.min(0, Math.max(-dragButton.openPosition, -internal.swipeFilterItem.peek));
+                        dragButton.openIntention = listItem.swipe.position < dragButton.lastPosition;
+                    }
+
+                    dragButton.lastPosition = listItem.swipe.position;
+                }
+                function onPressed(mouse) {
+                    if (internal.edgeEnabled) {
+                        dragButton.onPressed(mouse);
+                    }
+                }
+                function onClicked(mouse) {
+                    if (Math.abs(listItem.background.x) < Kirigami.Units.gridUnit && internal.edgeEnabled) {
+                        dragButton.clicked(mouse);
+                    }
+                }
+                function onReleased(mouse) {
+                    if (internal.edgeEnabled) {
+                        dragButton.released(mouse);
+                    }
+                }
+                function onCurrentItemChanged() {
+                    if (!internal.edgeEnabled) {
+                        slideAnim.to = 0;
+                        slideAnim.restart();
+                    }
+                }
+            }
+        }
+    }
+
+    // TODO: expose in API?
+    Component {
+        id: actionsBackgroundDelegate
+        MouseArea {
+
+            anchors.fill: parent
+
+            // QQC2.SwipeDelegate.onPressedChanged is broken with touch
+            onClicked: mouse => {
+                    slideAnim.to = 0;
+                    slideAnim.restart();
+            }
+            Rectangle {
+                anchors.fill: parent
+                color: parent.pressed ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Qt.darker(Kirigami.Theme.backgroundColor, 1.05)
+            }
+
+            visible: listItem.swipe.position != 0
+
+
+            EdgeShadow {
+                edge: Qt.TopEdge
+                visible: background.x != 0
+                anchors {
+                    right: parent.right
+                    left: parent.left
+                    top: parent.top
+                }
+            }
+            EdgeShadow {
+                edge: LayoutMirroring.enabled ? Qt.RightEdge : Qt.LeftEdge
+                x: LayoutMirroring.enabled ? listItem.background.x - width : (listItem.background.x + listItem.background.width)
+                visible: background.x != 0
+                anchors {
+                    top: parent.top
+                    bottom: parent.bottom
+                }
+            }
+        }
+    }
+
+
+    RowLayout {
+        id: actionsLayout
+        anchors {
+            right: parent.right
+            top: parent.top
+            bottom: parent.bottom
+            rightMargin: Kirigami.Units.smallSpacing
+        }
+        visible: parent !== listItem
+        parent: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
+                ? listItem.swipe.leftItem || listItem.swipe.rightItem || listItem
+                : overlayLoader
+
+        property bool hasVisibleActions: false
+        function updateVisibleActions(definitelyVisible) {
+            if (definitelyVisible === undefined) {
+                definitelyVisible = false
+            }
+
+            if (definitelyVisible) {
+                hasVisibleActions = true;
+            } else {
+                const actionCount = listItem.actions.length;
+                for (let i = 0; i < actionCount; i++) {
+                    // Assuming that visible is only false if it is explicitly false, and not just falsy
+                    if (listItem.actions[i].visible === false) {
+                        continue;
+                    }
+                    hasVisibleActions = true;
+                    break;
+                }
+            }
+        }
+
+        Repeater {
+            model: {
+                if (listItem.actions.length === 0) {
+                    return null;
+                } else {
+                    return listItem.actions[0].text !== undefined &&
+                        listItem.actions[0].trigger !== undefined ?
+                            listItem.actions :
+                            listItem.actions[0];
+                }
+            }
+            delegate: QQC2.ToolButton {
+                icon.name: modelData.iconName !== "" ? modelData.iconName : ""
+                icon.source: modelData.iconSource !== "" ? modelData.iconSource : ""
+                enabled: (modelData && modelData.enabled !== undefined) ? modelData.enabled : true;
+                visible: (modelData && modelData.visible !== undefined) ? modelData.visible : true;
+                onVisibleChanged: actionsLayout.updateVisibleActions(visible);
+                Component.onCompleted: actionsLayout.updateVisibleActions(visible);
+                Component.onDestruction: actionsLayout.updateVisibleActions(visible);
+                QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
+                QQC2.ToolTip.timeout: 5000
+                QQC2.ToolTip.visible: listItem.visible && (Kirigami.Settings.tabletMode ? pressed : hovered) && QQC2.ToolTip.text.length > 0
+                QQC2.ToolTip.text: modelData.tooltip || modelData.text
+
+                onClicked: {
+                    if (modelData && modelData.trigger !== undefined) {
+                        modelData.trigger();
+                    }
+                    slideAnim.to = 0;
+                    slideAnim.restart();
+                }
+
+                Accessible.name: modelData.text
+                Accessible.description: modelData.tooltip
+            }
+        }
+    }
+
+
+    background: DefaultListItemBackground {}
+
+    swipe {
+        enabled: false
+        right: listItem.alwaysVisibleActions || listItem.LayoutMirroring.enabled || !Kirigami.Settings.tabletMode ? null : actionsBackgroundDelegate
+        left: listItem.alwaysVisibleActions || listItem.LayoutMirroring.enabled && Kirigami.Settings.tabletMode ? actionsBackgroundDelegate : null
+    }
+    NumberAnimation {
+        id: slideAnim
+        duration: Kirigami.Units.longDuration
+        easing.type: Easing.InOutQuad
+        target: listItem.swipe
+        property: "position"
+        from: listItem.swipe.position
+    }
+//END items
+}
diff --git a/src/controls/templates/private/BackButton.qml b/src/controls/templates/private/BackButton.qml
new file mode 100644 (file)
index 0000000..50049a9
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.4 as Kirigami
+
+QQC2.ToolButton {
+    id: button
+
+    icon.name: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic")
+
+    enabled: {
+        const pageStack = applicationWindow().pageStack;
+
+        if (pageStack.layers.depth > 1) {
+            return true;
+        }
+
+        if (pageStack.depth > 1) {
+            if (pageStack.currentIndex > 0) {
+                return true;
+            }
+
+            const view = pageStack.columnView;
+            if (LayoutMirroring.enabled) {
+                return view.contentWidth - view.width < view.contentX
+            } else {
+                return view.contentX > 0;
+            }
+        }
+
+        return false;
+    }
+
+    property var showNavButtons: {
+        try {
+            return globalToolBar.showNavigationButtons
+        } catch (_) {
+            return false
+        }
+    }
+    // The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
+    visible: applicationWindow().pageStack.layers.depth > 1 || (applicationWindow().pageStack.contentItem.contentWidth > applicationWindow().pageStack.width + Kirigami.Units.gridUnit && (button.showNavButtons === true || (button.showNavButtons & Kirigami.ApplicationHeaderStyle.ShowBackButton)))
+
+    onClicked: {
+        applicationWindow().pageStack.goBack();
+    }
+
+    text: qsTr("Navigate Back")
+    display: QQC2.ToolButton.IconOnly
+
+    QQC2.ToolTip {
+        visible: button.hovered
+        text: button.text
+        delay: Kirigami.Units.toolTipDelay
+        timeout: 5000
+        y: button.height
+    }
+}
diff --git a/src/controls/templates/private/BorderPropertiesGroup.qml b/src/controls/templates/private/BorderPropertiesGroup.qml
new file mode 100644 (file)
index 0000000..571c702
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+
+QtObject {
+    /**
+     *  @brief This property holds the color of this border.
+     */
+    property color color
+
+    /**
+     *  @brief This property holds the width of this border.
+     */
+    property real width
+}
diff --git a/src/controls/templates/private/ContextIcon.qml b/src/controls/templates/private/ContextIcon.qml
new file mode 100644 (file)
index 0000000..7ef27d0
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.4 as Kirigami
+
+Item {
+    id: canvas
+    width: height
+    height: Kirigami.Units.iconSizes.smallMedium
+    property Kirigami.OverlayDrawer drawer
+    property color color: Kirigami.Theme.textColor
+    opacity: 0.8
+    layer.enabled: true
+
+    LayoutMirroring.enabled: false
+    LayoutMirroring.childrenInherit: true
+
+    Item {
+        id: iconRoot
+        anchors {
+            fill: parent
+            margins: Kirigami.Units.smallSpacing
+        }
+        property int thickness: 2
+        Rectangle {
+            anchors {
+                horizontalCenter: parent.horizontalCenter
+                top: parent.top
+                //horizontalCenterOffset: -parent.width/2
+                topMargin: (parent.height/2 - iconRoot.thickness/2) * drawer.position
+            }
+            antialiasing: drawer.position !== 0
+            transformOrigin: Item.Center
+            width: (1 - drawer.position) * height + drawer.position * (Math.sqrt(2*(parent.width*parent.width)))
+            height: iconRoot.thickness
+            color: canvas.color
+            rotation: 45 * drawer.position
+        }
+
+        Rectangle {
+            anchors.centerIn: parent
+            width: height
+            height: iconRoot.thickness
+            color: canvas.color
+        }
+
+
+        Rectangle {
+            anchors {
+                horizontalCenter: parent.horizontalCenter
+                bottom: parent.bottom
+             //   topMargin: -iconRoot.thickness/2 * drawer.position
+                bottomMargin: (parent.height/2 - iconRoot.thickness/2) * drawer.position
+            }
+            antialiasing: drawer.position !== 0
+            transformOrigin: Item.Center
+            width: (1 - drawer.position) * height + drawer.position * (Math.sqrt(2*(parent.width*parent.width)))
+            height: iconRoot.thickness
+            color: canvas.color
+            rotation: -45 * drawer.position
+        }
+    }
+}
+
diff --git a/src/controls/templates/private/ForwardButton.qml b/src/controls/templates/private/ForwardButton.qml
new file mode 100644 (file)
index 0000000..34b84c8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.4 as Kirigami
+
+QQC2.ToolButton {
+    id: button
+
+    icon.name: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
+
+    enabled: applicationWindow().pageStack.currentIndex < applicationWindow().pageStack.depth-1
+
+    property var showNavButtons: {
+        try {
+            return globalToolBar.showNavigationButtons
+        } catch (_) {
+            return false
+        }
+    }
+    // The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
+    visible: applicationWindow().pageStack.layers.depth === 1 && applicationWindow().pageStack.contentItem.contentWidth > applicationWindow().pageStack.width + Kirigami.Units.gridUnit && (showNavButtons === true || (showNavButtons & Kirigami.ApplicationHeaderStyle.ShowForwardButton))
+
+    onClicked: applicationWindow().pageStack.goForward();
+
+    text: qsTr("Navigate Forward")
+    display: QQC2.ToolButton.IconOnly
+
+    QQC2.ToolTip {
+        visible: button.hovered
+        text: button.text
+        delay: Kirigami.Units.toolTipDelay
+        timeout: 5000
+        y: button.height
+    }
+}
diff --git a/src/controls/templates/private/GenericDrawerIcon.qml b/src/controls/templates/private/GenericDrawerIcon.qml
new file mode 100644 (file)
index 0000000..adafb25
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.4 as Kirigami
+
+Item {
+    width: height
+    height: Kirigami.Units.iconSizes.smallMedium
+    property Kirigami.OverlayDrawer drawer
+    property color color: Kirigami.Theme.textColor
+    opacity: 0.8
+    layer.enabled: true
+
+    Kirigami.Icon {
+        selected: drawer.handle.pressed
+        opacity: 1 - drawer.position
+        anchors.fill: parent
+        source: drawer.handleClosedIcon.name ? drawer.handleClosedIcon.name : drawer.handleClosedIcon.source
+        color: drawer.handleClosedIcon.color
+    }
+    Kirigami.Icon {
+        selected: drawer.handle.pressed
+        opacity: drawer.position
+        anchors.fill: parent
+        source: drawer.handleOpenIcon.name ? drawer.handleOpenIcon.name : drawer.handleOpenIcon.source
+        color: drawer.handleOpenIcon.color
+    }
+}
+
diff --git a/src/controls/templates/private/IconPropertiesGroup.qml b/src/controls/templates/private/IconPropertiesGroup.qml
new file mode 100644 (file)
index 0000000..d306d4e
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQml 2.1
+
+/**
+ * @brief Group of icon properties.
+ * 
+ * This is a subset of those used in QQC2, Kirigami.Action still needs the full one as it needs 100% api compatibility.
+ *
+ * @note Depending on the implementation, if a Freedesktop standard icon with the
+ * specified name is not found, the ::source property will be used instead.
+ */
+QtObject {
+    /**
+     * @brief This property holds a Freedesktop standard icon name.
+     *
+     * The icon will be loaded from the selected icon theme, which can be set
+     * by the platform or included with the app.
+     *
+     * @see kirigami::Icon::source
+     */
+    property string name
+    
+    /**
+     * @brief This property holds the icon source.
+     *
+     * The icon will be loaded as a regular image.
+     *
+     * @see kirigami::Icon::source
+     */
+    property var source
+
+    /**
+     * @brief This property holds the icon tint color.
+     *
+     * The icon is tinted with the specified color, unless the color is set to "transparent".
+     *
+     * default: ``transparent``
+     *
+     * @see kirigami::Icon::color
+     */
+    property color color: "transparent"
+}
+
diff --git a/src/controls/templates/private/MenuIcon.qml b/src/controls/templates/private/MenuIcon.qml
new file mode 100644 (file)
index 0000000..7b3cc12
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import org.kde.kirigami 2.4 as Kirigami
+
+Item {
+    id: canvas
+    width: height
+    height: Kirigami.Units.iconSizes.smallMedium
+    property Kirigami.OverlayDrawer drawer
+    property color color: Kirigami.Theme.textColor
+    opacity: 0.8
+    layer.enabled: true
+
+    LayoutMirroring.enabled: false
+    LayoutMirroring.childrenInherit: true
+    Item {
+        id: iconRoot
+        anchors {
+            fill: parent
+            margins: Kirigami.Units.smallSpacing
+        }
+        readonly property int thickness: 2
+        readonly property real drawerPosition: drawer ? drawer.position : 0
+
+        Rectangle {
+            anchors {
+                right: parent.right
+                top: parent.top
+                topMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
+            }
+            antialiasing: iconRoot.drawerPosition !== 0
+            transformOrigin: Item.Right
+            width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
+            height: iconRoot.thickness
+            color: canvas.color
+            rotation: -45 * iconRoot.drawerPosition
+        }
+
+        Rectangle {
+            anchors.centerIn: parent
+            width: parent.width - parent.width * iconRoot.drawerPosition
+            height: iconRoot.thickness
+            color: canvas.color
+        }
+
+        Rectangle {
+            anchors {
+                right: parent.right
+                bottom: parent.bottom
+                bottomMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
+            }
+            antialiasing: iconRoot.drawerPosition !== 0
+            transformOrigin: Item.Right
+            width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
+            height: iconRoot.thickness
+            color: canvas.color
+            rotation: 45 * iconRoot.drawerPosition
+        }
+    }
+}
diff --git a/src/controls/templates/private/PassiveNotificationsManager.qml b/src/controls/templates/private/PassiveNotificationsManager.qml
new file mode 100644 (file)
index 0000000..518c725
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.20 as Kirigami
+
+/**
+ * @brief PassiveNotificationManager is meant to display small, passive and
+ * inline notifications in the app.
+ *
+ * It is used to show notifications of limited importance that make sense only
+ * when the user is using the application and wouldn't be suited as a global
+ * system-wide notification.
+*/
+Item {
+    id: root
+
+    property int maximumNotificationWidth: {
+        if (Kirigami.Settings.isMobile) {
+            return applicationWindow().width - Kirigami.Units.largeSpacing * 4
+        } else {
+            return Math.min(Kirigami.Units.gridUnit * 25, applicationWindow().width / 1.5)
+        }
+    }
+
+    readonly property int maximumNotificationCount: 4
+
+    function showNotification(message, timeout, actionText, callBack) {
+        if (!message) {
+            return;
+        }
+
+        let interval = 7000;
+
+        if (timeout === "short") {
+            interval = 4000;
+        } else if (timeout === "long") {
+            interval = 12000;
+        } else if (timeout > 0) {
+            interval = timeout;
+        }
+
+        // this wrapper is necessary because of Qt casting a function into an object
+        const callBackWrapperObj = callBackWrapper.createObject()
+        callBackWrapperObj.callBack = callBack
+
+        // set empty string & function for qml not to complain
+        notificationsModel.append({
+            text: message,
+            actionButtonText: actionText || "",
+            closeInterval: interval,
+            callBackWrapper: callBackWrapperObj
+        })
+        // remove the oldest notification if new notification count would exceed 3
+        if (notificationsModel.count === maximumNotificationCount) {
+            if (listView.itemAtIndex(0).hovered === true) {
+                hideNotification(1)
+            } else {
+                hideNotification()
+            }
+        }
+    }
+
+    function hideNotification(index = 0) {
+        if (index >= 0 && notificationsModel.count > index) {
+            const callBackWrapperObj = notificationsModel.get(index).callBackWrapper
+            if (callBackWrapperObj) {
+                callBackWrapperObj.destroy()
+            }
+            notificationsModel.remove(index)
+        }
+    }
+
+    // we have to set height to show more than one notification
+    height: Math.min(applicationWindow().height, Kirigami.Units.gridUnit * 10)
+
+    implicitHeight: listView.implicitHeight
+    implicitWidth: listView.implicitWidth
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
+
+    ListModel {
+        id: notificationsModel
+    }
+
+    ListView {
+        id: listView
+
+        anchors.fill: parent
+        anchors.bottomMargin: Kirigami.Units.largeSpacing
+
+        implicitWidth: root.maximumNotificationWidth
+        spacing: Kirigami.Units.smallSpacing
+        model: notificationsModel
+        verticalLayoutDirection: ListView.BottomToTop
+        keyNavigationEnabled: false
+        reuseItems: false  // do not resue items, otherwise delegates do not hide themselves properly
+        focus: false
+        interactive: false
+
+        add: Transition {
+            id: addAnimation
+            ParallelAnimation {
+                alwaysRunToEnd: true
+                NumberAnimation {
+                    property: "opacity"
+                    from: 0
+                    to: 1
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+                NumberAnimation {
+                    property: "y"
+                    from: addAnimation.ViewTransition.destination.y - Kirigami.Units.gridUnit * 3
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.OutCubic
+                }
+            }
+        }
+        displaced: Transition {
+            ParallelAnimation {
+                alwaysRunToEnd: true
+                NumberAnimation {
+                    property: "y"
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InOutCubic
+                }
+                NumberAnimation {
+                    property: "opacity"
+                    duration: 0
+                    to: 1
+                }
+            }
+        }
+        remove: Transition {
+            ParallelAnimation {
+                alwaysRunToEnd: true
+                NumberAnimation {
+                    property: "opacity"
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+                NumberAnimation {
+                    property: "y"
+                    to: Kirigami.Units.gridUnit * 3
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+                PropertyAction {
+                    property: "transformOrigin"
+                    value: Item.Bottom
+                }
+                PropertyAnimation {
+                    property: "scale"
+                    from: 1
+                    to: 0
+                    duration: Kirigami.Units.longDuration
+                    easing.type: Easing.InCubic
+                }
+            }
+        }
+        delegate: QQC2.Control {
+            id: delegate
+
+            hoverEnabled: true
+
+            anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+            width: Math.min(implicitWidth, maximumNotificationWidth)
+            z: {
+                if (delegate.hovered) {
+                    return 2;
+                } else if (delegate.index === 0) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            }
+
+            leftPadding: Kirigami.Units.largeSpacing
+            rightPadding: Kirigami.Units.largeSpacing
+            topPadding: Kirigami.Units.largeSpacing
+            bottomPadding: Kirigami.Units.largeSpacing
+
+            contentItem: RowLayout {
+                id: mainLayout
+
+                Kirigami.Theme.inherit: false
+                Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
+
+                spacing: Kirigami.Units.mediumSpacing
+
+                TapHandler {
+                    acceptedButtons: Qt.LeftButton
+                    onTapped: eventPoint => hideNotification(index)
+                }
+                Timer {
+                    id: timer
+                    interval: model.closeInterval
+                    running: !delegate.hovered
+                    onTriggered: hideNotification(index)
+                }
+
+                QQC2.Label {
+                    id: label
+                    text: model.text
+                    elide: Text.ElideRight
+                    wrapMode: Text.Wrap
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignVCenter
+                }
+
+                QQC2.Button {
+                    id: actionButton
+                    text: model.actionButtonText
+                    visible: text.length > 0
+                    Layout.alignment: Qt.AlignVCenter
+                    onClicked: {
+                        const callBack = model.callBackWrapper.callBack
+                        hideNotification(index)
+                        if (callBack && (typeof callBack === "function")) {
+                            callBack();
+                        }
+                    }
+                }
+            }
+            background: Kirigami.ShadowedRectangle {
+                Kirigami.Theme.inherit: false
+                Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
+                shadow {
+                    size: Kirigami.Units.gridUnit/2
+                    color: Qt.rgba(0, 0, 0, 0.4)
+                    yOffset: 2
+                }
+                radius: Kirigami.Units.smallSpacing * 2
+                color: Kirigami.Theme.backgroundColor
+                opacity: 0.9
+            }
+        }
+    }
+    Component {
+        id: callBackWrapper
+        QtObject {
+            property var callBack
+        }
+    }
+}
+
diff --git a/src/controls/templates/qmldir b/src/controls/templates/qmldir
new file mode 100644 (file)
index 0000000..17c3c94
--- /dev/null
@@ -0,0 +1,10 @@
+module org.kde.kirigami.templates
+
+OverlaySheet 2.2 OverlaySheet.qml
+FormLayout 2.2 FormLayout.qml
+SwipeListItem 2.2 SwipeListItem.qml
+AbstractListItem 2.2 AbstractListItem.qml
+ApplicationHeader 2.2 ApplicationHeader.qml
+AbstractApplicationHeader 2.2 AbstractApplicationHeader.qml
+OverlayDrawer 2.2 OverlayDrawer.qml
+singleton AppHeaderSizeGroup 2.2 SingletonHeaderSizeGroup.qml
\ No newline at end of file
diff --git a/src/delegaterecycler.cpp b/src/delegaterecycler.cpp
new file mode 100644 (file)
index 0000000..3d1c0f3
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "delegaterecycler.h"
+
+#include "loggingcategory.h"
+#include <QDebug>
+#include <QQmlComponent>
+#include <QQmlContext>
+#include <QQmlEngine>
+
+DelegateRecyclerAttached::DelegateRecyclerAttached(QObject *parent)
+    : QObject(parent)
+{
+}
+
+DelegateRecyclerAttached::~DelegateRecyclerAttached()
+{
+}
+/*
+void setRecycler(DelegateRecycler *recycler)
+{
+    m_recycler = recycler;
+}
+
+DelegateRecycler *recycler() const
+{
+    return m_recycler;
+}
+*/
+
+class DelegateCache
+{
+public:
+    DelegateCache();
+    ~DelegateCache();
+
+    void ref(QQmlComponent *);
+    void deref(QQmlComponent *);
+
+    void insert(QQmlComponent *, QQuickItem *);
+    QQuickItem *take(QQmlComponent *);
+
+private:
+    static const int s_cacheSize = 40;
+    QHash<QQmlComponent *, int> m_refs;
+    QHash<QQmlComponent *, QList<QQuickItem *>> m_unusedItems;
+};
+
+Q_GLOBAL_STATIC(DelegateCache, s_delegateCache)
+
+DelegateCache::DelegateCache()
+{
+}
+
+DelegateCache::~DelegateCache()
+{
+    for (auto &item : std::as_const(m_unusedItems)) {
+        qDeleteAll(item);
+    }
+}
+
+void DelegateCache::ref(QQmlComponent *component)
+{
+    m_refs[component]++;
+}
+
+void DelegateCache::deref(QQmlComponent *component)
+{
+    auto itRef = m_refs.find(component);
+    if (itRef == m_refs.end()) {
+        return;
+    }
+
+    (*itRef)--;
+    if (*itRef <= 0) {
+        m_refs.erase(itRef);
+
+        qDeleteAll(m_unusedItems.take(component));
+    }
+}
+
+void DelegateCache::insert(QQmlComponent *component, QQuickItem *item)
+{
+    auto &items = m_unusedItems[component];
+    if (items.length() >= s_cacheSize) {
+        item->deleteLater();
+        return;
+    }
+
+    DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(item, false));
+    if (attached) {
+        Q_EMIT attached->pooled();
+    }
+
+    item->setParentItem(nullptr);
+    items.append(item);
+}
+
+QQuickItem *DelegateCache::take(QQmlComponent *component)
+{
+    auto it = m_unusedItems.find(component);
+    if (it != m_unusedItems.end() && !it->isEmpty()) {
+        return it->takeFirst();
+    }
+    return nullptr;
+}
+
+DelegateRecycler::DelegateRecycler(QQuickItem *parent)
+    : QQuickItem(parent)
+{
+    setFlags(QQuickItem::ItemIsFocusScope);
+}
+
+DelegateRecycler::~DelegateRecycler()
+{
+    if (m_sourceComponent) {
+        s_delegateCache->insert(m_sourceComponent, m_item);
+        s_delegateCache->deref(m_sourceComponent);
+    }
+}
+
+void DelegateRecycler::syncIndex()
+{
+    const QVariant newIndex = m_propertiesTracker->property("trackedIndex");
+    if (!m_item || !newIndex.isValid()) {
+        return;
+    }
+    QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+    ctx->setContextProperty(QStringLiteral("index"), newIndex);
+}
+
+void DelegateRecycler::syncModel()
+{
+    const QVariant newModel = m_propertiesTracker->property("trackedModel");
+    if (!m_item || !newModel.isValid()) {
+        return;
+    }
+    QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+    ctx->setContextProperty(QStringLiteral("model"), newModel);
+
+    // try to bind all properties
+    QObject *modelObj = newModel.value<QObject *>();
+    if (modelObj) {
+        const QMetaObject *metaObj = modelObj->metaObject();
+        for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
+            ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj));
+        }
+    }
+}
+
+void DelegateRecycler::syncModelProperties()
+{
+    const QVariant model = m_propertiesTracker->property("trackedModel");
+    if (!m_item || !model.isValid()) {
+        return;
+    }
+    QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+
+    // try to bind all properties
+    QObject *modelObj = model.value<QObject *>();
+    if (modelObj) {
+        const QMetaObject *metaObj = modelObj->metaObject();
+        for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
+            ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj));
+        }
+    }
+}
+
+void DelegateRecycler::syncModelData()
+{
+    const QVariant newModelData = m_propertiesTracker->property("trackedModelData");
+    if (!m_item || !newModelData.isValid()) {
+        return;
+    }
+    QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+    ctx->setContextProperty(QStringLiteral("modelData"), newModelData);
+}
+
+QQmlComponent *DelegateRecycler::sourceComponent() const
+{
+    return m_sourceComponent;
+}
+
+void DelegateRecycler::setSourceComponent(QQmlComponent *component)
+{
+    if (component && component->parent() == this) {
+        qCWarning(KirigamiLog) << "Error: source components cannot be declared inside DelegateRecycler";
+        return;
+    }
+    if (m_sourceComponent == component) {
+        return;
+    }
+
+    if (!m_propertiesTracker) {
+        static QHash<QQmlEngine *, QQmlComponent *> propertiesTrackerComponent;
+        auto engine = qmlEngine(this);
+        auto it = propertiesTrackerComponent.find(engine);
+        if (it == propertiesTrackerComponent.end()) {
+            connect(engine, &QObject::destroyed, engine, [engine] {
+                propertiesTrackerComponent.remove(engine);
+            });
+            it = propertiesTrackerComponent.insert(engine, new QQmlComponent(engine, engine));
+
+            /* clang-format off */
+            (*it)->setData(QByteArrayLiteral(R"(
+import QtQuick 2.3
+QtObject {
+    property int trackedIndex: index
+    property var trackedModel: typeof model != 'undefined' ? model : null
+    property var trackedModelData: typeof modelData != 'undefined' ? modelData : null
+}
+)"), QUrl(QStringLiteral("delegaterecycler.cpp")));
+        }
+        /* clang-format on */
+        m_propertiesTracker = (*it)->create(QQmlEngine::contextForObject(this));
+
+        connect(m_propertiesTracker, SIGNAL(trackedIndexChanged()), this, SLOT(syncIndex()));
+        connect(m_propertiesTracker, SIGNAL(trackedModelChanged()), this, SLOT(syncModel()));
+        connect(m_propertiesTracker, SIGNAL(trackedModelDataChanged()), this, SLOT(syncModelData()));
+    }
+
+    if (m_sourceComponent) {
+        if (m_item) {
+            disconnect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints);
+            disconnect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints);
+            s_delegateCache->insert(component, m_item);
+        }
+        s_delegateCache->deref(component);
+    }
+
+    m_sourceComponent = component;
+    s_delegateCache->ref(component);
+
+    m_item = s_delegateCache->take(component);
+
+    if (!m_item) {
+        QQuickItem *candidate = parentItem();
+        QQmlContext *ctx = nullptr;
+        if (component->creationContext()) {
+            ctx = new QQmlContext(component->creationContext());
+        }
+        while (!ctx && candidate) {
+            QQmlContext *parentCtx = QQmlEngine::contextForObject(candidate);
+            if (parentCtx) {
+                ctx = new QQmlContext(parentCtx, candidate);
+                break;
+            } else {
+                candidate = candidate->parentItem();
+            }
+        }
+
+        Q_ASSERT(ctx);
+
+        QObject *contextObjectToSet = nullptr;
+        {
+            // Find the first parent that has a context object with a valid translationDomain property, i.e. is a KLocalizedContext
+            QQmlContext *auxCtx = ctx;
+            while (auxCtx != nullptr) {
+                QObject *auxCtxObj = auxCtx->contextObject();
+                if (auxCtxObj && auxCtxObj->property("translationDomain").isValid()) {
+                    contextObjectToSet = auxCtxObj;
+                    break;
+                }
+                auxCtx = auxCtx->parentContext();
+            }
+        }
+        if (contextObjectToSet) {
+            ctx->setContextObject(contextObjectToSet);
+        }
+
+        QObject *modelObj = m_propertiesTracker->property("trackedModel").value<QObject *>();
+        if (modelObj) {
+            const QMetaObject *metaObj = modelObj->metaObject();
+            for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) {
+                QMetaProperty prop = metaObj->property(i);
+                ctx->setContextProperty(QString::fromUtf8(prop.name()), prop.read(modelObj));
+                if (prop.hasNotifySignal()) {
+                    QMetaMethod updateSlot = metaObject()->method(metaObject()->indexOfSlot("syncModelProperties()"));
+                    connect(modelObj, prop.notifySignal(), this, updateSlot);
+                }
+            }
+        }
+
+        ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel"));
+        ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData"));
+        ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex"));
+        ctx->setContextProperty(QStringLiteral("delegateRecycler"), this);
+
+        QObject *obj = component->create(ctx);
+        m_item = qobject_cast<QQuickItem *>(obj);
+        if (!m_item) {
+            obj->deleteLater();
+        } else {
+            connect(m_item.data(), &QObject::destroyed, ctx, &QObject::deleteLater);
+            // if the user binded an explicit width, consider it, otherwise base upon implicit
+            m_widthFromItem = m_item->width() > 0 && m_item->width() != m_item->implicitWidth();
+            m_heightFromItem = m_item->height() > 0 && m_item->height() != m_item->implicitHeight();
+
+            if (m_widthFromItem && m_heightFromItem) {
+                connect(m_item.data(), &QQuickItem::heightChanged, this, [this]() {
+                    updateSize(false);
+                });
+            }
+        }
+    } else {
+        syncModel();
+
+        QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+        ctx->setContextProperties({QQmlContext::PropertyPair{QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData")},
+                                   QQmlContext::PropertyPair{QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")},
+                                   QQmlContext::PropertyPair{QStringLiteral("delegateRecycler"), QVariant::fromValue<QObject *>(this)}});
+
+        DelegateRecyclerAttached *attached = qobject_cast<DelegateRecyclerAttached *>(qmlAttachedPropertiesObject<DelegateRecycler>(m_item, false));
+        if (attached) {
+            Q_EMIT attached->reused();
+        }
+    }
+
+    if (m_item) {
+        m_item->setParentItem(this);
+        connect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints);
+        connect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints);
+
+        updateSize(true);
+    }
+
+    Q_EMIT sourceComponentChanged();
+}
+
+void DelegateRecycler::resetSourceComponent()
+{
+    s_delegateCache->deref(m_sourceComponent);
+    m_sourceComponent = nullptr;
+}
+
+DelegateRecyclerAttached *DelegateRecycler::qmlAttachedProperties(QObject *object)
+{
+    return new DelegateRecyclerAttached(object);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void DelegateRecycler::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+#else
+void DelegateRecycler::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+#endif
+{
+    if (m_item && newGeometry.size() != oldGeometry.size()) {
+        updateSize(true);
+    }
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QQuickItem::geometryChanged(newGeometry, oldGeometry);
+#else
+    QQuickItem::geometryChange(newGeometry, oldGeometry);
+#endif
+}
+
+void DelegateRecycler::focusInEvent(QFocusEvent *event)
+{
+    QQuickItem::focusInEvent(event);
+    if (!m_item) {
+        return;
+    }
+
+    m_item->setFocus(event->reason());
+}
+
+void DelegateRecycler::updateHints()
+{
+    updateSize(false);
+}
+
+void DelegateRecycler::updateSize(bool parentResized)
+{
+    if (!m_item) {
+        return;
+    }
+
+    const bool needToUpdateWidth = !m_widthFromItem && parentResized && widthValid();
+    const bool needToUpdateHeight = !m_heightFromItem && parentResized && heightValid();
+
+    if (parentResized) {
+        m_item->setPosition(QPoint(0, 0));
+    }
+    if (needToUpdateWidth && needToUpdateHeight) {
+        m_item->setSize(QSizeF(width(), height()));
+    } else if (needToUpdateWidth) {
+        m_item->setWidth(width());
+    } else if (needToUpdateHeight) {
+        m_item->setHeight(height());
+    }
+
+    if (m_updatingSize) {
+        return;
+    }
+
+    m_updatingSize = true;
+
+    if (m_heightFromItem) {
+        setHeight(m_item->height());
+    }
+    if (m_widthFromItem) {
+        setWidth(m_item->width());
+    }
+
+    setImplicitSize(m_item->implicitWidth() >= 0 ? m_item->implicitWidth() : m_item->width(),
+                    m_item->implicitHeight() >= 0 ? m_item->implicitHeight() : m_item->height());
+
+    m_updatingSize = false;
+}
diff --git a/src/delegaterecycler.h b/src/delegaterecycler.h
new file mode 100644 (file)
index 0000000..a1a62bb
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef DELEGATERECYCLER_H
+#define DELEGATERECYCLER_H
+
+#include <QPointer>
+#include <QQuickItem>
+#include <QVariant>
+
+class DelegateRecyclerAttached : public QObject
+{
+    Q_OBJECT
+
+public:
+    DelegateRecyclerAttached(QObject *parent = nullptr);
+    ~DelegateRecyclerAttached() override;
+
+Q_SIGNALS:
+    void pooled();
+    void reused();
+};
+
+/**
+ * This class may be used as a delegate of a QtQuick.ListView or a QtQuick.GridView
+ * in the case the intended delegate is a bit heavy, with many objects inside.
+ * This will ensure the delegate instances will be put back in a common pool after
+ * destruction, so when scrolling a big list, the delegates from old delete items will
+ * be taken from the pool and reused, minimizing the need of instantiating new objects
+ * and deleting old ones. It ensures scrolling of lists with heavy delegates is
+ * smoother and helps with memory fragmentations as well.
+ *
+ * @note org::kde::kirigami::CardsListView and org::kde::kirigami::CardsGridView
+ * are already using this recycler, so do NOT use it as a delegate for those 2 views.
+ * Also, do NOT use this with a QtQuick.Repeater.
+ *
+ * @since org.kde.kirigami 2.4
+ */
+class DelegateRecycler : public QQuickItem
+{
+    Q_OBJECT
+
+    /**
+     * The Component the actual delegates will be built from.
+     *
+     * @note the component may not be a child of this object, therefore it can't be
+     * declared inside the DelegateRecycler declaration.
+     *
+     * The DelegateRecycler will not take ownership of the delegate Component, so it's up
+     * to the caller to delete it (usually with the normal child/parent relationship)
+     */
+    Q_PROPERTY(QQmlComponent *sourceComponent READ sourceComponent WRITE setSourceComponent RESET resetSourceComponent NOTIFY sourceComponentChanged)
+
+public:
+    DelegateRecycler(QQuickItem *parent = nullptr);
+    ~DelegateRecycler() override;
+
+    QQmlComponent *sourceComponent() const;
+    void setSourceComponent(QQmlComponent *component);
+    void resetSourceComponent();
+
+    static DelegateRecyclerAttached *qmlAttachedProperties(QObject *object);
+
+protected:
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#else
+    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#endif
+    void focusInEvent(QFocusEvent *event) override;
+
+    void updateHints();
+    void updateSize(bool parentResized);
+
+Q_SIGNALS:
+    void sourceComponentChanged();
+
+private Q_SLOTS:
+    void syncIndex();
+    void syncModel();
+    void syncModelProperties();
+    void syncModelData();
+
+private:
+    QPointer<QQmlComponent> m_sourceComponent;
+    QPointer<QQuickItem> m_item;
+    QObject *m_propertiesTracker = nullptr;
+    bool m_updatingSize = false;
+    bool m_widthFromItem = false;
+    bool m_heightFromItem = false;
+};
+
+QML_DECLARE_TYPEINFO(DelegateRecycler, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif
diff --git a/src/enums.cpp b/src/enums.cpp
new file mode 100644 (file)
index 0000000..3b55546
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "enums.h"
+
+#include "moc_enums.cpp"
+
+#include <QVariant>
+
+bool DisplayHint::displayHintSet(DisplayHints values, Hint hint)
+{
+    return isDisplayHintSet(values, hint);
+}
+
+bool DisplayHint::displayHintSet(QObject *object, DisplayHint::Hint hint)
+{
+    if (!object) {
+        return false;
+    }
+
+    auto property = object->property("displayHint");
+    if (property.isValid()) {
+        return isDisplayHintSet(DisplayHints{property.toInt()}, hint);
+    } else {
+        return false;
+    }
+}
+
+bool DisplayHint::isDisplayHintSet(DisplayHint::DisplayHints values, DisplayHint::Hint hint)
+{
+    if (hint == DisplayHint::AlwaysHide && (values & DisplayHint::KeepVisible)) {
+        return false;
+    }
+
+    return values & hint;
+}
diff --git a/src/enums.h b/src/enums.h
new file mode 100644 (file)
index 0000000..ea92c1f
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef ENUMS_H
+#define ENUMS_H
+
+#include <QObject>
+
+/**
+ * @brief Types used in org::kde::kirigami::PageRow and org::kde::kirigami::Page that indicate how
+ * top bar controls should be represented to the user.
+ */
+class ApplicationHeaderStyle : public QObject
+{
+    Q_OBJECT
+
+public:
+    /**
+     * @brief Types that indicate how the global toolbar should be shown to the
+     * user.
+     */
+    enum Status {
+        /**
+         * @brief Automatically choose other values depending on the device's
+         * form factor.
+         */
+        Auto = 0,
+
+        /**
+         * @brief Display the main, left, and right actions horizontally
+         * centered at the bottom of the page in a mobile-friendly way.
+         */
+        Breadcrumb,
+
+        /**
+         * @brief Each page will only have its title at the top alongside breadcrumb
+         * page actions controls.
+         */
+        Titles,
+
+        /**
+         * @brief Each page will be shown as a tab button inside the tab bar.
+         * @deprecated This implementation in Kirigami.PageRow will be removed in
+         * KF6 (this enum value might be removed too).
+         */
+        TabBar,
+
+        /**
+         * @brief Each page will show its title at the top together with action buttons and menus
+         * that represent global and current pages actions.
+         *
+         * org::kde::kirigami::PageRow does not implement this mode for mobile formfactor devices.
+         */
+        ToolBar,
+
+        /**
+         * @brief Do not display the global toolbar.
+         *
+         * The global drawer handle will be shown at the bottom left corner of the application
+         * alongside breadcrumb controls.
+         */
+        None,
+    };
+    Q_ENUM(Status)
+
+    /**
+     * @brief Flags for implementations using navigation buttons indicating
+     * which buttons to display.
+     */
+    enum NavigationButton {
+        /**
+         * @brief Display no navigation buttons.
+         */
+        NoNavigationButtons = 0,
+
+        /**
+         * @brief Display the back navigation button.
+         */
+        ShowBackButton = 0x1,
+
+        /**
+         * @brief Display the forward navigation button.
+         */
+        ShowForwardButton = 0x2,
+    };
+    Q_ENUM(NavigationButton)
+    Q_DECLARE_FLAGS(NavigationButtons, NavigationButton)
+};
+
+/**
+ * @brief Types for implementations using messages indicating preference
+ * about how to display the message (e.g. color).
+ */
+class MessageType : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum Type {
+        /**
+         * @brief Display an informative message to the user.
+         *
+         * Use this to announce a result or provide commentary.
+         */
+        Information = 0,
+
+        /**
+         * @brief Display a positive message to the user.
+         *
+         * Use this to announce a successful result
+         * or the successful completion of a procedure.
+         */
+        Positive,
+
+        /**
+         * @brief Display a warning message to the user.
+         *
+         * Use this to provide critical guidance or
+         * a warning about something that is not going to work.
+         */
+        Warning,
+
+        /**
+         * @brief Display an error message to the user.
+         *
+         * Use this to announce something has gone wrong
+         * or that input will not be accepted.
+         */
+        Error,
+    };
+    Q_ENUM(Type)
+};
+
+/**
+ * @brief This enum contains hints on how a @link Kirigami.Action Kirigami.Action @endlink should be displayed.
+ * @note Implementations may choose to disregard the set hint.
+ */
+class DisplayHint : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum Hint : uint {
+        /**
+         * @brief No specific preference on how to display this Action.
+         */
+        NoPreference = 0,
+
+        /**
+         * @brief Only display an icon for this Action.
+         */
+        IconOnly = 1,
+
+        /**
+         * @brief Try to keep the Action visible even with constrained space.
+         *
+         * Mutually exclusive with AlwaysHide, KeepVisible has priority.
+         */
+        KeepVisible = 2,
+
+        /**
+         * @brief If possible, hide the action in an overflow menu or similar
+         * location.
+         *
+         * Mutually exclusive with KeepVisible, KeepVisible has priority.
+         */
+        AlwaysHide = 4,
+
+        /**
+         * @brief When this action has children, do not display any indicator
+         * (like a menu arrow) for this action.
+         */
+        HideChildIndicator = 8,
+    };
+    Q_DECLARE_FLAGS(DisplayHints, Hint)
+    Q_ENUM(Hint)
+    Q_FLAG(DisplayHints)
+
+    // Note: These functions are instance methods because they need to be
+    // exposed to QML. Unfortunately static methods are not supported.
+
+    /**
+     * @brief A helper function to check if a certain display hint has been set.
+     *
+     * This function is mostly convenience to enforce certain behaviour of the
+     * various display hints, primarily the mutual exclusivity of ::KeepVisible
+     * and ::AlwaysHide.
+     *
+     * @param values The display hints to check.
+     * @param hint The display hint to check if it is set.
+     *
+     * @return @c true if the hint was set for this action, @c false if not.
+     *
+     * @since org.kde.kirigami 2.14
+     */
+    Q_INVOKABLE bool displayHintSet(DisplayHints values, Hint hint);
+
+    /**
+     * @brief Check if a certain display hint has been set on an object.
+     *
+     * This overloads displayHintSet(DisplayHints, Hint) to accept a QObject
+     * instance. This object is checked to see if it has a ``displayHint`` property
+     * and if so, if that property has a @p hint set.
+     *
+     * @param object The object to check.
+     * @param hint The hint to check for.
+     *
+     * @return @c false if object is null, object has no displayHint property or
+     * the hint was not set. @c true if it has the property and the hint
+     * is set.
+     */
+    Q_INVOKABLE bool displayHintSet(QObject *object, Hint hint);
+
+    /**
+     * Static version of displayHintSet(DisplayHints, Hint) that can be
+     * called from C++ code.
+     */
+    static bool isDisplayHintSet(DisplayHints values, Hint hint);
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(DisplayHint::DisplayHints)
+
+#endif // ENUMS_H
diff --git a/src/formlayoutattached.cpp b/src/formlayoutattached.cpp
new file mode 100644 (file)
index 0000000..f165a75
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "formlayoutattached.h"
+#include <QDebug>
+#include <QQuickItem>
+
+FormLayoutAttached::FormLayoutAttached(QObject *parent)
+    : QObject(parent)
+{
+    m_buddyFor = qobject_cast<QQuickItem *>(parent);
+}
+
+FormLayoutAttached::~FormLayoutAttached()
+{
+}
+
+void FormLayoutAttached::setLabel(const QString &text)
+{
+    if (m_label == text) {
+        return;
+    }
+
+    m_label = text;
+    Q_EMIT labelChanged();
+}
+
+QString FormLayoutAttached::label() const
+{
+    return m_label;
+}
+
+void FormLayoutAttached::setLabelAlignment(int alignment)
+{
+    if (m_labelAlignment == alignment) {
+        return;
+    }
+
+    m_labelAlignment = alignment;
+    Q_EMIT labelAlignmentChanged();
+}
+
+int FormLayoutAttached::labelAlignment() const
+{
+    return m_labelAlignment;
+}
+
+void FormLayoutAttached::setIsSection(bool section)
+{
+    if (m_isSection == section) {
+        return;
+    }
+
+    m_isSection = section;
+    Q_EMIT isSectionChanged();
+}
+
+bool FormLayoutAttached::isSection() const
+{
+    return m_isSection;
+}
+
+void FormLayoutAttached::setCheckable(bool checkable)
+{
+    if (checkable == m_checkable) {
+        return;
+    }
+
+    m_checkable = checkable;
+    Q_EMIT checkableChanged();
+}
+
+bool FormLayoutAttached::checkable() const
+{
+    return m_checkable;
+}
+
+void FormLayoutAttached::setChecked(bool checked)
+{
+    if (checked == m_checked) {
+        return;
+    }
+
+    m_checked = checked;
+    Q_EMIT checkedChanged();
+}
+
+bool FormLayoutAttached::checked() const
+{
+    return m_checked;
+}
+
+void FormLayoutAttached::setEnabled(bool enabled)
+{
+    if (enabled == m_enabled) {
+        return;
+    }
+
+    m_enabled = enabled;
+    Q_EMIT enabledChanged();
+}
+
+bool FormLayoutAttached::enabled() const
+{
+    return m_enabled;
+}
+
+QQuickItem *FormLayoutAttached::buddyFor() const
+{
+    return m_buddyFor;
+}
+
+void FormLayoutAttached::setBuddyFor(QQuickItem *buddyfor)
+{
+    if (m_buddyFor == buddyfor || !m_buddyFor->isAncestorOf(buddyfor)) {
+        return;
+    }
+
+    m_buddyFor = buddyfor;
+    Q_EMIT buddyForChanged();
+}
+
+FormLayoutAttached *FormLayoutAttached::qmlAttachedProperties(QObject *object)
+{
+    return new FormLayoutAttached(object);
+}
+
+#include "moc_formlayoutattached.cpp"
diff --git a/src/formlayoutattached.h b/src/formlayoutattached.h
new file mode 100644 (file)
index 0000000..1cfefe9
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef FORMLAYOUTATTACHED_H
+#define FORMLAYOUTATTACHED_H
+
+#include <QObject>
+#include <QtQml>
+
+class QQuickItem;
+
+/**
+ * @brief This attached property contains the information for decorating a FormLayout:
+ *
+ * It contains the text labels of fields and information about sections.
+ *
+ * Some of its properties can be used with other QtQuick.Layouts.Layout types.
+ *
+ * Example usage:
+ * @include formlayout.qml
+ *
+ * @see org::kde::kirigami::FormLayout
+ * @since org.kde.kirigami 2.3
+ */
+class FormLayoutAttached : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the text for the field's label.
+     */
+    Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
+
+    /**
+     * @brief This property holds the alignment for the field's label.
+     */
+    Q_PROPERTY(int labelAlignment READ labelAlignment WRITE setLabelAlignment NOTIFY labelAlignmentChanged)
+
+    /**
+     * @brief This property sets whether this field acts as a section separator.
+     *
+     * default: ``false``
+     *
+     * You can use it in the following ways:
+     * * As space between two fields:
+     * @code
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label:"
+     * }
+     * Item {
+     *     Kirigami.FormData.isSection: true
+     * }
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label:"
+     * }
+     * @endcode
+     *
+     * * As space with a section title:
+     * @code
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label:"
+     * }
+     * Item {
+     *     Kirigami.FormData.label: "Section Title"
+     *     Kirigami.FormData.isSection: true
+     * }
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label text"
+     * }
+     * @endcode
+     *
+     * * As space with a section title and a separator line:
+     * @code
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label:"
+     * }
+     * Kirigami.Separator {
+     *     Kirigami.FormData.label: "Section Title"
+     *     Kirigami.FormData.isSection: true
+     * }
+     * QQC2.TextField {
+     *     Kirigami.FormData.label: "Label:"
+     * }
+     * @endcode
+     *
+     * @see org::kde::kirigami::FormLayout
+     */
+    Q_PROPERTY(bool isSection READ isSection WRITE setIsSection NOTIFY isSectionChanged)
+
+    /**
+     * @brief This property sets whether a checkbox should be added before the field's item.
+     *
+     * default: ``false``
+     */
+    Q_PROPERTY(bool checkable READ checkable WRITE setCheckable NOTIFY checkableChanged)
+
+    /**
+     * @brief This property sets whether the checkbox created by the ::checkable
+     * property should be checked.
+     *
+     * default: ``false``
+     *
+     * @see ::checkable
+     */
+    Q_PROPERTY(bool checked READ checked WRITE setChecked NOTIFY checkedChanged)
+
+    /**
+     * @brief This property sets whether the label or checkbox created by the
+     * FormLayout attached property should be enabled.
+     *
+     * default: ``true``
+     */
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+    /**
+     * This property can only be used
+     * in conjunction with a Kirigami.FormData.label,
+     * often in a layout that is a child of a FormLayout.
+     *
+     * It then turns the item specified into a "buddy"
+     * of the label, making it work as if it were
+     * a child of the org::kde::kirigami::FormLayout.
+     *
+     * A buddy item is useful for instance when the label has a keyboard accelerator,
+     * which when triggered provides active keyboard focus to the buddy item.
+     *
+     * @code
+     * Kirigami.FormLayout {
+     *     ColumnLayout {
+     *         // If the accelerator is in the letter S,
+     *         // pressing Alt+S gives focus to the slider.
+     *         Kirigami.FormData.label: "Slider label:"
+     *         Kirigami.FormData.buddyFor: slider
+     *
+     *         QQC2.Slider {
+     *             id: slider
+     *             from: 0
+     *             to: 100
+     *             value: 50
+     *         }
+     *     }
+     * }
+     * @endcode
+     */
+    Q_PROPERTY(QQuickItem *buddyFor READ buddyFor WRITE setBuddyFor NOTIFY buddyForChanged)
+
+public:
+    explicit FormLayoutAttached(QObject *parent = nullptr);
+    ~FormLayoutAttached() override;
+
+    void setLabel(const QString &text);
+    QString label() const;
+
+    void setIsSection(bool section);
+    bool isSection() const;
+
+    void setCheckable(bool checkable);
+    bool checkable() const;
+
+    void setChecked(bool checked);
+    bool checked() const;
+
+    void setEnabled(bool enabled);
+    bool enabled() const;
+
+    QQuickItem *buddyFor() const;
+    void setBuddyFor(QQuickItem *buddyfor);
+
+    int labelAlignment() const;
+    void setLabelAlignment(int alignment);
+
+    // QML attached property
+    static FormLayoutAttached *qmlAttachedProperties(QObject *object);
+
+Q_SIGNALS:
+    void labelChanged();
+    void isSectionChanged();
+    void checkableChanged();
+    void checkedChanged();
+    void enabledChanged();
+    void buddyForChanged();
+    void labelAlignmentChanged();
+
+private:
+    QString m_label;
+    QString m_actualDecoratedLabel;
+    QString m_decoratedLabel;
+    QPointer<QQuickItem> m_buddyFor;
+    bool m_isSection = false;
+    bool m_checkable = false;
+    bool m_checked = false;
+    bool m_enabled = true;
+    int m_labelAlignment = 0;
+};
+
+QML_DECLARE_TYPEINFO(FormLayoutAttached, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // FORMLAYOUTATTACHED_H
diff --git a/src/icon.cpp b/src/icon.cpp
new file mode 100644 (file)
index 0000000..2236f61
--- /dev/null
@@ -0,0 +1,643 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "icon.h"
+#include "libkirigami/platformtheme.h"
+#include "scenegraph/managedtexturenode.h"
+
+#include "loggingcategory.h"
+#include <QBitmap>
+#include <QDebug>
+#include <QGuiApplication>
+#include <QIcon>
+#include <QPainter>
+#include <QQuickImageProvider>
+#include <QQuickWindow>
+#include <QSGSimpleTextureNode>
+#include <QSGTexture>
+#include <QScreen>
+#include <QtQml>
+
+Q_GLOBAL_STATIC(ImageTexturesCache, s_iconImageCache)
+
+Icon::Icon(QQuickItem *parent)
+    : QQuickItem(parent)
+    , m_changed(false)
+    , m_active(false)
+    , m_selected(false)
+    , m_isMask(false)
+{
+    setFlag(ItemHasContents, true);
+    // Using 32 because Icon used to redefine implicitWidth and implicitHeight and hardcode them to 32
+    setImplicitSize(32, 32);
+    // FIXME: not necessary anymore
+    connect(qApp, &QGuiApplication::paletteChanged, this, &QQuickItem::polish);
+    connect(this, &QQuickItem::enabledChanged, this, &QQuickItem::polish);
+    connect(this, &QQuickItem::smoothChanged, this, &QQuickItem::polish);
+}
+
+Icon::~Icon()
+{
+}
+
+void Icon::setSource(const QVariant &icon)
+{
+    if (m_source == icon) {
+        return;
+    }
+    m_source = icon;
+    m_monochromeHeuristics.clear();
+
+    if (!m_theme) {
+        m_theme = static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(this, true));
+        Q_ASSERT(m_theme);
+
+        connect(m_theme, &Kirigami::PlatformTheme::colorsChanged, this, &QQuickItem::polish);
+    }
+
+    if (icon.type() == QVariant::String) {
+        const QString iconSource = icon.toString();
+        updateIsMaskHeuristic(iconSource);
+        Q_EMIT isMaskChanged();
+    }
+
+    if (m_networkReply) {
+        // if there was a network query going on, interrupt it
+        m_networkReply->close();
+    }
+    m_loadedImage = QImage();
+    setStatus(Loading);
+
+    polish();
+    Q_EMIT sourceChanged();
+    Q_EMIT validChanged();
+}
+
+QVariant Icon::source() const
+{
+    return m_source;
+}
+
+void Icon::setActive(const bool active)
+{
+    if (active == m_active) {
+        return;
+    }
+    m_active = active;
+    polish();
+    Q_EMIT activeChanged();
+}
+
+bool Icon::active() const
+{
+    return m_active;
+}
+
+bool Icon::valid() const
+{
+    // TODO: should this be return m_status == Ready?
+    // Consider an empty URL invalid, even though isNull() will say false
+    if (m_source.canConvert<QUrl>() && m_source.toUrl().isEmpty()) {
+        return false;
+    }
+
+    return !m_source.isNull();
+}
+
+void Icon::setSelected(const bool selected)
+{
+    if (selected == m_selected) {
+        return;
+    }
+    m_selected = selected;
+    polish();
+    Q_EMIT selectedChanged();
+}
+
+bool Icon::selected() const
+{
+    return m_selected;
+}
+
+void Icon::setIsMask(bool mask)
+{
+    if (m_isMask == mask) {
+        return;
+    }
+
+    m_isMask = mask;
+    m_isMaskHeuristic = mask;
+    polish();
+    Q_EMIT isMaskChanged();
+}
+
+bool Icon::isMask() const
+{
+    return m_isMask || m_isMaskHeuristic;
+}
+
+void Icon::setColor(const QColor &color)
+{
+    if (m_color == color) {
+        return;
+    }
+
+    m_color = color;
+    polish();
+    Q_EMIT colorChanged();
+}
+
+QColor Icon::color() const
+{
+    return m_color;
+}
+
+QSGNode *Icon::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData * /*data*/)
+{
+    if (m_source.isNull() || qFuzzyIsNull(width()) || qFuzzyIsNull(height())) {
+        delete node;
+        return Q_NULLPTR;
+    }
+
+    if (m_changed || node == nullptr) {
+        const QSize itemSize(width(), height());
+        QRect nodeRect(QPoint(0, 0), itemSize);
+
+        ManagedTextureNode *mNode = dynamic_cast<ManagedTextureNode *>(node);
+        if (!mNode) {
+            delete node;
+            mNode = new ManagedTextureNode;
+        }
+        if (itemSize.width() != 0 && itemSize.height() != 0) {
+            mNode->setTexture(s_iconImageCache->loadTexture(window(), m_icon, QQuickWindow::TextureCanUseAtlas));
+            if (m_icon.size() != itemSize) {
+                // At this point, the image will already be scaled, but we need to output it in
+                // the correct aspect ratio, painted centered in the viewport. So:
+                QRect destination(QPoint(0, 0), m_icon.size().scaled(itemSize, Qt::KeepAspectRatio));
+                destination.moveCenter(nodeRect.center());
+                nodeRect = destination;
+            }
+        }
+        mNode->setRect(nodeRect);
+        node = mNode;
+        if (smooth()) {
+            mNode->setFiltering(QSGTexture::Linear);
+        }
+        m_changed = false;
+    }
+
+    return node;
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void Icon::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+    QQuickItem::geometryChanged(newGeometry, oldGeometry);
+#else
+void Icon::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+    QQuickItem::geometryChange(newGeometry, oldGeometry);
+#endif
+    if (newGeometry.size() != oldGeometry.size()) {
+        polish();
+    }
+}
+
+void Icon::handleRedirect(QNetworkReply *reply)
+{
+    QNetworkAccessManager *qnam = reply->manager();
+    if (reply->error() != QNetworkReply::NoError) {
+        return;
+    }
+    const QUrl possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+    if (!possibleRedirectUrl.isEmpty()) {
+        const QUrl redirectUrl = reply->url().resolved(possibleRedirectUrl);
+        if (redirectUrl == reply->url()) {
+            // no infinite redirections thank you very much
+            reply->deleteLater();
+            return;
+        }
+        reply->deleteLater();
+        QNetworkRequest request(possibleRedirectUrl);
+        request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
+        m_networkReply = qnam->get(request);
+        connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() {
+            handleFinished(m_networkReply);
+        });
+    }
+}
+
+void Icon::handleFinished(QNetworkReply *reply)
+{
+    if (!reply) {
+        return;
+    }
+
+    reply->deleteLater();
+    if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull()) {
+        handleRedirect(reply);
+        return;
+    }
+
+    m_loadedImage = QImage();
+
+    const QString filename = reply->url().fileName();
+    if (!m_loadedImage.load(reply, filename.mid(filename.indexOf(QLatin1Char('.'))).toLatin1().constData())) {
+        qCWarning(KirigamiLog) << "received broken image" << reply->url();
+
+        // broken image from data, inform the user of this with some useful broken-image thing...
+        const QIcon icon = QIcon::fromTheme(m_fallback);
+        m_loadedImage = icon.pixmap(window(), icon.actualSize(size().toSize()), iconMode(), QIcon::On).toImage();
+    }
+
+    polish();
+}
+
+void Icon::updatePolish()
+{
+    QQuickItem::updatePolish();
+
+    if (m_source.isNull()) {
+        setStatus(Ready);
+        updatePaintedGeometry();
+        update();
+        return;
+    }
+
+    const QSize itemSize(width(), height());
+    if (itemSize.width() != 0 && itemSize.height() != 0) {
+        const auto multiplier = QCoreApplication::instance()->testAttribute(Qt::AA_UseHighDpiPixmaps)
+            ? 1
+            : (window() ? window()->effectiveDevicePixelRatio() : qGuiApp->devicePixelRatio());
+        const QSize size = itemSize * multiplier;
+
+        switch (m_source.type()) {
+        case QVariant::Pixmap:
+            m_icon = m_source.value<QPixmap>().toImage();
+            break;
+        case QVariant::Image:
+            m_icon = m_source.value<QImage>();
+            break;
+        case QVariant::Bitmap:
+            m_icon = m_source.value<QBitmap>().toImage();
+            break;
+        case QVariant::Icon: {
+            const QIcon icon = m_source.value<QIcon>();
+            m_icon = icon.pixmap(window(), icon.actualSize(itemSize), iconMode(), QIcon::On).toImage();
+            break;
+        }
+        case QVariant::Url:
+        case QVariant::String:
+            m_icon = findIcon(size);
+            break;
+        case QVariant::Brush:
+            // todo: fill here too?
+        case QVariant::Color:
+            m_icon = QImage(size, QImage::Format_Alpha8);
+            m_icon.fill(m_source.value<QColor>());
+            break;
+        default:
+            break;
+        }
+
+        if (m_icon.isNull()) {
+            m_icon = QImage(size, QImage::Format_Alpha8);
+            m_icon.fill(Qt::transparent);
+        }
+
+        const QColor tintColor = //
+            !m_color.isValid() || m_color == Qt::transparent //
+            ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor())
+            : m_color;
+
+        // TODO: initialize m_isMask with icon.isMask()
+        if (tintColor.alpha() > 0 && (isMask() || guessMonochrome(m_icon))) {
+            QPainter p(&m_icon);
+            p.setCompositionMode(QPainter::CompositionMode_SourceIn);
+            p.fillRect(m_icon.rect(), tintColor);
+            p.end();
+        }
+    }
+    m_changed = true;
+    updatePaintedGeometry();
+    update();
+}
+
+QImage Icon::findIcon(const QSize &size)
+{
+    QImage img;
+    QString iconSource = m_source.toString();
+
+    if (iconSource.startsWith(QLatin1String("image://"))) {
+        const auto multiplier = QCoreApplication::instance()->testAttribute(Qt::AA_UseHighDpiPixmaps)
+            ? (window() ? window()->effectiveDevicePixelRatio() : qGuiApp->devicePixelRatio())
+            : 1;
+        QUrl iconUrl(iconSource);
+        QString iconProviderId = iconUrl.host();
+        // QUrl path has the  "/" prefix while iconId does not
+        QString iconId = iconUrl.path().remove(0, 1);
+
+        QSize actualSize;
+        QQuickImageProvider *imageProvider = dynamic_cast<QQuickImageProvider *>(qmlEngine(this)->imageProvider(iconProviderId));
+        if (!imageProvider) {
+            return img;
+        }
+        switch (imageProvider->imageType()) {
+        case QQmlImageProviderBase::Image:
+            img = imageProvider->requestImage(iconId, &actualSize, size * multiplier);
+            if (!img.isNull()) {
+                setStatus(Ready);
+            }
+            break;
+        case QQmlImageProviderBase::Pixmap:
+            img = imageProvider->requestPixmap(iconId, &actualSize, size * multiplier).toImage();
+            if (!img.isNull()) {
+                setStatus(Ready);
+            }
+            break;
+        case QQmlImageProviderBase::ImageResponse: {
+            if (!m_loadedImage.isNull()) {
+                setStatus(Ready);
+                return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
+            }
+            QQuickAsyncImageProvider *provider = dynamic_cast<QQuickAsyncImageProvider *>(imageProvider);
+            auto response = provider->requestImageResponse(iconId, size * multiplier);
+            connect(response, &QQuickImageResponse::finished, this, [iconId, response, this]() {
+                if (response->errorString().isEmpty()) {
+                    QQuickTextureFactory *textureFactory = response->textureFactory();
+                    if (textureFactory) {
+                        m_loadedImage = textureFactory->image();
+                        delete textureFactory;
+                    }
+                    if (m_loadedImage.isNull()) {
+                        // broken image from data, inform the user of this with some useful broken-image thing...
+                        const QIcon icon = QIcon::fromTheme(m_fallback);
+                        m_loadedImage = icon.pixmap(window(), icon.actualSize(QSize(width(), height())), iconMode(), QIcon::On).toImage();
+                        setStatus(Error);
+                    } else {
+                        setStatus(Ready);
+                    }
+                    polish();
+                }
+                response->deleteLater();
+            });
+            // Temporary icon while we wait for the real image to load...
+            const QIcon icon = QIcon::fromTheme(m_placeholder);
+            img = icon.pixmap(window(), icon.actualSize(size), iconMode(), QIcon::On).toImage();
+            break;
+        }
+        case QQmlImageProviderBase::Texture: {
+            QQuickTextureFactory *textureFactory = imageProvider->requestTexture(iconId, &actualSize, size * multiplier);
+            if (textureFactory) {
+                img = textureFactory->image();
+            }
+            if (img.isNull()) {
+                // broken image from data, or the texture factory wasn't healthy, inform the user of this with some useful broken-image thing...
+                const QIcon icon = QIcon::fromTheme(m_fallback);
+                img = icon.pixmap(window(), icon.actualSize(QSize(width(), height())), iconMode(), QIcon::On).toImage();
+                setStatus(Error);
+            } else {
+                setStatus(Ready);
+            }
+            break;
+        }
+        case QQmlImageProviderBase::Invalid:
+            // will have to investigate this more
+            setStatus(Error);
+            break;
+        }
+    } else if (iconSource.startsWith(QLatin1String("http://")) || iconSource.startsWith(QLatin1String("https://"))) {
+        if (!m_loadedImage.isNull()) {
+            setStatus(Ready);
+            return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
+        }
+        const auto url = m_source.toUrl();
+        QQmlEngine *engine = qmlEngine(this);
+        QNetworkAccessManager *qnam;
+        if (engine && (qnam = engine->networkAccessManager()) && (!m_networkReply || m_networkReply->url() != url)) {
+            QNetworkRequest request(url);
+            request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
+            m_networkReply = qnam->get(request);
+            connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() {
+                handleFinished(m_networkReply);
+            });
+        }
+        // Temporary icon while we wait for the real image to load...
+        const QIcon icon = QIcon::fromTheme(m_placeholder);
+        img = icon.pixmap(window(), icon.actualSize(size), iconMode(), QIcon::On).toImage();
+    } else {
+        if (iconSource.startsWith(QLatin1String("qrc:/"))) {
+            iconSource = iconSource.mid(3);
+        } else if (iconSource.startsWith(QLatin1String("file:/"))) {
+            iconSource = QUrl(iconSource).path();
+        }
+
+        QIcon icon;
+        const bool isPath = iconSource.contains(QLatin1String("/"));
+        if (isPath) {
+            icon = QIcon(iconSource);
+        } else {
+            if (icon.isNull()) {
+                icon = m_theme->iconFromTheme(iconSource, m_color);
+                if (m_isMaskHeuristic && icon.name() != iconSource) {
+                    updateIsMaskHeuristic(icon.name());
+                    if (!m_isMaskHeuristic) {
+                        Q_EMIT isMaskChanged();
+                    }
+                }
+            }
+        }
+        if (!icon.isNull()) {
+            img = icon.pixmap(window(), icon.actualSize(window(), size), iconMode(), QIcon::On).toImage();
+
+            setStatus(Ready);
+            /*const QColor tintColor = !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor())
+            : m_color;
+
+            if (m_isMask || icon.isMask() || iconSource.endsWith(QLatin1String("-symbolic")) || iconSource.endsWith(QLatin1String("-symbolic-rtl")) ||
+            iconSource.endsWith(QLatin1String("-symbolic-ltr")) || guessMonochrome(img)) { //
+                QPainter p(&img);
+                p.setCompositionMode(QPainter::CompositionMode_SourceIn);
+                p.fillRect(img.rect(), tintColor);
+                p.end();
+            }*/
+        }
+    }
+
+    if (!iconSource.isEmpty() && img.isNull()) {
+        setStatus(Error);
+        const QIcon icon = QIcon::fromTheme(m_fallback);
+        img = icon.pixmap(window(), icon.actualSize(size), iconMode(), QIcon::On).toImage();
+    }
+    return img;
+}
+
+QIcon::Mode Icon::iconMode() const
+{
+    if (!isEnabled()) {
+        return QIcon::Disabled;
+    } else if (m_selected) {
+        return QIcon::Selected;
+    } else if (m_active) {
+        return QIcon::Active;
+    }
+    return QIcon::Normal;
+}
+
+bool Icon::guessMonochrome(const QImage &img)
+{
+    // don't try for too big images
+    if (img.width() >= 256 || m_theme->supportsIconColoring()) {
+        return false;
+    }
+    // round size to a standard size. hardcode as we can't use KIconLoader
+    int stdSize;
+    if (img.width() <= 16) {
+        stdSize = 16;
+    } else if (img.width() <= 22) {
+        stdSize = 22;
+    } else if (img.width() <= 24) {
+        stdSize = 24;
+    } else if (img.width() <= 32) {
+        stdSize = 32;
+    } else if (img.width() <= 48) {
+        stdSize = 48;
+    } else if (img.width() <= 64) {
+        stdSize = 64;
+    } else {
+        stdSize = 128;
+    }
+
+    auto findIt = m_monochromeHeuristics.constFind(stdSize);
+    if (findIt != m_monochromeHeuristics.constEnd()) {
+        return findIt.value();
+    }
+
+    QHash<int, int> dist;
+    int transparentPixels = 0;
+    int saturatedPixels = 0;
+    for (int x = 0; x < img.width(); x++) {
+        for (int y = 0; y < img.height(); y++) {
+            QColor color = QColor::fromRgba(qUnpremultiply(img.pixel(x, y)));
+            if (color.alpha() < 100) {
+                ++transparentPixels;
+                continue;
+            } else if (color.saturation() > 84) {
+                ++saturatedPixels;
+            }
+            dist[qGray(color.rgb())]++;
+        }
+    }
+
+    QMultiMap<int, int> reverseDist;
+    auto it = dist.constBegin();
+    qreal entropy = 0;
+    while (it != dist.constEnd()) {
+        reverseDist.insert(it.value(), it.key());
+        qreal probability = qreal(it.value()) / qreal(img.size().width() * img.size().height() - transparentPixels);
+        entropy -= probability * log(probability) / log(255);
+        ++it;
+    }
+
+    // Arbitrarily low values of entropy and colored pixels
+    m_monochromeHeuristics[stdSize] = saturatedPixels <= (img.size().width() * img.size().height() - transparentPixels) * 0.3 && entropy <= 0.3;
+    return m_monochromeHeuristics[stdSize];
+}
+
+QString Icon::fallback() const
+{
+    return m_fallback;
+}
+
+void Icon::setFallback(const QString &fallback)
+{
+    if (m_fallback != fallback) {
+        m_fallback = fallback;
+        Q_EMIT fallbackChanged(fallback);
+    }
+}
+
+QString Icon::placeholder() const
+{
+    return m_placeholder;
+}
+
+void Icon::setPlaceholder(const QString &placeholder)
+{
+    if (m_placeholder != placeholder) {
+        m_placeholder = placeholder;
+        Q_EMIT placeholderChanged(placeholder);
+    }
+}
+
+void Icon::setStatus(Status status)
+{
+    if (status == m_status) {
+        return;
+    }
+
+    m_status = status;
+    Q_EMIT statusChanged();
+}
+
+Icon::Status Icon::status() const
+{
+    return m_status;
+}
+
+qreal Icon::paintedWidth() const
+{
+    return m_paintedWidth;
+}
+
+qreal Icon::paintedHeight() const
+{
+    return m_paintedHeight;
+}
+
+void Icon::updatePaintedGeometry()
+{
+    qreal newWidth = 0.0;
+    qreal newHeight = 0.0;
+    if (!m_icon.width() || !m_icon.height()) {
+        newWidth = newHeight = 0.0;
+    } else {
+        const qreal w = widthValid() ? width() : m_icon.size().width();
+        const qreal widthScale = w / m_icon.size().width();
+        const qreal h = heightValid() ? height() : m_icon.size().height();
+        const qreal heightScale = h / m_icon.size().height();
+        if (widthScale <= heightScale) {
+            newWidth = w;
+            newHeight = widthScale * m_icon.size().height();
+        } else if (heightScale < widthScale) {
+            newWidth = heightScale * m_icon.size().width();
+            newHeight = h;
+        }
+    }
+    if (newWidth != m_paintedWidth || newHeight != m_paintedHeight) {
+        m_paintedWidth = newWidth;
+        m_paintedHeight = newHeight;
+        Q_EMIT paintedAreaChanged();
+    }
+}
+
+void Icon::updateIsMaskHeuristic(const QString &iconSource)
+{
+    m_isMaskHeuristic = (iconSource.endsWith(QLatin1String("-symbolic")) //
+                         || iconSource.endsWith(QLatin1String("-symbolic-rtl")) //
+                         || iconSource.endsWith(QLatin1String("-symbolic-ltr")));
+}
+
+void Icon::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
+{
+    if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
+        polish();
+    }
+    QQuickItem::itemChange(change, value);
+}
+
+#include "moc_icon.cpp"
diff --git a/src/icon.h b/src/icon.h
new file mode 100644 (file)
index 0000000..4b06d47
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QIcon>
+#include <QPointer>
+#include <QQuickItem>
+#include <QVariant>
+
+class QNetworkReply;
+
+namespace Kirigami
+{
+class PlatformTheme;
+}
+
+/**
+ * Class for rendering an icon in UI.
+ */
+class Icon : public QQuickItem
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the source of this icon.
+     *
+     * The icon can be pulled from:
+     * * The Freedesktop standard icon name:
+     * @include icon/IconThemeSource.qml
+     * * The filesystem:
+     * @include icon/FilesystemSource.qml
+     * * Remote URIs:
+     * @include icon/InternetSource.qml
+     * * Custom providers:
+     * @include icon/CustomSource.qml
+     * * Your application's bundled resources:
+     * @include icon/ResourceSource.qml
+     *
+     * @note See https://doc.qt.io/qt-5/qtquickcontrols2-icons.html for how to
+     * bundle icon themes in your application to refer to them by name instead of
+     * by resource URL.
+     *
+     * @note Use `fallback` to provide a fallback theme name for icons.
+     *
+     * @note Cuttlefish is a KDE application that lets you view all the icons that
+     * you can use for your application. It offers a number of useful features such
+     * as previews of their appearance across different installed themes, previews
+     * at different sizes, and more. You might find it a useful tool when deciding
+     * on which icons to use in your application.
+     */
+    Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged)
+
+    /**
+     * @brief This property holds the name of an icon from the icon theme
+     * as a fallback for when an icon set with the ``source`` property is not found.
+     *
+     * @include icon/Fallback.qml
+     * @note This will only be loaded if source is unavailable (e.g. it doesn't exist, or network issues have prevented loading).
+     */
+    Q_PROPERTY(QString fallback READ fallback WRITE setFallback NOTIFY fallbackChanged)
+
+    /**
+     * @brief This property holds the name of an icon from the icon theme to show
+     * while the icon set in `source` is being loaded.
+     *
+     * This will only be used if the source image is a type that can have such a
+     * long loading time that showing a temporary image in its place makes sense
+     * (e.g. a remote image, or an image from an ImageProvider of the type
+     * QtQml.QQmlImageProviderBase.ImageResponse).
+     *
+     * default: ``"image-png"``
+     *
+     * @since KDE Frameworks 5.15
+     */
+    Q_PROPERTY(QString placeholder READ placeholder WRITE setPlaceholder NOTIFY placeholderChanged)
+
+    /**
+     * @brief This property sets whether the icon will use the
+     * @link QtGui.QIcon.Mode QIcon.Active @endlink mode,
+     * resulting in a graphical effect being applied when the icon is currently active.
+     *
+     * @note This is typically used to indicate when an item is being hovered or pressed.
+     *
+     * @image html icon/active.png
+     *
+     * The color differences under the default KDE color palette, Breeze. Note
+     * that a dull highlight background is typically displayed behind active icons and
+     * it is recommended to add one if you are creating a custom component.
+     *
+     * default: ``false``
+     */
+    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+
+    /**
+     * @brief This property specifies whether the icon's `source` is valid and is being used.
+     */
+    Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
+
+    /**
+     * @brief This property sets whether the icon will use the
+     * @link QtGui.QIcon.Mode QIcon.Selected @endlink mode,
+     * resulting in a graphical effect being applied when the icon is currently selected.
+     *
+     * This is typically used to indicate when a list item is currently selected.
+     *
+     * @image html icon/selected.png
+     *
+     * The color differences under the default KDE color palette, Breeze. Note
+     * that a blue background is typically displayed behind selected elements.
+     *
+     * default: ``false``
+     */
+    Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
+
+    /**
+     * @brief This property sets whether this icon will be treated as a mask.
+     *
+     * When an icon is being used as a mask, all non-transparent colors are replaced
+     * with the color provided in the Icon's ::color property.
+     *
+     * default: ``false``
+     *
+     * @see ::color
+     */
+    Q_PROPERTY(bool isMask READ isMask WRITE setIsMask NOTIFY isMaskChanged)
+
+    /**
+     * @brief This property holds the color to use when drawing the icon.
+     *
+     * This property is used only when ::isMask is set to true.
+     * If this property is not set or is `Qt::transparent`, the icon will use
+     * the text or the selected text color, depending on if ::selected is set to
+     * true.
+     *
+     * default: ``Qt::transparent``
+     * @see Qt::GlobalColor
+     */
+    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
+
+    /**
+     * @brief This property specifies the status of the icon.
+     *
+     * @note Image loading will not be initiated until the item is shown, so if the Icon is not visible,
+     * it can only have Null or Loading states.
+     *
+     * default: ``Status::Null``
+     *
+     * @since KDE Frameworks 5.15
+     */
+    Q_PROPERTY(Icon::Status status READ status NOTIFY statusChanged)
+
+    /**
+     * @brief This property holds the width of the painted area in pixels.
+     *
+     * This will be smaller than or equal to the width of the area
+     * taken up by the Item itself. This can be 0.
+     *
+     * default: ``0.0``
+     *
+     * @since KDE Frameworks 5.15
+     */
+    Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedAreaChanged)
+
+    /**
+     * @brief This property holds the height of the painted area in pixels.
+     *
+     * This will be smaller than or equal to the height of the area
+     * taken up by the Item itself. This can be 0.
+     *
+     * default: ``0.0``
+     *
+     * @since KDE Frameworks 5.15
+     */
+    Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedAreaChanged)
+public:
+    /**
+     * @brief This enum indicates the current status of the icon.
+     */
+    enum Status {
+        /**
+         * @brief No icon source has been set.
+         */
+        Null = 0,
+
+        /**
+         * @brief The icon has been loaded correctly.
+         */
+        Ready,
+
+        /**
+         * @brief The icon is currently being loaded.
+         */
+        Loading,
+
+        /**
+         * @brief There was an error while loading the icon, for instance
+         * a non existent themed name, or an invalid url.
+         */
+        Error,
+    };
+    Q_ENUM(Status)
+
+    Icon(QQuickItem *parent = nullptr);
+    ~Icon() override;
+
+    void setSource(const QVariant &source);
+    QVariant source() const;
+
+    void setActive(bool active = true);
+    bool active() const;
+
+    bool valid() const;
+
+    void setSelected(bool selected = true);
+    bool selected() const;
+
+    void setIsMask(bool mask);
+    bool isMask() const;
+
+    void setColor(const QColor &color);
+    QColor color() const;
+
+    QString fallback() const;
+    void setFallback(const QString &fallback);
+
+    QString placeholder() const;
+    void setPlaceholder(const QString &placeholder);
+
+    Status status() const;
+
+    qreal paintedWidth() const;
+    qreal paintedHeight() const;
+
+    QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override;
+
+Q_SIGNALS:
+    void sourceChanged();
+    void activeChanged();
+    void validChanged();
+    void selectedChanged();
+    void isMaskChanged();
+    void colorChanged();
+    void fallbackChanged(const QString &fallback);
+    void placeholderChanged(const QString &placeholder);
+    void statusChanged();
+    void paintedAreaChanged();
+
+protected:
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#else
+    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#endif
+    QImage findIcon(const QSize &size);
+    void handleFinished(QNetworkReply *reply);
+    void handleRedirect(QNetworkReply *reply);
+    QIcon::Mode iconMode() const;
+    bool guessMonochrome(const QImage &img);
+    void setStatus(Status status);
+    void updatePolish() override;
+    void updatePaintedGeometry();
+    void updateIsMaskHeuristic(const QString &iconSource);
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
+
+private:
+    Kirigami::PlatformTheme *m_theme = nullptr;
+    QPointer<QNetworkReply> m_networkReply;
+    QHash<int, bool> m_monochromeHeuristics;
+    QVariant m_source;
+    Status m_status = Null;
+    bool m_changed;
+    bool m_active;
+    bool m_selected;
+    bool m_isMask;
+    bool m_isMaskHeuristic = false;
+    QImage m_loadedImage;
+    QColor m_color = Qt::transparent;
+    QString m_fallback = QStringLiteral("unknown");
+    QString m_placeholder = QStringLiteral("image-png");
+    qreal m_paintedWidth = 0.0;
+    qreal m_paintedHeight = 0.0;
+
+    QImage m_icon;
+};
diff --git a/src/imagecolors.cpp b/src/imagecolors.cpp
new file mode 100644 (file)
index 0000000..9974b6a
--- /dev/null
@@ -0,0 +1,610 @@
+/*
+ *  Copyright 2020 Marco Martin <mart@kde.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  2.010-1301, USA.
+ */
+
+#include "imagecolors.h"
+#include "platformtheme.h"
+
+#include <QDebug>
+#include <QGuiApplication>
+#include <QTimer>
+#include <QtConcurrent>
+
+#include "loggingcategory.h"
+#include <cmath>
+#include <vector>
+
+#include "config-OpenMP.h"
+#if HAVE_OpenMP
+#include <omp.h>
+#endif
+
+#define return_fallback(value)                                                                                                                                 \
+    if (m_imageData.m_samples.size() == 0) {                                                                                                                   \
+        return value;                                                                                                                                          \
+    }
+
+#define return_fallback_finally(value, finally)                                                                                                                \
+    if (m_imageData.m_samples.size() == 0) {                                                                                                                   \
+        return value.isValid() ? value : static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(this, true))->finally();  \
+    }
+
+ImageColors::ImageColors(QObject *parent)
+    : QObject(parent)
+{
+    m_imageSyncTimer = new QTimer(this);
+    m_imageSyncTimer->setSingleShot(true);
+    m_imageSyncTimer->setInterval(100);
+    /* connect(m_imageSyncTimer, &QTimer::timeout, this, [this]() {
+        generatePalette();
+     });*/
+}
+
+ImageColors::~ImageColors()
+{
+}
+
+void ImageColors::setSource(const QVariant &source)
+{
+    if (m_futureSourceImageData) {
+        m_futureSourceImageData->cancel();
+        m_futureSourceImageData->deleteLater();
+        m_futureSourceImageData = nullptr;
+    }
+
+    if (source.canConvert<QQuickItem *>()) {
+        setSourceItem(source.value<QQuickItem *>());
+    } else if (source.canConvert<QImage>()) {
+        setSourceImage(source.value<QImage>());
+    } else if (source.canConvert<QIcon>()) {
+        setSourceImage(source.value<QIcon>().pixmap(128, 128).toImage());
+    } else if (source.canConvert<QString>()) {
+        const QString sourceString = source.toString();
+
+        if (QIcon::hasThemeIcon(sourceString)) {
+            setSourceImage(QIcon::fromTheme(sourceString).pixmap(128, 128).toImage());
+        } else {
+            QFuture<QImage> future = QtConcurrent::run([sourceString]() {
+                if (auto url = QUrl(sourceString); url.isLocalFile()) {
+                    return QImage(url.toLocalFile());
+                }
+                return QImage(sourceString);
+            });
+            m_futureSourceImageData = new QFutureWatcher<QImage>(this);
+            connect(m_futureSourceImageData, &QFutureWatcher<QImage>::finished, this, [this, source]() {
+                const QImage image = m_futureSourceImageData->future().result();
+                m_futureSourceImageData->deleteLater();
+                m_futureSourceImageData = nullptr;
+                setSourceImage(image);
+                m_source = source;
+                Q_EMIT sourceChanged();
+            });
+            m_futureSourceImageData->setFuture(future);
+            return;
+        }
+    } else {
+        return;
+    }
+
+    m_source = source;
+    Q_EMIT sourceChanged();
+}
+
+QVariant ImageColors::source() const
+{
+    return m_source;
+}
+
+void ImageColors::setSourceImage(const QImage &image)
+{
+    if (m_window) {
+        disconnect(m_window.data(), nullptr, this, nullptr);
+    }
+    if (m_sourceItem) {
+        disconnect(m_sourceItem.data(), nullptr, this, nullptr);
+    }
+    if (m_grabResult) {
+        disconnect(m_grabResult.data(), nullptr, this, nullptr);
+        m_grabResult.clear();
+    }
+
+    m_sourceItem.clear();
+
+    m_sourceImage = image;
+    update();
+}
+
+QImage ImageColors::sourceImage() const
+{
+    return m_sourceImage;
+}
+
+void ImageColors::setSourceItem(QQuickItem *source)
+{
+    if (m_sourceItem == source) {
+        return;
+    }
+
+    if (m_window) {
+        disconnect(m_window.data(), nullptr, this, nullptr);
+    }
+    if (m_sourceItem) {
+        disconnect(m_sourceItem, nullptr, this, nullptr);
+    }
+    m_sourceItem = source;
+    update();
+
+    if (m_sourceItem) {
+        auto syncWindow = [this]() {
+            if (m_window) {
+                disconnect(m_window.data(), nullptr, this, nullptr);
+            }
+            m_window = m_sourceItem->window();
+            if (m_window) {
+                connect(m_window, &QWindow::visibleChanged, this, &ImageColors::update);
+            }
+        };
+
+        connect(m_sourceItem, &QQuickItem::windowChanged, this, syncWindow);
+        syncWindow();
+    }
+}
+
+QQuickItem *ImageColors::sourceItem() const
+{
+    return m_sourceItem;
+}
+
+void ImageColors::update()
+{
+    if (m_futureImageData) {
+        m_futureImageData->cancel();
+        m_futureImageData->deleteLater();
+        m_futureImageData = nullptr;
+    }
+    auto runUpdate = [this]() {
+        QFuture<ImageData> future = QtConcurrent::run([this]() {
+            return generatePalette(m_sourceImage);
+        });
+        m_futureImageData = new QFutureWatcher<ImageData>(this);
+        connect(m_futureImageData, &QFutureWatcher<ImageData>::finished, this, [this]() {
+            if (!m_futureImageData) {
+                return;
+            }
+            m_imageData = m_futureImageData->future().result();
+            m_futureImageData->deleteLater();
+            m_futureImageData = nullptr;
+
+            Q_EMIT paletteChanged();
+        });
+        m_futureImageData->setFuture(future);
+    };
+
+    if (!m_sourceItem) {
+        if (!m_sourceImage.isNull()) {
+            runUpdate();
+        } else {
+            m_imageData = {};
+            Q_EMIT paletteChanged();
+        }
+        return;
+    }
+
+    if (m_grabResult) {
+        disconnect(m_grabResult.data(), nullptr, this, nullptr);
+        m_grabResult.clear();
+    }
+
+    m_grabResult = m_sourceItem->grabToImage(QSize(128, 128));
+
+    if (m_grabResult) {
+        connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this, runUpdate]() {
+            m_sourceImage = m_grabResult->image();
+            m_grabResult.clear();
+            runUpdate();
+        });
+    }
+}
+
+inline int squareDistance(QRgb color1, QRgb color2)
+{
+    // https://en.wikipedia.org/wiki/Color_difference
+    // Using RGB distance for performance, as CIEDE2000 istoo complicated
+    if (qRed(color1) - qRed(color2) < 128) {
+        return 2 * pow(qRed(color1) - qRed(color2), 2) //
+            + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
+            + 3 * pow(qBlue(color1) - qBlue(color2), 2);
+    } else {
+        return 3 * pow(qRed(color1) - qRed(color2), 2) //
+            + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
+            + 2 * pow(qBlue(color1) - qBlue(color2), 2);
+    }
+}
+
+void ImageColors::positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters)
+{
+    for (auto &stat : clusters) {
+        if (squareDistance(rgb, stat.centroid) < s_minimumSquareDistance) {
+            stat.colors.append(rgb);
+            return;
+        }
+    }
+
+    ImageData::colorStat stat;
+    stat.colors.append(rgb);
+    stat.centroid = rgb;
+    clusters << stat;
+}
+
+ImageData ImageColors::generatePalette(const QImage &sourceImage) const
+{
+    ImageData imageData;
+
+    if (sourceImage.isNull() || sourceImage.width() == 0) {
+        return imageData;
+    }
+
+    imageData.m_clusters.clear();
+    imageData.m_samples.clear();
+
+#if HAVE_OpenMP
+    static const int numCore = std::min(8, omp_get_num_procs());
+    omp_set_num_threads(numCore);
+    std::vector<decltype(imageData.m_samples)> tempSamples(numCore, decltype(imageData.m_samples){});
+#endif
+    int r = 0;
+    int g = 0;
+    int b = 0;
+    int c = 0;
+
+#pragma omp parallel for collapse(2) reduction(+ : r) reduction(+ : g) reduction(+ : b) reduction(+ : c)
+    for (int x = 0; x < sourceImage.width(); ++x) {
+        for (int y = 0; y < sourceImage.height(); ++y) {
+            const QColor sampleColor = sourceImage.pixelColor(x, y);
+            if (sampleColor.alpha() == 0) {
+                continue;
+            }
+            if (ColorUtils::chroma(sampleColor) < 20) {
+                continue;
+            }
+            QRgb rgb = sampleColor.rgb();
+            ++c;
+            r += qRed(rgb);
+            g += qGreen(rgb);
+            b += qBlue(rgb);
+#if HAVE_OpenMP
+            tempSamples[omp_get_thread_num()] << rgb;
+#else
+            imageData.m_samples << rgb;
+#endif
+        }
+    } // END omp parallel for
+
+#if HAVE_OpenMP
+    for (auto &s : tempSamples) {
+        imageData.m_samples << std::move(s);
+    }
+#endif
+
+    if (imageData.m_samples.isEmpty()) {
+        return imageData;
+    }
+
+    for (QRgb rgb : std::as_const(imageData.m_samples)) {
+        positionColor(rgb, imageData.m_clusters);
+    }
+
+    imageData.m_average = QColor(r / c, g / c, b / c, 255);
+
+    for (int iteration = 0; iteration < 5; ++iteration) {
+#pragma omp parallel for private(r, g, b, c)
+        for (int i = 0; i < imageData.m_clusters.size(); ++i) {
+            auto &stat = imageData.m_clusters[i];
+            r = 0;
+            g = 0;
+            b = 0;
+            c = 0;
+
+            for (auto color : std::as_const(stat.colors)) {
+                c++;
+                r += qRed(color);
+                g += qGreen(color);
+                b += qBlue(color);
+            }
+            r = r / c;
+            g = g / c;
+            b = b / c;
+            stat.centroid = qRgb(r, g, b);
+            stat.ratio = qreal(stat.colors.count()) / qreal(imageData.m_samples.count());
+            stat.colors = QList<QRgb>({stat.centroid});
+        } // END omp parallel for
+
+        for (auto color : std::as_const(imageData.m_samples)) {
+            positionColor(color, imageData.m_clusters);
+        }
+    }
+
+    std::sort(imageData.m_clusters.begin(), imageData.m_clusters.end(), [this](const ImageData::colorStat &a, const ImageData::colorStat &b) {
+        return getClusterScore(a) > getClusterScore(b);
+    });
+
+    // compress blocks that became too similar
+    auto sourceIt = imageData.m_clusters.end();
+    // Use index instead of iterator, because QList::erase may invalidate iterator.
+    std::vector<int> itemsToDelete;
+    while (sourceIt != imageData.m_clusters.begin()) {
+        sourceIt--;
+        for (auto destIt = imageData.m_clusters.begin(); destIt != imageData.m_clusters.end() && destIt != sourceIt; destIt++) {
+            if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) {
+                const qreal ratio = (*sourceIt).ratio / (*destIt).ratio;
+                const int r = ratio * qreal(qRed((*sourceIt).centroid)) + (1 - ratio) * qreal(qRed((*destIt).centroid));
+                const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + (1 - ratio) * qreal(qGreen((*destIt).centroid));
+                const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + (1 - ratio) * qreal(qBlue((*destIt).centroid));
+                (*destIt).ratio += (*sourceIt).ratio;
+                (*destIt).centroid = qRgb(r, g, b);
+                itemsToDelete.push_back(std::distance(imageData.m_clusters.begin(), sourceIt));
+                break;
+            }
+        }
+    }
+    for (auto i : std::as_const(itemsToDelete)) {
+        imageData.m_clusters.removeAt(i);
+    }
+
+    imageData.m_highlight = QColor();
+    imageData.m_dominant = QColor(imageData.m_clusters.first().centroid);
+    imageData.m_closestToBlack = Qt::white;
+    imageData.m_closestToWhite = Qt::black;
+
+    imageData.m_palette.clear();
+
+    bool first = true;
+
+    for (const auto &stat : std::as_const(imageData.m_clusters)) {
+        QVariantMap entry;
+        const QColor color(stat.centroid);
+        entry[QStringLiteral("color")] = color;
+        entry[QStringLiteral("ratio")] = stat.ratio;
+
+        QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
+        contrast.setHsl(contrast.hslHue(), //
+                        contrast.hslSaturation(), //
+                        128 + (128 - contrast.lightness()));
+        QColor tempContrast;
+        int minimumDistance = 4681800; // max distance: 4*3*2*3*255*255
+        for (const auto &stat : std::as_const(imageData.m_clusters)) {
+            const int distance = squareDistance(contrast.rgb(), stat.centroid);
+
+            if (distance < minimumDistance) {
+                tempContrast = QColor(stat.centroid);
+                minimumDistance = distance;
+            }
+        }
+
+        if (imageData.m_clusters.size() <= 3) {
+            if (qGray(imageData.m_dominant.rgb()) < 120) {
+                contrast = QColor(230, 230, 230);
+            } else {
+                contrast = QColor(20, 20, 20);
+            }
+            // TODO: replace m_clusters.size() > 3 with entropy calculation
+        } else if (squareDistance(contrast.rgb(), tempContrast.rgb()) < s_minimumSquareDistance * 1.5) {
+            contrast = tempContrast;
+        } else {
+            contrast = tempContrast;
+            contrast.setHsl(contrast.hslHue(),
+                            contrast.hslSaturation(),
+                            contrast.lightness() > 128 ? qMin(contrast.lightness() + 20, 255) : qMax(0, contrast.lightness() - 20));
+        }
+
+        entry[QStringLiteral("contrastColor")] = contrast;
+
+        if (first) {
+            imageData.m_dominantContrast = contrast;
+            imageData.m_dominant = color;
+        }
+        first = false;
+
+        if (!imageData.m_highlight.isValid() || ColorUtils::chroma(color) > ColorUtils::chroma(imageData.m_highlight)) {
+            imageData.m_highlight = color;
+        }
+
+        if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.rgb())) {
+            imageData.m_closestToWhite = color;
+        }
+        if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.rgb())) {
+            imageData.m_closestToBlack = color;
+        }
+        imageData.m_palette << entry;
+    }
+
+    postProcess(imageData);
+
+    return imageData;
+}
+
+double ImageColors::getClusterScore(const ImageData::colorStat &stat) const
+{
+    return stat.ratio * ColorUtils::chroma(QColor(stat.centroid));
+}
+
+void ImageColors::postProcess(ImageData &imageData) const
+{
+    constexpr short unsigned WCAG_NON_TEXT_CONTRAST_RATIO = 3;
+    constexpr qreal WCAG_TEXT_CONTRAST_RATIO = 4.5;
+    const QColor backgroundColor = static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(this, true))->backgroundColor();
+    const qreal backgroundLum = ColorUtils::luminance(backgroundColor);
+    qreal lowerLum, upperLum;
+    // 192 is from kcm_colors
+    if (qGray(backgroundColor.rgb()) < 192) {
+        // (lowerLum + 0.05) / (backgroundLum + 0.05) >= 3
+        lowerLum = WCAG_NON_TEXT_CONTRAST_RATIO * (backgroundLum + 0.05) - 0.05;
+        upperLum = 0.95;
+    } else {
+        // For light themes, still prefer lighter colors
+        // (lowerLum + 0.05) / (textLum + 0.05) >= 4.5
+        const QColor textColor = static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(this, true))->textColor();
+        const qreal textLum = ColorUtils::luminance(textColor);
+        lowerLum = WCAG_TEXT_CONTRAST_RATIO * (textLum + 0.05) - 0.05;
+        upperLum = backgroundLum;
+    }
+
+    auto adjustSaturation = [](QColor &color) {
+        // Adjust saturation to make the color more vibrant
+        if (color.hsvSaturationF() < 0.5) {
+            const qreal h = color.hsvHueF();
+            const qreal v = color.valueF();
+            color.setHsvF(h, 0.5, v);
+        }
+    };
+    adjustSaturation(imageData.m_dominant);
+    adjustSaturation(imageData.m_highlight);
+    adjustSaturation(imageData.m_average);
+
+    auto adjustLightness = [lowerLum, upperLum](QColor &color) {
+        short unsigned colorOperationCount = 0;
+        const qreal h = color.hslHueF();
+        const qreal s = color.hslSaturationF();
+        const qreal l = color.lightnessF();
+        while (ColorUtils::luminance(color.rgb()) < lowerLum && colorOperationCount++ < 10) {
+            color.setHslF(h, s, std::min(1.0, l + colorOperationCount * 0.03));
+        }
+        while (ColorUtils::luminance(color.rgb()) > upperLum && colorOperationCount++ < 10) {
+            color.setHslF(h, s, std::max(0.0, l - colorOperationCount * 0.03));
+        }
+    };
+    adjustLightness(imageData.m_dominant);
+    adjustLightness(imageData.m_highlight);
+    adjustLightness(imageData.m_average);
+}
+
+QVariantList ImageColors::palette() const
+{
+    if (m_futureImageData) {
+        qCWarning(KirigamiLog) << m_futureImageData->future().isFinished();
+    }
+    return_fallback(m_fallbackPalette) return m_imageData.m_palette;
+}
+
+ColorUtils::Brightness ImageColors::paletteBrightness() const
+{
+    /* clang-format off */
+    return_fallback(m_fallbackPaletteBrightness)
+
+    return qGray(m_imageData.m_dominant.rgb()) < 128 ? ColorUtils::Dark : ColorUtils::Light;
+    /* clang-format on */
+}
+
+QColor ImageColors::average() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackAverage, linkBackgroundColor)
+
+    return m_imageData.m_average;
+    /* clang-format on */
+}
+
+QColor ImageColors::dominant() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackDominant, linkBackgroundColor)
+
+    return m_imageData.m_dominant;
+    /* clang-format on */
+}
+
+QColor ImageColors::dominantContrast() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackDominantContrasting, linkBackgroundColor)
+
+    return m_imageData.m_dominantContrast;
+    /* clang-format on */
+}
+
+QColor ImageColors::foreground() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackForeground, textColor)
+
+    if (paletteBrightness() == ColorUtils::Dark)
+    {
+        if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
+            return QColor(230, 230, 230);
+        }
+        return m_imageData.m_closestToWhite;
+    } else {
+        if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
+            return QColor(20, 20, 20);
+        }
+        return m_imageData.m_closestToBlack;
+    }
+    /* clang-format on */
+}
+
+QColor ImageColors::background() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackBackground, backgroundColor)
+
+    if (paletteBrightness() == ColorUtils::Dark) {
+        if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
+            return QColor(20, 20, 20);
+        }
+        return m_imageData.m_closestToBlack;
+    } else {
+        if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
+            return QColor(230, 230, 230);
+        }
+        return m_imageData.m_closestToWhite;
+    }
+    /* clang-format on */
+}
+
+QColor ImageColors::highlight() const
+{
+    /* clang-format off */
+    return_fallback_finally(m_fallbackHighlight, linkColor)
+
+    return m_imageData.m_highlight;
+    /* clang-format on */
+}
+
+QColor ImageColors::closestToWhite() const
+{
+    /* clang-format off */
+    return_fallback(Qt::white)
+    if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
+        return QColor(230, 230, 230);
+    }
+    /* clang-format on */
+
+    return m_imageData.m_closestToWhite;
+}
+
+QColor ImageColors::closestToBlack() const
+{
+    /* clang-format off */
+    return_fallback(Qt::black)
+    if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
+        return QColor(20, 20, 20);
+    }
+    /* clang-format on */
+    return m_imageData.m_closestToBlack;
+}
+
+#include "moc_imagecolors.cpp"
diff --git a/src/imagecolors.h b/src/imagecolors.h
new file mode 100644 (file)
index 0000000..77fa351
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ *  Copyright 2020 Marco Martin <mart@kde.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  2.010-1301, USA.
+ */
+
+#pragma once
+
+#include "colorutils.h"
+
+#include <QColor>
+#include <QFuture>
+#include <QImage>
+#include <QObject>
+#include <QPointer>
+#include <QQuickItem>
+#include <QQuickItemGrabResult>
+#include <QQuickWindow>
+
+class QTimer;
+
+struct ImageData {
+    struct colorStat {
+        QList<QRgb> colors;
+        QRgb centroid = 0;
+        qreal ratio = 0;
+    };
+
+    struct colorSet {
+        QColor average;
+        QColor text;
+        QColor background;
+        QColor highlight;
+    };
+
+    QList<QRgb> m_samples;
+    QList<colorStat> m_clusters;
+    QVariantList m_palette;
+
+    bool m_darkPalette = true;
+    QColor m_dominant = Qt::transparent;
+    QColor m_dominantContrast;
+    QColor m_average;
+    QColor m_highlight;
+
+    QColor m_closestToBlack;
+    QColor m_closestToWhite;
+};
+
+/**
+ * @brief Helps extract specific colors from an element or image
+ * (e.g., its dominant colors).
+ */
+class ImageColors : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the source from which colors should be extracted.
+     *
+     * The following values are allowed:
+     * * QtQuick.Item
+     * * QtGui.QImage
+     * * QtGui.QIcon
+     * * @link Icon::source Icon.name @endlink
+     *
+     * @note An Item's color palette will only be extracted once unless you * call `update()`, regardless of how the item hanges.
+     */
+    Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged)
+
+    /**
+     * @brief This property holds a list of colors and related information about them.
+     *
+     * Each list item has the following properties:
+     * * `color`: The color of the list item.
+     * * `ratio`: How dominant the color is in the source image.
+     * * `contrastingColor`: The color from the source image that's closest to the inverse of `color`.
+     *
+     * The list is sorted by `ratio`; the first element is the most
+     * dominant color in the source image and the last element is the
+     * least dominant color of the image.
+     *
+     * @note K-means clustering is used to extract these colors; see https://en.wikipedia.org/wiki/K-means_clustering.
+     */
+    Q_PROPERTY(QVariantList palette READ palette NOTIFY paletteChanged)
+
+    /**
+     * @brief This property specifies whether the palette is a light or dark color scheme.
+     */
+    Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the average color of the source image.
+     */
+    Q_PROPERTY(QColor average READ average NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the dominant color of the source image.
+     *
+     * The dominant color of the image is the color of the largest
+     * cluster in the image.
+     *
+     * @see https://en.wikipedia.org/wiki/K-means_clustering
+     */
+    Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged)
+
+    /**
+     * @brief Ths property holds the suggested "contrasting" color to the dominant one.
+     *
+     * It's the color in the palette nearest to the negative of the dominant.
+     */
+    Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds an accent color extracted from the source image.
+     *
+     * The accent color is the color cluster with the highest CIELAB
+     * chroma in the source image.
+     *
+     * @see https://en.wikipedia.org/wiki/Colorfulness#Chroma
+     */
+    Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the color suitable for rendering text and other foreground
+     * over the source image.
+     *
+     * On dark items, this will be the color closest to white in
+     * the image if it's light enough, or a bright gray otherwise.
+     * On light items, this will be the color closest to black in
+     * the image if it's dark enough, or a dark gray otherwise.
+     */
+    Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds a color that is suitable as a
+     * a background behind the source image.
+     *
+     * There are two possible outcomes:
+     * * On dark items, this will be the color closest to black in the
+     * image if it's dark enough, or a dark gray otherwise.
+     * * On light items, this will be the color closest to white
+     * in the image if it's light enough, or a bright gray otherwise.
+     */
+    Q_PROPERTY(QColor background READ background NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the lightest color of the source image.
+     */
+    Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the darkest color of the source image.
+     */
+    Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged)
+
+    /**
+     * @brief This property holds the value to return when the palette is not
+     * available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QVariantList fallbackPalette MEMBER m_fallbackPalette NOTIFY fallbackPaletteChanged)
+
+    /**
+     * @brief This property holds the value to return instead when
+     * paletteBrightness is not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(ColorUtils::Brightness fallbackPaletteBrightness MEMBER m_fallbackPaletteBrightness NOTIFY fallbackPaletteBrightnessChanged)
+
+    /**
+     * @brief This property holds the value to return instead when average is
+     * not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackAverage MEMBER m_fallbackAverage NOTIFY fallbackAverageChanged)
+
+    /**
+     * @brief This property holds the value to return instead when dominant is
+     * not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackDominant MEMBER m_fallbackDominant NOTIFY fallbackDominantChanged)
+
+    /**
+     * @brief This property holds the value of the palette to return instead
+     * when dominantContrasting is not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackDominantContrasting MEMBER m_fallbackDominantContrasting NOTIFY fallbackDominantContrastingChanged)
+
+    /**
+     * @brief This property holds the value to return instead when highlight is
+     * not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackHighlight MEMBER m_fallbackHighlight NOTIFY fallbackHighlightChanged)
+
+    /**
+     * @brief This property holds the value to return instead when foreground is
+     * not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackForeground MEMBER m_fallbackForeground NOTIFY fallbackForegroundChanged)
+
+    /**
+     * @brief This property holds the value to return instead when background is
+     * not available.
+     *
+     * @note This may happen when ImageColors is still computing or the image
+     * source is invalid.
+     */
+    Q_PROPERTY(QColor fallbackBackground MEMBER m_fallbackBackground NOTIFY fallbackBackgroundChanged)
+
+public:
+    explicit ImageColors(QObject *parent = nullptr);
+    ~ImageColors() override;
+
+    void setSource(const QVariant &source);
+    QVariant source() const;
+
+    void setSourceImage(const QImage &image);
+    QImage sourceImage() const;
+
+    void setSourceItem(QQuickItem *source);
+    QQuickItem *sourceItem() const;
+
+    Q_INVOKABLE void update();
+
+    QVariantList palette() const;
+    ColorUtils::Brightness paletteBrightness() const;
+    QColor average() const;
+    QColor dominant() const;
+    QColor dominantContrast() const;
+    QColor highlight() const;
+    QColor foreground() const;
+    QColor background() const;
+    QColor closestToWhite() const;
+    QColor closestToBlack() const;
+
+Q_SIGNALS:
+    void sourceChanged();
+    void paletteChanged();
+    void fallbackPaletteChanged();
+    void fallbackPaletteBrightnessChanged();
+    void fallbackAverageChanged();
+    void fallbackDominantChanged();
+    void fallbackDominantContrastingChanged();
+    void fallbackHighlightChanged();
+    void fallbackForegroundChanged();
+    void fallbackBackgroundChanged();
+
+private:
+    static inline void positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters);
+    ImageData generatePalette(const QImage &sourceImage) const;
+
+    double getClusterScore(const ImageData::colorStat &stat) const;
+    void postProcess(ImageData &imageData) const;
+
+    // Arbitrary number that seems to work well
+    static const int s_minimumSquareDistance = 32000;
+    QPointer<QQuickWindow> m_window;
+    QVariant m_source;
+    QPointer<QQuickItem> m_sourceItem;
+    QSharedPointer<QQuickItemGrabResult> m_grabResult;
+    QImage m_sourceImage;
+    QFutureWatcher<QImage> *m_futureSourceImageData = nullptr;
+
+    QTimer *m_imageSyncTimer;
+
+    QFutureWatcher<ImageData> *m_futureImageData = nullptr;
+    ImageData m_imageData;
+
+    QVariantList m_fallbackPalette;
+    ColorUtils::Brightness m_fallbackPaletteBrightness;
+    QColor m_fallbackAverage;
+    QColor m_fallbackDominant;
+    QColor m_fallbackDominantContrasting;
+    QColor m_fallbackHighlight;
+    QColor m_fallbackForeground;
+    QColor m_fallbackBackground;
+};
diff --git a/src/inputmethod.cpp b/src/inputmethod.cpp
new file mode 100644 (file)
index 0000000..cf3bc42
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "inputmethod.h"
+
+#include "libkirigami/virtualkeyboardwatcher.h"
+
+class Q_DECL_HIDDEN InputMethod::Private
+{
+public:
+    bool available = false;
+    bool enabled = false;
+    bool active = false;
+    bool visible = false;
+};
+
+InputMethod::InputMethod(QObject *parent)
+    : QObject(parent)
+    , d(std::make_unique<Private>())
+{
+    auto watcher = Kirigami::VirtualKeyboardWatcher::self();
+
+    connect(watcher, &Kirigami::VirtualKeyboardWatcher::availableChanged, this, [this]() {
+        d->available = Kirigami::VirtualKeyboardWatcher::self()->available();
+        Q_EMIT availableChanged();
+    });
+
+    connect(watcher, &Kirigami::VirtualKeyboardWatcher::enabledChanged, this, [this]() {
+        d->enabled = Kirigami::VirtualKeyboardWatcher::self()->enabled();
+        Q_EMIT enabledChanged();
+    });
+
+    connect(watcher, &Kirigami::VirtualKeyboardWatcher::activeChanged, this, [this]() {
+        d->active = Kirigami::VirtualKeyboardWatcher::self()->active();
+        Q_EMIT activeChanged();
+    });
+
+    connect(watcher, &Kirigami::VirtualKeyboardWatcher::visibleChanged, this, [this]() {
+        d->visible = Kirigami::VirtualKeyboardWatcher::self()->visible();
+        Q_EMIT visibleChanged();
+    });
+
+    connect(watcher, &Kirigami::VirtualKeyboardWatcher::willShowOnActiveChanged, this, [this]() {
+        Q_EMIT willShowOnActiveChanged();
+    });
+
+    d->available = watcher->available();
+    d->enabled = watcher->enabled();
+    d->active = watcher->active();
+    d->visible = watcher->visible();
+}
+
+InputMethod::~InputMethod() = default;
+
+bool InputMethod::available() const
+{
+    return d->available;
+}
+
+bool InputMethod::enabled() const
+{
+    return d->enabled;
+}
+
+void InputMethod::setEnabled(bool newEnabled)
+{
+    if (newEnabled == d->enabled) {
+        return;
+    }
+
+    d->enabled = newEnabled;
+    Q_EMIT enabledChanged();
+}
+
+bool InputMethod::active() const
+{
+    return d->active;
+}
+
+void InputMethod::setActive(bool newActive)
+{
+    if (newActive == d->active) {
+        return;
+    }
+
+    d->active = newActive;
+    Q_EMIT activeChanged();
+}
+
+bool InputMethod::visible() const
+{
+    return d->visible;
+}
+
+bool InputMethod::willShowOnActive() const
+{
+    return Kirigami::VirtualKeyboardWatcher::self()->willShowOnActive();
+}
diff --git a/src/inputmethod.h b/src/inputmethod.h
new file mode 100644 (file)
index 0000000..12b4383
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef INPUTMETHOD_H
+#define INPUTMETHOD_H
+
+#include <memory>
+
+#include <QObject>
+
+/**
+ * This exposes information about the current used input method.
+ */
+class InputMethod : public QObject
+{
+    Q_OBJECT
+
+public:
+    InputMethod(QObject *parent = nullptr);
+    ~InputMethod() override;
+
+    /**
+     * @brief This property specifies whether an input method is available.
+     *
+     * This will be @c true if there is an input method available. When it is
+     * @c false it means there's no special input method configured and input
+     * happens directly through keyboard events.
+     */
+    Q_PROPERTY(bool available READ available NOTIFY availableChanged)
+    bool available() const;
+    Q_SIGNAL void availableChanged();
+
+    /**
+     * @brief This property sets whether the current input method is enabled.
+     *
+     * If this is false, that means the input method is available but not in use.
+     */
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+    bool enabled() const;
+    void setEnabled(bool newEnabled);
+    Q_SIGNAL void enabledChanged();
+
+    /**
+     * @brief This property sets whether the current method is active.
+     *
+     * What active means depends on the type of input method. In case of a
+     * virtual keyboard for example, it would mean the virtual keyboard is
+     * visible.
+     */
+    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+    bool active() const;
+    void setActive(bool newActive);
+    Q_SIGNAL void activeChanged();
+
+    /**
+     * @brief This property specifies whether the current input method is visible.
+     * Is the current input method visible?
+     *
+     * For some input methods this will match ::active, for others this may differ.
+     */
+    Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
+    bool visible() const;
+    Q_SIGNAL void visibleChanged();
+
+    /**
+     * @brief This property sets whether the input method will be shown
+     * when a text input field gains focus.
+     *
+     * This is intended to be used to decide whether to give an input field
+     * default focus. For certain input methods, like virtual keyboards, it may
+     * not be desirable to show it by default. For example, having a search
+     * field focused on application startup may cause the virtual keyboard to
+     * show, greatly reducing the available application space.
+     */
+    Q_PROPERTY(bool willShowOnActive READ willShowOnActive NOTIFY willShowOnActiveChanged)
+    bool willShowOnActive() const;
+    Q_SIGNAL void willShowOnActiveChanged();
+
+private:
+    class Private;
+    const std::unique_ptr<Private> d;
+};
+
+#endif // INPUTMETHOD_H
diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp
new file mode 100644 (file)
index 0000000..c31002b
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ *  SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
+ *  SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "kirigamiplugin.h"
+#include "avatar.h"
+#include "colorutils.h"
+#include "columnview.h"
+#include "delegaterecycler.h"
+#include "enums.h"
+#include "formlayoutattached.h"
+#include "icon.h"
+#include "imagecolors.h"
+#include "inputmethod.h"
+#include "mnemonicattached.h"
+#include "pagepool.h"
+#include "pagerouter.h"
+#include "scenepositionattached.h"
+#include "settings.h"
+#include "shadowedrectangle.h"
+#include "shadowedtexture.h"
+#include "sizegroup.h"
+#include "spellcheckinghint.h"
+#include "toolbarlayout.h"
+#include "units.h"
+#include "wheelhandler.h"
+
+#include <QClipboard>
+#include <QGuiApplication>
+#include <QQmlContext>
+#include <QQuickItem>
+#include <QQuickStyle>
+
+#include "libkirigami/basictheme_p.h"
+#include "libkirigami/platformtheme.h"
+#include "libkirigami/styleselector_p.h"
+#include "loggingcategory.h"
+#include "libkirigami/basictheme_p.h"
+#include "libkirigami/kirigamipluginfactory.h"
+
+static QString s_selectedStyle;
+
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+#include "loggingcategory.h"
+#include <QDebug>
+#endif
+
+class CopyHelperPrivate : public QObject
+{
+    Q_OBJECT
+public:
+    Q_INVOKABLE static void copyTextToClipboard(const QString &text)
+    {
+        qGuiApp->clipboard()->setText(text);
+    }
+};
+
+// we can't do this in the plugin object directly, as that can live in a different thread
+// and event filters are only allowed in the same thread as the filtered object
+class LanguageChangeEventFilter : public QObject
+{
+    Q_OBJECT
+public:
+    bool eventFilter(QObject *receiver, QEvent *event) override
+    {
+        if (event->type() == QEvent::LanguageChange && receiver == QCoreApplication::instance()) {
+            Q_EMIT languageChangeEvent();
+        }
+        return QObject::eventFilter(receiver, event);
+    }
+
+Q_SIGNALS:
+    void languageChangeEvent();
+};
+
+KirigamiPlugin::KirigamiPlugin(QObject *parent)
+    : QQmlExtensionPlugin(parent)
+{
+    auto filter = new LanguageChangeEventFilter;
+    filter->moveToThread(QCoreApplication::instance()->thread());
+    QCoreApplication::instance()->installEventFilter(filter);
+    connect(filter, &LanguageChangeEventFilter::languageChangeEvent, this, &KirigamiPlugin::languageChangeEvent);
+}
+
+QUrl KirigamiPlugin::componentUrl(const QString &fileName) const
+{
+    return Kirigami::StyleSelector::componentUrl(fileName);
+}
+
+using SingletonCreationFunction = QObject *(*)(QQmlEngine *, QJSEngine *);
+
+template<class T>
+inline SingletonCreationFunction singleton()
+{
+    static_assert(std::is_base_of<QObject, T>::value);
+    return [](QQmlEngine *engine, QJSEngine *) -> QObject * {
+        auto x = new T;
+        x->setParent(engine);
+        return x;
+    };
+}
+
+void KirigamiPlugin::registerTypes(const char *uri)
+{
+#if defined(Q_OS_ANDROID)
+    QResource::registerResource(QStringLiteral("assets:/android_rcc_bundle.rcc"));
+#endif
+
+    Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.kirigami"));
+
+    Kirigami::StyleSelector::setBaseUrl(baseUrl());
+
+    if (QIcon::themeName().isEmpty() && !qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) {
+        QIcon::setThemeSearchPaths({Kirigami::StyleSelector::resolveFilePath(QStringLiteral(".")), QStringLiteral(":/icons")});
+        QIcon::setThemeName(QStringLiteral("breeze-internal"));
+    } else {
+        QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << Kirigami::StyleSelector::resolveFilePath(QStringLiteral("icons")));
+    }
+
+    qmlRegisterSingletonType<Settings>(uri, 2, 0, "Settings", [](QQmlEngine *e, QJSEngine *) -> QObject * {
+        Settings *settings = Settings::self();
+        // singleton managed internally, qml should never delete it
+        e->setObjectOwnership(settings, QQmlEngine::CppOwnership);
+        settings->setStyle(Kirigami::StyleSelector::style());
+        return settings;
+    });
+
+    qmlRegisterUncreatableType<ApplicationHeaderStyle>(uri,
+                                                       2,
+                                                       0,
+                                                       "ApplicationHeaderStyle",
+                                                       QStringLiteral("Cannot create objects of type ApplicationHeaderStyle"));
+
+    // old legacy retrocompatible Theme
+    qmlRegisterSingletonType<Kirigami::BasicThemeDefinition>(uri, 2, 0, "Theme", [](QQmlEngine *, QJSEngine *) {
+        qCWarning(KirigamiLog) << "The Theme singleton is deprecated (since 5.39). Import Kirigami 2.2 or higher and use the attached property instead.";
+        return new Kirigami::BasicThemeDefinition{};
+    });
+
+    qmlRegisterSingletonType<Kirigami::Units>(uri, 2, 0, "Units", [] (QQmlEngine *engine, QJSEngine *) {
+#ifndef KIRIGAMI_BUILD_TYPE_STATIC
+        auto plugin = Kirigami::KirigamiPluginFactory::findPlugin();
+        if (plugin) {
+            // Check if the plugin implements units
+            auto pluginV2 = qobject_cast<Kirigami::KirigamiPluginFactoryV2 *>(plugin);
+            if (pluginV2) {
+                auto units = pluginV2->createUnits(engine);
+                if (units) {
+                    return units;
+                } else {
+                    qWarning(KirigamiLog) << "The style returned a nullptr Units*, falling back to defaults";
+                }
+            } else {
+                qWarning(KirigamiLog) << "The style does not provide a C++ Units implementation."
+                                      << "QML Units implementations are no longer supported.";
+            }
+        } else {
+            qWarning(KirigamiLog) << "Failed to find a Kirigami platform plugin";
+        }
+#endif
+        // Fall back to the default units implementation
+        return new Kirigami::Units(engine);
+    });
+
+    qmlRegisterType(componentUrl(QStringLiteral("Action.qml")), uri, 2, 0, "Action");
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationHeader.qml")), uri, 2, 0, "AbstractApplicationHeader");
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationWindow.qml")), uri, 2, 0, "AbstractApplicationWindow");
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractListItem.qml")), uri, 2, 0, "AbstractListItem");
+    qmlRegisterType(componentUrl(QStringLiteral("ApplicationHeader.qml")), uri, 2, 0, "ApplicationHeader");
+    qmlRegisterType(componentUrl(QStringLiteral("ToolBarApplicationHeader.qml")), uri, 2, 0, "ToolBarApplicationHeader");
+    qmlRegisterType(componentUrl(QStringLiteral("ApplicationWindow.qml")), uri, 2, 0, "ApplicationWindow");
+    qmlRegisterType(componentUrl(QStringLiteral("BasicListItem.qml")), uri, 2, 0, "BasicListItem");
+    qmlRegisterType(componentUrl(QStringLiteral("OverlayDrawer.qml")), uri, 2, 0, "OverlayDrawer");
+    qmlRegisterType(componentUrl(QStringLiteral("ContextDrawer.qml")), uri, 2, 0, "ContextDrawer");
+    qmlRegisterType(componentUrl(QStringLiteral("GlobalDrawer.qml")), uri, 2, 0, "GlobalDrawer");
+    qmlRegisterType(componentUrl(QStringLiteral("Heading.qml")), uri, 2, 0, "Heading");
+    qmlRegisterType(componentUrl(QStringLiteral("Separator.qml")), uri, 2, 0, "Separator");
+    qmlRegisterType(componentUrl(QStringLiteral("PageRow.qml")), uri, 2, 0, "PageRow");
+
+    qmlRegisterType<Icon>(uri, 2, 0, "Icon");
+
+    qmlRegisterType(componentUrl(QStringLiteral("Label.qml")), uri, 2, 0, "Label");
+    // TODO: uncomment for 2.3 release
+    // qmlRegisterTypeNotAvailable(uri, 2, 3, "Label", "Label type not supported anymore, use QtQuick.Controls.Label 2.0 instead");
+    qmlRegisterType(componentUrl(QStringLiteral("OverlaySheet.qml")), uri, 2, 0, "OverlaySheet");
+    qmlRegisterType(componentUrl(QStringLiteral("Page.qml")), uri, 2, 0, "Page");
+    qmlRegisterType(componentUrl(QStringLiteral("ScrollablePage.qml")), uri, 2, 0, "ScrollablePage");
+    qmlRegisterType(componentUrl(QStringLiteral("SplitDrawer.qml")), uri, 2, 0, "SplitDrawer");
+    qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem.qml")), uri, 2, 0, "SwipeListItem");
+
+    // 2.1
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractItemViewHeader.qml")), uri, 2, 1, "AbstractItemViewHeader");
+    qmlRegisterType(componentUrl(QStringLiteral("ItemViewHeader.qml")), uri, 2, 1, "ItemViewHeader");
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationItem.qml")), uri, 2, 1, "AbstractApplicationItem");
+    qmlRegisterType(componentUrl(QStringLiteral("ApplicationItem.qml")), uri, 2, 1, "ApplicationItem");
+
+    // 2.2
+    // Theme changed from a singleton to an attached property
+    qmlRegisterUncreatableType<Kirigami::PlatformTheme>(uri,
+                                                        2,
+                                                        2,
+                                                        "Theme",
+                                                        QStringLiteral("Cannot create objects of type Theme, use it as an attached property"));
+
+    // 2.3
+    qmlRegisterType(componentUrl(QStringLiteral("FormLayout.qml")), uri, 2, 3, "FormLayout");
+    qmlRegisterUncreatableType<FormLayoutAttached>(uri,
+                                                   2,
+                                                   3,
+                                                   "FormData",
+                                                   QStringLiteral("Cannot create objects of type FormData, use it as an attached property"));
+    qmlRegisterUncreatableType<MnemonicAttached>(uri,
+                                                 2,
+                                                 3,
+                                                 "MnemonicData",
+                                                 QStringLiteral("Cannot create objects of type MnemonicData, use it as an attached property"));
+
+    // 2.4
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractCard.qml")), uri, 2, 4, "AbstractCard");
+    qmlRegisterType(componentUrl(QStringLiteral("Card.qml")), uri, 2, 4, "Card");
+    qmlRegisterType(componentUrl(QStringLiteral("CardsListView.qml")), uri, 2, 4, "CardsListView");
+    qmlRegisterType(componentUrl(QStringLiteral("CardsGridView.qml")), uri, 2, 4, "CardsGridView");
+    qmlRegisterType(componentUrl(QStringLiteral("CardsLayout.qml")), uri, 2, 4, "CardsLayout");
+    qmlRegisterType(componentUrl(QStringLiteral("InlineMessage.qml")), uri, 2, 4, "InlineMessage");
+    qmlRegisterUncreatableType<MessageType>(uri, 2, 4, "MessageType", QStringLiteral("Cannot create objects of type MessageType"));
+    qmlRegisterType<DelegateRecycler>(uri, 2, 4, "DelegateRecycler");
+
+    // 2.5
+    qmlRegisterType(componentUrl(QStringLiteral("ListItemDragHandle.qml")), uri, 2, 5, "ListItemDragHandle");
+    qmlRegisterType(componentUrl(QStringLiteral("ActionToolBar.qml")), uri, 2, 5, "ActionToolBar");
+    qmlRegisterUncreatableType<ScenePositionAttached>(uri,
+                                                      2,
+                                                      5,
+                                                      "ScenePosition",
+                                                      QStringLiteral("Cannot create objects of type ScenePosition, use it as an attached property"));
+
+    // 2.6
+    qmlRegisterType(componentUrl(QStringLiteral("AboutPage.qml")), uri, 2, 6, "AboutPage");
+    qmlRegisterType(componentUrl(QStringLiteral("LinkButton.qml")), uri, 2, 6, "LinkButton");
+    qmlRegisterType(componentUrl(QStringLiteral("UrlButton.qml")), uri, 2, 6, "UrlButton");
+    qmlRegisterSingletonType<CopyHelperPrivate>("org.kde.kirigami.private", 2, 6, "CopyHelperPrivate", singleton<CopyHelperPrivate>());
+
+    // 2.7
+    qmlRegisterType<ColumnView>(uri, 2, 7, "ColumnView");
+    qmlRegisterType(componentUrl(QStringLiteral("ActionTextField.qml")), uri, 2, 7, "ActionTextField");
+
+    // 2.8
+    qmlRegisterType(componentUrl(QStringLiteral("SearchField.qml")), uri, 2, 8, "SearchField");
+    qmlRegisterType(componentUrl(QStringLiteral("PasswordField.qml")), uri, 2, 8, "PasswordField");
+
+    // 2.9
+    qmlRegisterType<WheelHandler>(uri, 2, 9, "WheelHandler");
+    qmlRegisterUncreatableType<KirigamiWheelEvent>(uri, 2, 9, "WheelEvent", QStringLiteral("Cannot create objects of type WheelEvent."));
+
+    // 2.10
+    qmlRegisterType(componentUrl(QStringLiteral("ListSectionHeader.qml")), uri, 2, 10, "ListSectionHeader");
+
+    // 2.11
+    qmlRegisterType<PagePool>(uri, 2, 11, "PagePool");
+    qmlRegisterType(componentUrl(QStringLiteral("PagePoolAction.qml")), uri, 2, 11, "PagePoolAction");
+
+    // TODO: remove
+    qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem2.qml")), uri, 2, 11, "SwipeListItem2");
+
+    // 2.12
+    qmlRegisterType<ShadowedRectangle>(uri, 2, 12, "ShadowedRectangle");
+    qmlRegisterType<ShadowedTexture>(uri, 2, 12, "ShadowedTexture");
+    qmlRegisterType(componentUrl(QStringLiteral("ShadowedImage.qml")), uri, 2, 12, "ShadowedImage");
+    qmlRegisterType(componentUrl(QStringLiteral("PlaceholderMessage.qml")), uri, 2, 12, "PlaceholderMessage");
+
+    qmlRegisterUncreatableType<BorderGroup>(uri, 2, 12, "BorderGroup", QStringLiteral("Used as grouped property"));
+    qmlRegisterUncreatableType<ShadowGroup>(uri, 2, 12, "ShadowGroup", QStringLiteral("Used as grouped property"));
+    qmlRegisterSingletonType<ColorUtils>(uri, 2, 12, "ColorUtils", singleton<ColorUtils>());
+
+    qmlRegisterUncreatableType<CornersGroup>(uri, 2, 12, "CornersGroup", QStringLiteral("Used as grouped property"));
+    qmlRegisterType<PageRouter>(uri, 2, 12, "PageRouter");
+    qmlRegisterType<PageRoute>(uri, 2, 12, "PageRoute");
+    qmlRegisterUncreatableType<PageRouterAttached>(uri, 2, 12, "PageRouterAttached", QStringLiteral("PageRouterAttached cannot be created"));
+    qmlRegisterType(componentUrl(QStringLiteral("RouterWindow.qml")), uri, 2, 12, "RouterWindow");
+
+    // 2.13
+    qmlRegisterType<ImageColors>(uri, 2, 13, "ImageColors");
+    qmlRegisterType(componentUrl(QStringLiteral("Avatar.qml")), uri, 2, 13, "Avatar");
+    qmlRegisterType(componentUrl(QStringLiteral("swipenavigator/SwipeNavigator.qml")), uri, 2, 13, "SwipeNavigator");
+
+    // 2.14
+    qmlRegisterUncreatableType<PreloadRouteGroup>(uri, 2, 14, "PreloadRouteGroup", QStringLiteral("PreloadRouteGroup cannot be created"));
+    qmlRegisterType(componentUrl(QStringLiteral("FlexColumn.qml")), uri, 2, 14, "FlexColumn");
+    qmlRegisterType<ToolBarLayout>(uri, 2, 14, "ToolBarLayout");
+    qmlRegisterSingletonType<DisplayHint>(uri, 2, 14, "DisplayHint", singleton<DisplayHint>());
+    qmlRegisterType<SizeGroup>(uri, 2, 14, "SizeGroup");
+    qmlRegisterType<AvatarGroup>("org.kde.kirigami.private", 2, 14, "AvatarGroup");
+    qmlRegisterType(componentUrl(QStringLiteral("CheckableListItem.qml")), uri, 2, 14, "CheckableListItem");
+    qmlRegisterSingletonType<NameUtils>(uri, 2, 14, "NameUtils", singleton<NameUtils>());
+
+    qmlRegisterType(componentUrl(QStringLiteral("Hero.qml")), uri, 2, 15, "Hero");
+
+    // 2.16
+    qmlRegisterType<Kirigami::BasicThemeDefinition>(uri, 2, 16, "BasicThemeDefinition");
+
+    // 2.17
+    qmlRegisterType(componentUrl(QStringLiteral("swipenavigator/TabViewLayout.qml")), uri, 2, 17, "TabViewLayout");
+    qmlRegisterType(componentUrl(QStringLiteral("swipenavigator/PageTab.qml")), uri, 2, 17, "PageTab");
+
+    // 2.18
+    qmlRegisterType<SpellCheckingAttached>(uri, 2, 18, "SpellChecking");
+    qmlRegisterType(componentUrl(QStringLiteral("settingscomponents/CategorizedSettings.qml")), uri, 2, 18, "CategorizedSettings");
+    qmlRegisterType(componentUrl(QStringLiteral("settingscomponents/GenericSettingsPage.qml")), uri, 2, 18, "GenericSettingsPage");
+    qmlRegisterType(componentUrl(QStringLiteral("settingscomponents/SettingAction.qml")), uri, 2, 18, "SettingAction");
+    
+    // 2.19
+    qmlRegisterType(componentUrl(QStringLiteral("AboutItem.qml")), uri, 2, 19, "AboutItem");
+    qmlRegisterType(componentUrl(QStringLiteral("NavigationTabBar.qml")), uri, 2, 19, "NavigationTabBar");
+    qmlRegisterType(componentUrl(QStringLiteral("NavigationTabButton.qml")), uri, 2, 19, "NavigationTabButton");
+    qmlRegisterType(componentUrl(QStringLiteral("Dialog.qml")), uri, 2, 19, "Dialog");
+    qmlRegisterType(componentUrl(QStringLiteral("MenuDialog.qml")), uri, 2, 19, "MenuDialog");
+    qmlRegisterType(componentUrl(QStringLiteral("PromptDialog.qml")), uri, 2, 19, "PromptDialog");
+    qmlRegisterType(componentUrl(QStringLiteral("AbstractChip.qml")), uri, 2, 19, "AbstractChip");
+    qmlRegisterType(componentUrl(QStringLiteral("Chip.qml")), uri, 2, 19, "Chip");
+    qmlRegisterType(componentUrl(QStringLiteral("LoadingPlaceholder.qml")), uri, 2, 19, "LoadingPlaceholder");
+
+    qmlRegisterSingletonType<InputMethod>(uri, 2, 19, "InputMethod", [](QQmlEngine *, QJSEngine *) {
+        return new InputMethod{};
+    });
+
+    // 2.20
+    qmlRegisterType(componentUrl(QStringLiteral("SelectableLabel.qml")), uri, 2, 20, "SelectableLabel");
+
+    qmlProtectModule(uri, 2);
+}
+
+void KirigamiPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
+{
+    Q_UNUSED(uri);
+    connect(this, &KirigamiPlugin::languageChangeEvent, engine, &QQmlEngine::retranslate);
+}
+
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+KirigamiPlugin& KirigamiPlugin::getInstance()
+{
+    static KirigamiPlugin instance;
+    return instance;
+}
+
+void KirigamiPlugin::registerTypes(QQmlEngine* engine)
+{
+    Q_INIT_RESOURCE(shaders);
+    Q_INIT_RESOURCE(KirigamiPlugin);
+
+    if (engine) {
+        engine->addImportPath(QLatin1String(":/"));
+    }
+    else {
+        qCWarning(KirigamiLog)
+            << "Registering Kirigami on a null QQmlEngine instance - you likely want to pass a valid engine, or you will want to manually add the "
+            "qrc root path :/ to your import paths list so the engine is able to load the plugin";
+    }
+}
+#endif
+
+#include "kirigamiplugin.moc"
diff --git a/src/kirigamiplugin.h b/src/kirigamiplugin.h
new file mode 100644 (file)
index 0000000..8ec815e
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
+ *  SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMIPLUGIN_H
+#define KIRIGAMIPLUGIN_H
+
+#include <QUrl>
+#include <QQmlEngine>
+#include <QQmlExtensionPlugin>
+
+class KirigamiPlugin : public QQmlExtensionPlugin
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+    KirigamiPlugin(QObject *parent = nullptr);
+    void registerTypes(const char *uri) override;
+    void initializeEngine(QQmlEngine *engine, const char *uri) override;
+
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+    static KirigamiPlugin& getInstance();
+    static void registerTypes(QQmlEngine* engine = nullptr);
+#endif
+
+Q_SIGNALS:
+    void languageChangeEvent();
+
+private:
+    QUrl componentUrl(const QString &fileName) const;
+};
+
+#endif
diff --git a/src/libkirigami/CMakeLists.txt b/src/libkirigami/CMakeLists.txt
new file mode 100644 (file)
index 0000000..af7deb2
--- /dev/null
@@ -0,0 +1,161 @@
+set(KIRIGAMI_INSTALL_INCLUDEDIR "${KDE_INSTALL_INCLUDEDIR_KF}/Kirigami2")
+
+add_library(KF5Kirigami2 ${libkirigami_SRCS})
+add_library(KF5::Kirigami2 ALIAS KF5Kirigami2)
+
+target_sources(KF5Kirigami2 PRIVATE
+    platformtheme.cpp
+    basictheme.cpp
+    kirigamipluginfactory.cpp
+    tabletmodewatcher.cpp
+    styleselector.cpp
+    units.cpp
+    virtualkeyboardwatcher.cpp
+)
+
+set(libkirigami_extra_sources "")
+
+#use dbus on linux, bsd etc, but not android and apple stuff
+if (UNIX AND NOT ANDROID AND NOT(APPLE) AND NOT(DISABLE_DBUS))
+    qt_add_dbus_interface(libkirigami_extra_sources org.kde.KWin.TabletModeManager.xml tabletmodemanager_interface)
+    qt_add_dbus_interface(libkirigami_extra_sources org.kde.KWin.VirtualKeyboard.xml virtualkeyboard_interface)
+    set(LIBKIRIGAMKI_EXTRA_LIBS Qt${QT_MAJOR_VERSION}::DBus)
+endif()
+
+ecm_qt_declare_logging_category(libkirigami_extra_sources
+    HEADER loggingcategory.h
+    IDENTIFIER KirigamiLog
+    CATEGORY_NAME kf.kirigami
+    DEFAULT_SEVERITY Warning
+)
+
+ecm_generate_export_header(KF5Kirigami2
+    VERSION ${KF_VERSION}
+    BASE_NAME Kirigami2
+    DEPRECATION_VERSIONS 5.80 5.86
+)
+
+target_sources(KF5Kirigami2 PRIVATE ${libkirigami_extra_sources})
+
+target_include_directories(KF5Kirigami2
+    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}
+    INTERFACE "$<INSTALL_INTERFACE:${KIRIGAMI_INSTALL_INCLUDEDIR}>"
+)
+
+target_link_libraries(KF5Kirigami2
+    PUBLIC
+        Qt${QT_MAJOR_VERSION}::Core
+    PRIVATE
+        Qt${QT_MAJOR_VERSION}::Qml
+        Qt${QT_MAJOR_VERSION}::Quick
+        Qt${QT_MAJOR_VERSION}::QuickControls2
+        ${LIBKIRIGAMKI_EXTRA_LIBS}
+)
+
+set_target_properties(KF5Kirigami2 PROPERTIES
+    VERSION     ${KIRIGAMI2_VERSION}
+    SOVERSION   ${KIRIGAMI2_SOVERSION}
+    EXPORT_NAME "Kirigami2"
+)
+
+ecm_generate_headers(Kirigami2_CamelCase_HEADERS
+    HEADER_NAMES
+    PlatformTheme
+    KirigamiPluginFactory
+    TabletModeWatcher
+    Units
+    VirtualKeyboardWatcher
+
+    PREFIX Kirigami
+    REQUIRED_HEADERS Kirigami2_HEADERS
+)
+
+if(NOT BUILD_SHARED_LIBS)
+    ecm_generate_headers(Kirigami2_HEADERS
+        HEADER_NAMES KirigamiPlugin
+        REQUIRED_HEADERS Kirigami2_HEADERS
+        RELATIVE ..
+    )
+endif()
+
+install(TARGETS KF5Kirigami2
+        EXPORT KF5Kirigami2Targets
+        ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
+
+if(BUILD_SHARED_LIBS)
+    list(APPEND Kirigami2_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/kirigami2_export.h)
+endif()
+
+install(FILES ${Kirigami2_HEADERS}
+        DESTINATION ${KIRIGAMI_INSTALL_INCLUDEDIR}/kirigami # prefix matching C++ namespace
+        COMPONENT Devel)
+install(FILES ${Kirigami2_CamelCase_HEADERS}
+        DESTINATION ${KIRIGAMI_INSTALL_INCLUDEDIR}/Kirigami # prefix matching C++ namespace
+        COMPONENT Devel)
+
+# provide compat headers for old broken C++ namespaceless include path
+if(NOT EXCLUDE_DEPRECATED_BEFORE_AND_AT STREQUAL "CURRENT" AND
+   EXCLUDE_DEPRECATED_BEFORE_AND_AT VERSION_LESS 5.91.0)
+
+    function(generate_compat_headers)
+        foreach(classname ${ARGV})
+            string(TOLOWER ${classname} classname_lc)
+            set(HEADER_NAME "${classname_lc}.h")
+            # normal header
+            set(compat_header "${CMAKE_CURRENT_BINARY_DIR}/compat/${HEADER_NAME}")
+            set(NEW_INCLUDE "kirigami/${HEADER_NAME}")
+            configure_file(compatheader.h.in ${compat_header} @ONLY)
+            install(FILES ${compat_header}
+                DESTINATION ${KIRIGAMI_INSTALL_INCLUDEDIR}
+                COMPONENT Devel
+            )
+            # CamelCase header
+            set(compat_header "${CMAKE_CURRENT_BINARY_DIR}/compat/${classname}")
+            set(NEW_INCLUDE "Kirigami/${classname}")
+            configure_file(compatheader.h.in ${compat_header} @ONLY)
+            install(FILES ${compat_header}
+                DESTINATION ${KIRIGAMI_INSTALL_INCLUDEDIR}
+                COMPONENT Devel
+            )
+        endforeach()
+    endfunction()
+
+    generate_compat_headers(
+        PlatformTheme
+        KirigamiPluginFactory
+        TabletModeWatcher
+        Units
+    )
+endif()
+
+if(BUILD_QCH)
+    ecm_add_qch(
+        KF5Kirigami2_QCH
+        NAME Kirigami2
+        BASE_NAME KF5Kirigami2
+        VERSION ${KF_VERSION}
+        ORG_DOMAIN org.kde
+        SOURCES # using only public headers, to cover only public API
+            ${Kirigami2_HEADERS}
+        MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
+        LINK_QCHS
+            Qt5Core_QCH
+        BLANK_MACROS
+            KIRIGAMI_EXPORT
+            KIRIGAMI_DEPRECATED
+        TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+        QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+        COMPONENT Devel
+    )
+endif()
+
+include(ECMGeneratePriFile)
+ecm_generate_pri_file(BASE_NAME Kirigami2
+    LIB_NAME KF5Kirigami2
+    DEPS "core"
+    FILENAME_VAR PRI_FILENAME
+    INCLUDE_INSTALL_DIR ${KIRIGAMI_INSTALL_INCLUDEDIR}
+)
+install(FILES ${PRI_FILENAME}
+        DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
+
diff --git a/src/libkirigami/basictheme.cpp b/src/libkirigami/basictheme.cpp
new file mode 100644 (file)
index 0000000..a96448e
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "basictheme_p.h"
+
+#include "styleselector_p.h"
+#include <QFile>
+#include <QGuiApplication>
+
+#include "loggingcategory.h"
+
+namespace Kirigami
+{
+class CompatibilityThemeDefinition : public BasicThemeDefinition
+{
+    Q_OBJECT
+public:
+    CompatibilityThemeDefinition(QObject *component, QObject *parent = nullptr)
+        : BasicThemeDefinition(parent)
+    {
+        m_object = component;
+
+        connect(m_object, SIGNAL(textColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(disabledTextColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(highlightColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(highlightedTextColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(backgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(alternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(linkColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(visitedLinkColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(buttonTextColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(buttonBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(buttonAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(buttonHoverColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(buttonFocusColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(viewTextColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(viewBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(viewAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(viewHoverColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(viewFocusColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(complementaryTextColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(complementaryBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(complementaryAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(complementaryHoverColorChanged()), this, SLOT(syncFromQml()));
+        connect(m_object, SIGNAL(complementaryFocusColorChanged()), this, SLOT(syncFromQml()));
+    }
+
+    void syncToQml(PlatformTheme *object) override
+    {
+        BasicThemeDefinition::syncToQml(object);
+
+        QMetaObject::invokeMethod(m_object, "__propagateColorSet", Q_ARG(QVariant, QVariant::fromValue(object->parent())), Q_ARG(QVariant, object->colorSet()));
+        QMetaObject::invokeMethod(m_object,
+                                  "__propagateTextColor",
+                                  Q_ARG(QVariant, QVariant::fromValue(object->parent())),
+                                  Q_ARG(QVariant, object->textColor()));
+        QMetaObject::invokeMethod(m_object,
+                                  "__propagateBackgroundColor",
+                                  Q_ARG(QVariant, QVariant::fromValue(object->parent())),
+                                  Q_ARG(QVariant, object->backgroundColor()));
+        QMetaObject::invokeMethod(m_object,
+                                  "__propagatePrimaryColor",
+                                  Q_ARG(QVariant, QVariant::fromValue(object->parent())),
+                                  Q_ARG(QVariant, object->highlightColor()));
+        QMetaObject::invokeMethod(m_object,
+                                  "__propagateAccentColor",
+                                  Q_ARG(QVariant, QVariant::fromValue(object->parent())),
+                                  Q_ARG(QVariant, object->highlightColor()));
+    }
+
+    Q_SLOT void syncFromQml()
+    {
+        textColor = m_object->property("textColor").value<QColor>();
+        disabledTextColor = m_object->property("disabledTextColor").value<QColor>();
+        highlightColor = m_object->property("highlightColor").value<QColor>();
+        highlightedTextColor = m_object->property("highlightedTextColor").value<QColor>();
+        backgroundColor = m_object->property("backgroundColor").value<QColor>();
+        alternateBackgroundColor = m_object->property("alternateBackgroundColor").value<QColor>();
+        linkColor = m_object->property("linkColor").value<QColor>();
+        visitedLinkColor = m_object->property("visitedLinkColor").value<QColor>();
+        buttonTextColor = m_object->property("buttonTextColor").value<QColor>();
+        buttonBackgroundColor = m_object->property("buttonBackgroundColor").value<QColor>();
+        buttonAlternateBackgroundColor = m_object->property("buttonAlternateBackgroundColor").value<QColor>();
+        buttonHoverColor = m_object->property("buttonHoverColor").value<QColor>();
+        buttonFocusColor = m_object->property("buttonFocusColor").value<QColor>();
+        viewTextColor = m_object->property("viewTextColor").value<QColor>();
+        viewBackgroundColor = m_object->property("viewBackgroundColor").value<QColor>();
+        viewAlternateBackgroundColor = m_object->property("viewAlternateBackgroundColor").value<QColor>();
+        viewHoverColor = m_object->property("viewHoverColor").value<QColor>();
+        viewFocusColor = m_object->property("viewFocusColor").value<QColor>();
+        complementaryTextColor = m_object->property("complementaryTextColor").value<QColor>();
+        complementaryBackgroundColor = m_object->property("complementaryBackgroundColor").value<QColor>();
+        complementaryAlternateBackgroundColor = m_object->property("complementaryAlternateBackgroundColor").value<QColor>();
+        complementaryHoverColor = m_object->property("complementaryHoverColor").value<QColor>();
+        complementaryFocusColor = m_object->property("complementaryFocusColor").value<QColor>();
+
+        Q_EMIT changed();
+    }
+
+private:
+    QObject *m_object;
+};
+
+BasicThemeDefinition::BasicThemeDefinition(QObject *parent)
+    : QObject(parent)
+{
+    defaultFont = qGuiApp->font();
+
+    smallFont = qGuiApp->font();
+    smallFont.setPointSize(smallFont.pointSize() - 2);
+}
+
+void BasicThemeDefinition::syncToQml(PlatformTheme *object)
+{
+    auto item = qobject_cast<QQuickItem *>(object->parent());
+    if (item && qmlAttachedPropertiesObject<PlatformTheme>(item, false) == object) {
+        Q_EMIT sync(item);
+    }
+}
+
+BasicThemeInstance::BasicThemeInstance(QObject *parent)
+    : QObject(parent)
+{
+}
+
+BasicThemeDefinition &BasicThemeInstance::themeDefinition(QQmlEngine *engine)
+{
+    if (m_themeDefinition) {
+        return *m_themeDefinition;
+    }
+
+    auto componentUrl = StyleSelector::componentUrl(QStringLiteral("Theme.qml"));
+    QString path{componentUrl.toLocalFile()};
+    if (path.isEmpty() && componentUrl.scheme() == QLatin1String("qrc")) {
+        path = QLatin1Char(':') + componentUrl.path();
+    }
+    QFile themeFile{path};
+    if (themeFile.open(QIODevice::ReadOnly)) {
+        auto data = themeFile.readAll();
+
+        // Before Kirigami 5.80, custom Theme files would be registered as a
+        // "Theme" singleton that would then be proxied by BasicTheme. This has
+        // changed with the Theme singleton being an instance of BasicTheme.
+        // However, this means that old theme files fail to load because
+        // QQmlComponent complains about "pragma Singleton". To workaround this,
+        // we remove the pragma here, as everything else should still work
+        // correctly.
+        // TODO KF6: Remove this and rely on all Theme files not being singletons.
+        data.replace("\npragma Singleton\n", "");
+
+        QQmlComponent component(engine);
+        component.setData(data, componentUrl);
+        auto result = component.create();
+
+        if (!result) {
+            const auto errors = component.errors();
+            for (auto error : errors) {
+                qCWarning(KirigamiLog) << error.toString();
+            }
+
+            qCWarning(KirigamiLog) << "Invalid Theme file, using default Basic theme.";
+            m_themeDefinition = std::make_unique<BasicThemeDefinition>();
+        } else if (qobject_cast<BasicThemeDefinition *>(result)) {
+            m_themeDefinition.reset(qobject_cast<BasicThemeDefinition *>(result));
+        } else {
+            qCWarning(KirigamiLog) << "Warning: Theme implementations should use Kirigami.BasicThemeDefinition for its root item";
+            m_themeDefinition = std::make_unique<CompatibilityThemeDefinition>(result);
+        }
+    } else {
+        qCDebug(KirigamiLog) << "No Theme file found, using default Basic theme";
+        m_themeDefinition = std::make_unique<BasicThemeDefinition>();
+    }
+
+    connect(m_themeDefinition.get(), &BasicThemeDefinition::changed, this, &BasicThemeInstance::onDefinitionChanged);
+
+    return *m_themeDefinition;
+}
+
+void BasicThemeInstance::onDefinitionChanged()
+{
+    for (auto watcher : std::as_const(watchers)) {
+        watcher->sync();
+    }
+}
+
+Q_GLOBAL_STATIC(BasicThemeInstance, basicThemeInstance)
+
+BasicTheme::BasicTheme(QObject *parent)
+    : PlatformTheme(parent)
+{
+    basicThemeInstance()->watchers.append(this);
+
+    sync();
+}
+
+BasicTheme::~BasicTheme()
+{
+    basicThemeInstance()->watchers.removeOne(this);
+}
+
+void BasicTheme::sync()
+{
+    auto &definition = basicThemeInstance()->themeDefinition(qmlEngine(parent()));
+
+    switch (colorSet()) {
+    case BasicTheme::Button:
+        setTextColor(tint(definition.buttonTextColor));
+        setBackgroundColor(tint(definition.buttonBackgroundColor));
+        setAlternateBackgroundColor(tint(definition.buttonAlternateBackgroundColor));
+        setHoverColor(tint(definition.buttonHoverColor));
+        setFocusColor(tint(definition.buttonFocusColor));
+        break;
+    case BasicTheme::View:
+        setTextColor(tint(definition.viewTextColor));
+        setBackgroundColor(tint(definition.viewBackgroundColor));
+        setAlternateBackgroundColor(tint(definition.viewAlternateBackgroundColor));
+        setHoverColor(tint(definition.viewHoverColor));
+        setFocusColor(tint(definition.viewFocusColor));
+        break;
+    case BasicTheme::Selection:
+        setTextColor(tint(definition.selectionTextColor));
+        setBackgroundColor(tint(definition.selectionBackgroundColor));
+        setAlternateBackgroundColor(tint(definition.selectionAlternateBackgroundColor));
+        setHoverColor(tint(definition.selectionHoverColor));
+        setFocusColor(tint(definition.selectionFocusColor));
+        break;
+    case BasicTheme::Tooltip:
+        setTextColor(tint(definition.tooltipTextColor));
+        setBackgroundColor(tint(definition.tooltipBackgroundColor));
+        setAlternateBackgroundColor(tint(definition.tooltipAlternateBackgroundColor));
+        setHoverColor(tint(definition.tooltipHoverColor));
+        setFocusColor(tint(definition.tooltipFocusColor));
+        break;
+    case BasicTheme::Complementary:
+        setTextColor(tint(definition.complementaryTextColor));
+        setBackgroundColor(tint(definition.complementaryBackgroundColor));
+        setAlternateBackgroundColor(tint(definition.complementaryAlternateBackgroundColor));
+        setHoverColor(tint(definition.complementaryHoverColor));
+        setFocusColor(tint(definition.complementaryFocusColor));
+        break;
+    case BasicTheme::Window:
+    default:
+        setTextColor(tint(definition.textColor));
+        setBackgroundColor(tint(definition.backgroundColor));
+        setAlternateBackgroundColor(tint(definition.alternateBackgroundColor));
+        setHoverColor(tint(definition.hoverColor));
+        setFocusColor(tint(definition.focusColor));
+        break;
+    }
+
+    setDisabledTextColor(tint(definition.disabledTextColor));
+    setHighlightColor(tint(definition.highlightColor));
+    setHighlightedTextColor(tint(definition.highlightedTextColor));
+    setActiveTextColor(tint(definition.activeTextColor));
+    setActiveBackgroundColor(tint(definition.activeBackgroundColor));
+    setLinkColor(tint(definition.linkColor));
+    setLinkBackgroundColor(tint(definition.linkBackgroundColor));
+    setVisitedLinkColor(tint(definition.visitedLinkColor));
+    setVisitedLinkBackgroundColor(tint(definition.visitedLinkBackgroundColor));
+    setNegativeTextColor(tint(definition.negativeTextColor));
+    setNegativeBackgroundColor(tint(definition.negativeBackgroundColor));
+    setNeutralTextColor(tint(definition.neutralTextColor));
+    setNeutralBackgroundColor(tint(definition.neutralBackgroundColor));
+    setPositiveTextColor(tint(definition.positiveTextColor));
+    setPositiveBackgroundColor(tint(definition.positiveBackgroundColor));
+
+    setDefaultFont(definition.defaultFont);
+    setSmallFont(definition.smallFont);
+}
+
+bool BasicTheme::event(QEvent *event)
+{
+    if (event->type() == PlatformThemeEvents::DataChangedEvent::type) {
+        sync();
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) {
+        sync();
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) {
+        sync();
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) {
+        basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this);
+    }
+
+    if (event->type() == PlatformThemeEvents::FontChangedEvent::type) {
+        basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this);
+    }
+
+    return PlatformTheme::event(event);
+}
+
+QColor BasicTheme::tint(const QColor &color)
+{
+    switch (colorGroup()) {
+    case PlatformTheme::Inactive:
+        return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF());
+    case PlatformTheme::Disabled:
+        return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF() * 0.8);
+    default:
+        return color;
+    }
+}
+
+}
+
+#include "basictheme.moc"
diff --git a/src/libkirigami/basictheme_p.h b/src/libkirigami/basictheme_p.h
new file mode 100644 (file)
index 0000000..60d1009
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef BASICTHEME_H
+#define BASICTHEME_H
+
+#include "platformtheme.h"
+
+#include "kirigami2_export.h"
+
+namespace Kirigami
+{
+class BasicTheme;
+
+class KIRIGAMI2_EXPORT BasicThemeDefinition : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QColor textColor MEMBER textColor NOTIFY changed)
+    Q_PROPERTY(QColor disabledTextColor MEMBER disabledTextColor NOTIFY changed)
+
+    Q_PROPERTY(QColor highlightColor MEMBER highlightColor NOTIFY changed)
+    Q_PROPERTY(QColor highlightedTextColor MEMBER highlightedTextColor NOTIFY changed)
+    Q_PROPERTY(QColor backgroundColor MEMBER backgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor alternateBackgroundColor MEMBER alternateBackgroundColor NOTIFY changed)
+
+    Q_PROPERTY(QColor focusColor MEMBER focusColor NOTIFY changed)
+    Q_PROPERTY(QColor hoverColor MEMBER hoverColor NOTIFY changed)
+
+    Q_PROPERTY(QColor activeTextColor MEMBER activeTextColor NOTIFY changed)
+    Q_PROPERTY(QColor activeBackgroundColor MEMBER activeBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor linkColor MEMBER linkColor NOTIFY changed)
+    Q_PROPERTY(QColor linkBackgroundColor MEMBER linkBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor visitedLinkColor MEMBER visitedLinkColor NOTIFY changed)
+    Q_PROPERTY(QColor visitedLinkBackgroundColor MEMBER visitedLinkBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor negativeTextColor MEMBER negativeTextColor NOTIFY changed)
+    Q_PROPERTY(QColor negativeBackgroundColor MEMBER negativeBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor neutralTextColor MEMBER neutralTextColor NOTIFY changed)
+    Q_PROPERTY(QColor neutralBackgroundColor MEMBER neutralBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor positiveTextColor MEMBER positiveTextColor NOTIFY changed)
+    Q_PROPERTY(QColor positiveBackgroundColor MEMBER positiveBackgroundColor NOTIFY changed)
+
+    Q_PROPERTY(QColor buttonTextColor MEMBER buttonTextColor NOTIFY changed)
+    Q_PROPERTY(QColor buttonBackgroundColor MEMBER buttonBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor buttonAlternateBackgroundColor MEMBER buttonAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor buttonHoverColor MEMBER buttonHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor buttonFocusColor MEMBER buttonFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QColor viewTextColor MEMBER viewTextColor NOTIFY changed)
+    Q_PROPERTY(QColor viewBackgroundColor MEMBER viewBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor viewAlternateBackgroundColor MEMBER viewAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor viewHoverColor MEMBER viewHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor viewFocusColor MEMBER viewFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QColor selectionTextColor MEMBER selectionTextColor NOTIFY changed)
+    Q_PROPERTY(QColor selectionBackgroundColor MEMBER selectionBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor selectionAlternateBackgroundColor MEMBER selectionAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor selectionHoverColor MEMBER selectionHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor selectionFocusColor MEMBER selectionFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QColor tooltipTextColor MEMBER tooltipTextColor NOTIFY changed)
+    Q_PROPERTY(QColor tooltipBackgroundColor MEMBER tooltipBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor tooltipAlternateBackgroundColor MEMBER tooltipAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor tooltipHoverColor MEMBER tooltipHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor tooltipFocusColor MEMBER tooltipFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QColor complementaryTextColor MEMBER complementaryTextColor NOTIFY changed)
+    Q_PROPERTY(QColor complementaryBackgroundColor MEMBER complementaryBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor complementaryAlternateBackgroundColor MEMBER complementaryAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor complementaryHoverColor MEMBER complementaryHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor complementaryFocusColor MEMBER complementaryFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QColor headerTextColor MEMBER headerTextColor NOTIFY changed)
+    Q_PROPERTY(QColor headerBackgroundColor MEMBER headerBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor headerAlternateBackgroundColor MEMBER headerAlternateBackgroundColor NOTIFY changed)
+    Q_PROPERTY(QColor headerHoverColor MEMBER headerHoverColor NOTIFY changed)
+    Q_PROPERTY(QColor headerFocusColor MEMBER headerFocusColor NOTIFY changed)
+
+    Q_PROPERTY(QFont defaultFont MEMBER defaultFont NOTIFY changed)
+    Q_PROPERTY(QFont smallFont MEMBER smallFont NOTIFY changed)
+
+public:
+    explicit BasicThemeDefinition(QObject *parent = nullptr);
+
+    virtual void syncToQml(PlatformTheme *object);
+
+    QColor textColor = QColor{"#31363b"};
+    QColor disabledTextColor = QColor{"#9931363b"};
+
+    QColor highlightColor = QColor{"#2196F3"};
+    QColor highlightedTextColor = QColor{"#eff0fa"};
+    QColor backgroundColor = QColor{"#eff0f1"};
+    QColor alternateBackgroundColor = QColor{"#bdc3c7"};
+
+    QColor focusColor = QColor{"#2196F3"};
+    QColor hoverColor = QColor{"#2196F3"};
+
+    QColor activeTextColor = QColor{"#0176D3"};
+    QColor activeBackgroundColor = QColor{"#0176D3"};
+    QColor linkColor = QColor{"#2196F3"};
+    QColor linkBackgroundColor = QColor{"#2196F3"};
+    QColor visitedLinkColor = QColor{"#2196F3"};
+    QColor visitedLinkBackgroundColor = QColor{"#2196F3"};
+    QColor negativeTextColor = QColor{"#DA4453"};
+    QColor negativeBackgroundColor = QColor{"#DA4453"};
+    QColor neutralTextColor = QColor{"#F67400"};
+    QColor neutralBackgroundColor = QColor{"#F67400"};
+    QColor positiveTextColor = QColor{"#27AE60"};
+    QColor positiveBackgroundColor = QColor{"#27AE60"};
+
+    QColor buttonTextColor = QColor{"#31363b"};
+    QColor buttonBackgroundColor = QColor{"#eff0f1"};
+    QColor buttonAlternateBackgroundColor = QColor{"#bdc3c7"};
+    QColor buttonHoverColor = QColor{"#2196F3"};
+    QColor buttonFocusColor = QColor{"#2196F3"};
+
+    QColor viewTextColor = QColor{"#31363b"};
+    QColor viewBackgroundColor = QColor{"#fcfcfc"};
+    QColor viewAlternateBackgroundColor = QColor{"#eff0f1"};
+    QColor viewHoverColor = QColor{"#2196F3"};
+    QColor viewFocusColor = QColor{"#2196F3"};
+
+    QColor selectionTextColor = QColor{"#eff0fa"};
+    QColor selectionBackgroundColor = QColor{"#2196F3"};
+    QColor selectionAlternateBackgroundColor = QColor{"#1d99f3"};
+    QColor selectionHoverColor = QColor{"#2196F3"};
+    QColor selectionFocusColor = QColor{"#2196F3"};
+
+    QColor tooltipTextColor = QColor{"#eff0f1"};
+    QColor tooltipBackgroundColor = QColor{"#31363b"};
+    QColor tooltipAlternateBackgroundColor = QColor{"#4d4d4d"};
+    QColor tooltipHoverColor = QColor{"#2196F3"};
+    QColor tooltipFocusColor = QColor{"#2196F3"};
+
+    QColor complementaryTextColor = QColor{"#eff0f1"};
+    QColor complementaryBackgroundColor = QColor{"#31363b"};
+    QColor complementaryAlternateBackgroundColor = QColor{"#3b4045"};
+    QColor complementaryHoverColor = QColor{"#2196F3"};
+    QColor complementaryFocusColor = QColor{"#2196F3"};
+
+    QColor headerTextColor = QColor{"#232629"};
+    QColor headerBackgroundColor = QColor{"#e3e5e7"};
+    QColor headerAlternateBackgroundColor = QColor{"#eff0f1"};
+    QColor headerHoverColor = QColor{"#2196F3"};
+    QColor headerFocusColor = QColor{"#93cee9"};
+
+    QFont defaultFont;
+    QFont smallFont;
+
+    Q_SIGNAL void changed();
+    Q_SIGNAL void sync(QQuickItem *object);
+};
+
+class BasicThemeInstance : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit BasicThemeInstance(QObject *parent = nullptr);
+
+    BasicThemeDefinition &themeDefinition(QQmlEngine *engine);
+
+    QVector<BasicTheme *> watchers;
+
+private:
+    void onDefinitionChanged();
+
+    std::unique_ptr<BasicThemeDefinition> m_themeDefinition;
+};
+
+class BasicTheme : public PlatformTheme
+{
+    Q_OBJECT
+
+public:
+    explicit BasicTheme(QObject *parent = nullptr);
+    ~BasicTheme() override;
+
+    void sync();
+
+protected:
+    bool event(QEvent *event) override;
+
+private:
+    QColor tint(const QColor &color);
+};
+
+}
+
+#endif // BASICTHEME_H
diff --git a/src/libkirigami/compatheader.h.in b/src/libkirigami/compatheader.h.in
new file mode 100644 (file)
index 0000000..de61efb
--- /dev/null
@@ -0,0 +1,10 @@
+#include <kirigami/kirigami2_export.h>
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 91)
+#   include <kirigami/@HEADER_NAME@>
+#   if KIRIGAMI2_DEPRECATED_WARNINGS_SINCE >= 0x055b00
+#       pragma message("Deprecated header. Since 5.91, use #include <@NEW_INCLUDE@> instead")
+#   endif
+#else
+#   error "Include of deprecated header is disabled"
+#endif
diff --git a/src/libkirigami/kirigamipluginfactory.cpp b/src/libkirigami/kirigamipluginfactory.cpp
new file mode 100644 (file)
index 0000000..3bcd410
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "kirigamipluginfactory.h"
+
+#include <QDebug>
+
+#include <QDir>
+#include <QQuickStyle>
+#include <QPluginLoader>
+
+#include "styleselector_p.h"
+#include "units.h"
+
+#include "loggingcategory.h"
+
+namespace Kirigami {
+
+KirigamiPluginFactory::KirigamiPluginFactory(QObject *parent)
+    : QObject(parent)
+{
+}
+
+KirigamiPluginFactory::~KirigamiPluginFactory() = default;
+
+KirigamiPluginFactory *KirigamiPluginFactory::findPlugin()
+{
+    static KirigamiPluginFactory *pluginFactory = nullptr;
+
+    //check for the plugin only once: it's an heavy operation
+    if (pluginFactory) {
+        return pluginFactory;
+    }
+
+    static bool s_factoryChecked = false;
+
+    if (!s_factoryChecked) {
+        s_factoryChecked = true;
+
+        #ifdef KIRIGAMI_BUILD_TYPE_STATIC
+        for (QObject *staticPlugin : QPluginLoader::staticInstances()) {
+            KirigamiPluginFactory *factory = qobject_cast<KirigamiPluginFactory *>(staticPlugin);
+            if (factory) {
+                pluginFactory = factory;
+            }
+        }
+        #else
+        const auto libraryPaths = QCoreApplication::libraryPaths();
+        for (const QString &path : libraryPaths) {
+            #ifdef Q_OS_ANDROID
+            QDir dir(path);
+            #else
+            QDir dir(path + QStringLiteral("/kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/kirigami"));
+            #endif
+            const auto fileNames = dir.entryList(QDir::Files);
+
+            for (const QString &fileName : fileNames) {
+
+#ifdef Q_OS_ANDROID
+                if (fileName.startsWith(QStringLiteral("libplugins_kf" QT_STRINGIFY(QT_VERSION_MAJOR) "_kirigami_")) && QLibrary::isLibrary(fileName)) {
+#endif
+                    // TODO: env variable?
+                    if (!QQuickStyle::name().isEmpty() && fileName.contains(QQuickStyle::name())) {
+                        QPluginLoader loader(dir.absoluteFilePath(fileName));
+                        QObject *plugin = loader.instance();
+                        // TODO: load actually a factory as plugin
+
+                        qCDebug(KirigamiLog) << "Loading style plugin from" << dir.absoluteFilePath(fileName);
+
+                        KirigamiPluginFactory *factory = qobject_cast<KirigamiPluginFactory *>(plugin);
+                        if (factory) {
+                            pluginFactory = factory;
+                            break;
+                        }
+                    }
+#ifdef Q_OS_ANDROID
+                }
+#endif
+            }
+
+            // Ensure we only load the first plugin from the first plugin location.
+            // If we do not break here, we may end up loading a different plugin
+            // in place of the first one.
+            if (pluginFactory) {
+                break;
+            }
+        }
+#endif
+    }
+
+    return pluginFactory;
+}
+
+KirigamiPluginFactoryV2::KirigamiPluginFactoryV2(QObject *parent)
+    : KirigamiPluginFactory(parent)
+{
+}
+
+KirigamiPluginFactoryV2::~KirigamiPluginFactoryV2() = default;
+}
+
+#include "moc_kirigamipluginfactory.cpp"
diff --git a/src/libkirigami/kirigamipluginfactory.h b/src/libkirigami/kirigamipluginfactory.h
new file mode 100644 (file)
index 0000000..1c3cda7
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMI_KIRIGAMIPLUGINFACTORY_H
+#define KIRIGAMI_KIRIGAMIPLUGINFACTORY_H
+
+#include "platformtheme.h"
+#include <QObject>
+
+#ifndef KIRIGAMI_BUILD_TYPE_STATIC
+#include "kirigami2_export.h"
+#endif
+
+class QQmlEngine;
+
+namespace Kirigami {
+class Units;
+
+/**
+ * @class KirigamiPluginFactory kirigamipluginfactory.h <Kirigami/KirigamiPluginFactory>
+ *
+ * This class is reimpleented by plugins to provide different implementations
+ * of PlatformTheme
+ */
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+class KirigamiPluginFactory : public QObject
+#else
+class KIRIGAMI2_EXPORT KirigamiPluginFactory : public QObject
+#endif
+{
+    Q_OBJECT
+
+public:
+    explicit KirigamiPluginFactory(QObject *parent = nullptr);
+    ~KirigamiPluginFactory() override;
+
+    /**
+     * Creates an instance of PlatformTheme which can come out from
+     * an implementation provided by a plugin
+     *
+     * If this returns @c nullptr the PlatformTheme will use a fallback
+     * implementation that loads a theme definition from a QML file.
+     *
+     * @param parent the parent object of the created PlatformTheme
+     */
+    virtual PlatformTheme *createPlatformTheme(QObject *parent) = 0;
+
+    /**
+     * finds the plugin providing units and platformtheme for the current style
+     * The plugin pointer is cached, so only the first call is a potentially heavy operation
+     * @return pointer to the KirigamiPluginFactory of the current style
+     */
+    static KirigamiPluginFactory *findPlugin();
+};
+
+// TODO KF6 unify KirigamiPluginFactory and KirigamiPluginFactoryV2 again
+/**
+ * This class provides an extended version of KirigamiPluginFactory.
+ * Plugins that support Units need to implement it instead of KirigamiPluginFactory.
+ */
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+class KirigamiPluginFactoryV2 : public KirigamiPluginFactory
+#else
+class KIRIGAMI2_EXPORT KirigamiPluginFactoryV2 : public KirigamiPluginFactory
+#endif
+{
+    Q_OBJECT
+
+public:
+    explicit KirigamiPluginFactoryV2(QObject *parent = nullptr);
+    ~KirigamiPluginFactoryV2() override;
+
+    /**
+     * Creates an instance of Units which can come from an implementation
+     * provided by a plugin
+     * @param parent the parent of the units object
+     */
+    virtual Units *createUnits(QObject *parent) = 0;
+};
+}
+
+QT_BEGIN_NAMESPACE
+#define KirigamiPluginFactory_iid "org.kde.kirigami.KirigamiPluginFactory"
+Q_DECLARE_INTERFACE(Kirigami::KirigamiPluginFactory, KirigamiPluginFactory_iid)
+QT_END_NAMESPACE
+
+#endif // KIRIGAMIPLUGINFACTORY_H
diff --git a/src/libkirigami/org.kde.KWin.TabletModeManager.xml b/src/libkirigami/org.kde.KWin.TabletModeManager.xml
new file mode 100644 (file)
index 0000000..1daf5f0
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.KWin.TabletModeManager">
+    <property name="tabletModeAvailable" type="b" access="read"/>
+    <property name="tabletMode" type="b" access="read"/>
+    <signal name="tabletModeAvailableChanged">
+      <arg name="tabletModeAvailable" type="b" direction="out"/>
+    </signal>
+    <signal name="tabletModeChanged">
+      <arg name="tabletMode" type="b" direction="out"/>
+    </signal>
+  </interface>
+</node>
diff --git a/src/libkirigami/org.kde.KWin.VirtualKeyboard.xml b/src/libkirigami/org.kde.KWin.VirtualKeyboard.xml
new file mode 100644 (file)
index 0000000..e0a6650
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.kwin.VirtualKeyboard">
+    <property name="available" type="b" access="read"/>
+    <property name="enabled" type="b" access="readwrite"/>
+    <property name="active" type="b" access="readwrite"/>
+    <property name="visible" type="b" access="read"/>
+
+    <method name="willShowOnActive">
+      <arg name="show" type="b" direction="out"/>
+    </method>
+
+    <signal name="enabledChanged">
+    </signal>
+    <signal name="activeChanged">
+    </signal>
+    <signal name="visibleChanged">
+    </signal>
+    <signal name="availableChanged">
+    </signal>
+  </interface>
+  <interface name="org.freedesktop.DBus.Properties">
+    <method name="Get">
+      <arg name="interface_name" type="s" direction="in"/>
+      <arg name="property_name" type="s" direction="in"/>
+      <arg name="value" type="v" direction="out"/>
+    </method>
+    <method name="Set">
+      <arg name="interface_name" type="s" direction="in"/>
+      <arg name="property_name" type="s" direction="in"/>
+      <arg name="value" type="v" direction="in"/>
+    </method>
+    <method name="GetAll">
+      <arg name="interface_name" type="s" direction="in"/>
+      <arg name="values" type="a{sv}" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
+    </method>
+    <signal name="PropertiesChanged">
+      <arg name="interface_name" type="s" direction="out"/>
+      <arg name="changed_properties" type="a{sv}" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
+      <arg name="invalidated_properties" type="as" direction="out"/>
+    </signal>
+  </interface>
+</node>
diff --git a/src/libkirigami/platformtheme.cpp b/src/libkirigami/platformtheme.cpp
new file mode 100644 (file)
index 0000000..a087ddc
--- /dev/null
@@ -0,0 +1,1006 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "platformtheme.h"
+#include "basictheme_p.h"
+#include "kirigamipluginfactory.h"
+#include <QDebug>
+#include <QDir>
+#include <QGuiApplication>
+#include <QPluginLoader>
+#include <QPointer>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QQuickStyle>
+#include <QQuickWindow>
+
+#include <array>
+#include <cinttypes>
+#include <functional>
+#include <memory>
+#include <unordered_map>
+
+namespace Kirigami
+{
+template<>
+KIRIGAMI2_EXPORT QEvent::Type PlatformThemeEvents::DataChangedEvent::type = QEvent::None;
+template<>
+KIRIGAMI2_EXPORT QEvent::Type PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::None;
+template<>
+KIRIGAMI2_EXPORT QEvent::Type PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::None;
+template<>
+KIRIGAMI2_EXPORT QEvent::Type PlatformThemeEvents::ColorChangedEvent::type = QEvent::None;
+template<>
+KIRIGAMI2_EXPORT QEvent::Type PlatformThemeEvents::FontChangedEvent::type = QEvent::None;
+
+// Initialize event types.
+// We want to avoid collisions with application event types so we should use
+// registerEventType for generating the event types. Unfortunately, that method
+// is not constexpr so we need to call it somewhere during application startup.
+// This struct handles that.
+struct TypeInitializer {
+    TypeInitializer()
+    {
+        PlatformThemeEvents::DataChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+        PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+        PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+        PlatformThemeEvents::ColorChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+        PlatformThemeEvents::FontChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+    }
+};
+static TypeInitializer initializer;
+
+// This class encapsulates the actual data of the Theme object. It may be shared
+// among several instances of PlatformTheme, to ensure that the memory usage of
+// PlatformTheme stays low.
+class PlatformThemeData : public QObject
+{
+    Q_OBJECT
+
+public:
+    // An enum for all colors in PlatformTheme.
+    // This is used so we can have a QHash of local overrides in the
+    // PlatformTheme, which avoids needing to store all these colors in
+    // PlatformTheme even when they're not used.
+    enum ColorRole {
+        TextColor,
+        DisabledTextColor,
+        HighlightedTextColor,
+        ActiveTextColor,
+        LinkColor,
+        VisitedLinkColor,
+        NegativeTextColor,
+        NeutralTextColor,
+        PositiveTextColor,
+        BackgroundColor,
+        AlternateBackgroundColor,
+        HighlightColor,
+        ActiveBackgroundColor,
+        LinkBackgroundColor,
+        VisitedLinkBackgroundColor,
+        NegativeBackgroundColor,
+        NeutralBackgroundColor,
+        PositiveBackgroundColor,
+        FocusColor,
+        HoverColor,
+
+        // This should always be the last item. It indicates how many items
+        // there are and is used for the storage array below.
+        ColorRoleCount,
+    };
+
+    using ColorMap = std::unordered_map<std::underlying_type<ColorRole>::type, QColor>;
+
+    // Which PlatformTheme instance "owns" this data object. Only the owner is
+    // allowed to make changes to data.
+    QPointer<PlatformTheme> owner;
+
+    PlatformTheme::ColorSet colorSet = PlatformTheme::Window;
+    PlatformTheme::ColorGroup colorGroup = PlatformTheme::Active;
+
+    std::array<QColor, ColorRoleCount> colors;
+
+    QFont defaultFont;
+    QFont smallFont;
+
+    QPalette palette;
+
+    // A list of PlatformTheme instances that want to be notified when the data
+    // changes. This is used instead of signal/slots as this way we only store
+    // a little bit of data and that data is shared among instances, whereas
+    // signal/slots turn out to have a pretty large memory overhead per instance.
+    using Watcher = PlatformTheme *;
+    QVector<Watcher> watchers;
+
+    inline void setColorSet(PlatformTheme *sender, PlatformTheme::ColorSet set)
+    {
+        if (sender != owner || colorSet == set) {
+            return;
+        }
+
+        auto oldValue = colorSet;
+
+        colorSet = set;
+
+        notifyWatchers<PlatformTheme::ColorSet>(sender, oldValue, set);
+    }
+
+    inline void setColorGroup(PlatformTheme *sender, PlatformTheme::ColorGroup group)
+    {
+        if (sender != owner || colorGroup == group) {
+            return;
+        }
+
+        auto oldValue = colorGroup;
+
+        colorGroup = group;
+        palette.setCurrentColorGroup(QPalette::ColorGroup(group));
+
+        notifyWatchers<PlatformTheme::ColorGroup>(sender, oldValue, group);
+    }
+
+    inline void setColor(PlatformTheme *sender, ColorRole role, const QColor &color)
+    {
+        if (sender != owner || colors[role] == color) {
+            return;
+        }
+
+        auto oldValue = colors[role];
+
+        colors[role] = color;
+        updatePalette(palette, colors);
+
+        notifyWatchers<QColor>(sender, oldValue, colors[role]);
+    }
+
+    inline void setDefaultFont(PlatformTheme *sender, const QFont &font)
+    {
+        if (sender != owner || font == defaultFont) {
+            return;
+        }
+
+        auto oldValue = defaultFont;
+
+        defaultFont = font;
+
+        notifyWatchers<QFont>(sender, oldValue, font);
+    }
+
+    inline void setSmallFont(PlatformTheme *sender, const QFont &font)
+    {
+        if (sender != owner || font == smallFont) {
+            return;
+        }
+
+        auto oldValue = smallFont;
+
+        smallFont = font;
+
+        notifyWatchers<QFont>(sender, oldValue, smallFont);
+    }
+
+    inline void addChangeWatcher(PlatformTheme *object)
+    {
+        watchers.append(object);
+    }
+
+    inline void removeChangeWatcher(PlatformTheme *object)
+    {
+        watchers.removeOne(object);
+    }
+
+    template<typename T>
+    inline void notifyWatchers(PlatformTheme *sender, const T &oldValue, const T &newValue)
+    {
+        for (auto object : std::as_const(watchers)) {
+            PlatformThemeEvents::PropertyChangedEvent<T> event(sender, oldValue, newValue);
+            QCoreApplication::sendEvent(object, &event);
+        }
+    }
+
+    // Update a palette from a list of colors.
+    inline static void updatePalette(QPalette &palette, const std::array<QColor, ColorRoleCount> &colors)
+    {
+        for (std::size_t i = 0; i < colors.size(); ++i) {
+            setPaletteColor(palette, ColorRole(i), colors.at(i));
+        }
+    }
+
+    // Update a palette from a hash of colors.
+    inline static void updatePalette(QPalette &palette, const ColorMap &colors)
+    {
+        for (auto entry : colors) {
+            setPaletteColor(palette, ColorRole(entry.first), entry.second);
+        }
+    }
+
+    inline static void setPaletteColor(QPalette &palette, ColorRole role, const QColor &color)
+    {
+        switch (role) {
+        case TextColor:
+            palette.setColor(QPalette::Text, color);
+            palette.setColor(QPalette::WindowText, color);
+            palette.setColor(QPalette::ButtonText, color);
+            break;
+        case BackgroundColor:
+            palette.setColor(QPalette::Window, color);
+            palette.setColor(QPalette::Base, color);
+            palette.setColor(QPalette::Button, color);
+            break;
+        case AlternateBackgroundColor:
+            palette.setColor(QPalette::AlternateBase, color);
+            break;
+        case HighlightColor:
+            palette.setColor(QPalette::Highlight, color);
+            break;
+        case HighlightedTextColor:
+            palette.setColor(QPalette::HighlightedText, color);
+            break;
+        case LinkColor:
+            palette.setColor(QPalette::Link, color);
+            break;
+        case VisitedLinkColor:
+            palette.setColor(QPalette::LinkVisited, color);
+            break;
+
+        default:
+            break;
+        }
+    }
+};
+
+class PlatformThemePrivate
+{
+public:
+    PlatformThemePrivate()
+        : inherit(true)
+        , supportsIconColoring(false)
+        , pendingColorChange(false)
+        , pendingChildUpdate(false)
+        , colorSet(PlatformTheme::Window)
+        , colorGroup(PlatformTheme::Active)
+    {
+    }
+
+    inline QColor color(const PlatformTheme *theme, PlatformThemeData::ColorRole color) const
+    {
+        if (!data) {
+            return QColor{};
+        }
+
+        QColor value = data->colors.at(color);
+
+        if (data->owner != theme && localOverrides) {
+            auto itr = localOverrides->find(color);
+            if (itr != localOverrides->end()) {
+                value = itr->second;
+            }
+        }
+
+        return value;
+    }
+
+    inline void setColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value)
+    {
+        if (!localOverrides) {
+            localOverrides = std::make_unique<PlatformThemeData::ColorMap>();
+        }
+
+        if (!value.isValid()) {
+            // Invalid color, assume we are resetting the value.
+            auto itr = localOverrides->find(color);
+            if (itr != localOverrides->end()) {
+                localOverrides->erase(itr);
+
+                if (data) {
+                    // TODO: Find a better way to determine "default" color.
+                    // Right now this sets the color to transparent to force a
+                    // color change and relies on the style-specific subclass to
+                    // handle resetting the actual color.
+                    data->setColor(theme, color, Qt::transparent);
+                }
+
+                emitCompressedColorChanged(theme);
+            }
+
+            return;
+        }
+
+        auto itr = localOverrides->find(color);
+        if (itr != localOverrides->end() && itr->second == value && (data && data->owner != theme)) {
+            return;
+        }
+
+        (*localOverrides)[color] = value;
+
+        if (data) {
+            data->setColor(theme, color, value);
+        }
+
+        emitCompressedColorChanged(theme);
+    }
+
+    inline void setDataColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value)
+    {
+        // Only set color if we have no local override of the color.
+        // This is done because colorSet/colorGroup changes will trigger most
+        // subclasses to reevaluate and reset the colors, breaking any local
+        // overrides we have.
+        if (localOverrides) {
+            auto itr = localOverrides->find(color);
+            if (itr != localOverrides->end()) {
+                return;
+            }
+        }
+
+        if (data) {
+            data->setColor(theme, color, value);
+        }
+    }
+
+    inline void emitCompressedColorChanged(PlatformTheme *theme)
+    {
+        if (pendingColorChange) {
+            return;
+        }
+
+        pendingColorChange = true;
+        QMetaObject::invokeMethod(theme, &PlatformTheme::emitColorChanged, Qt::QueuedConnection);
+    }
+
+    inline void queueChildUpdate(PlatformTheme *theme)
+    {
+        if (pendingChildUpdate) {
+            return;
+        }
+
+        pendingChildUpdate = true;
+        QMetaObject::invokeMethod(
+            theme,
+            [this, theme]() {
+                pendingChildUpdate = false;
+                theme->updateChildren(theme->parent());
+            },
+            Qt::QueuedConnection);
+    }
+
+    /*
+     * Please note that there is no q pointer. This is intentional, as it avoids
+     * having to store that information for each instance of PlatformTheme,
+     * saving us 8 bytes per instance. Instead, we pass the theme object as
+     * first parameter of each method. This is a little uglier but essentially
+     * works the same without needing memory.
+     */
+
+    // An instance of the data object. This is potentially shared with many
+    // instances of PlatformTheme.
+    std::shared_ptr<PlatformThemeData> data;
+    // Used to store color overrides of inherited data. This is created on
+    // demand and will only exist if we actually have local overrides.
+    std::unique_ptr<PlatformThemeData::ColorMap> localOverrides;
+
+    bool inherit : 1;
+    bool supportsIconColoring : 1; // TODO KF6: Remove in favour of virtual method
+    bool pendingColorChange : 1;
+    bool pendingChildUpdate : 1;
+
+    // Note: We use these to store local values of PlatformTheme::ColorSet and
+    // PlatformTheme::ColorGroup. While these are standard enums and thus 32
+    // bits they only contain a few items so we store the value in only 4 bits
+    // to save space.
+    uint8_t colorSet : 4;
+    uint8_t colorGroup : 4;
+
+    // Ensure the above assumption holds. Should this static assert fail, the
+    // bit size above needs to be adjusted.
+    static_assert(PlatformTheme::ColorGroupCount <= 16, "PlatformTheme::ColorGroup contains more elements than can be stored in PlatformThemePrivate");
+    static_assert(PlatformTheme::ColorSetCount <= 16, "PlatformTheme::ColorSet contains more elements than can be stored in PlatformThemePrivate");
+
+    static KirigamiPluginFactory *s_pluginFactory;
+};
+
+KirigamiPluginFactory *PlatformThemePrivate::s_pluginFactory = nullptr;
+
+PlatformTheme::PlatformTheme(QObject *parent)
+    : QObject(parent)
+    , d(new PlatformThemePrivate)
+{
+    if (QQuickItem *item = qobject_cast<QQuickItem *>(parent)) {
+        connect(item, &QQuickItem::windowChanged, this, &PlatformTheme::update);
+        connect(item, &QQuickItem::parentChanged, this, &PlatformTheme::update);
+    }
+
+    update();
+}
+
+PlatformTheme::~PlatformTheme()
+{
+    if (d->data) {
+        d->data->removeChangeWatcher(this);
+    }
+
+    delete d;
+}
+
+void PlatformTheme::setColorSet(PlatformTheme::ColorSet colorSet)
+{
+    d->colorSet = colorSet;
+
+    if (d->data) {
+        d->data->setColorSet(this, colorSet);
+    }
+}
+
+PlatformTheme::ColorSet PlatformTheme::colorSet() const
+{
+    return d->data ? d->data->colorSet : Window;
+}
+
+void PlatformTheme::setColorGroup(PlatformTheme::ColorGroup colorGroup)
+{
+    d->colorGroup = colorGroup;
+
+    if (d->data) {
+        d->data->setColorGroup(this, colorGroup);
+    }
+}
+
+PlatformTheme::ColorGroup PlatformTheme::colorGroup() const
+{
+    return d->data ? d->data->colorGroup : Active;
+}
+
+bool PlatformTheme::inherit() const
+{
+    return d->inherit;
+}
+
+void PlatformTheme::setInherit(bool inherit)
+{
+    if (inherit == d->inherit) {
+        return;
+    }
+
+    d->inherit = inherit;
+    update();
+
+    Q_EMIT inheritChanged(inherit);
+}
+
+QColor PlatformTheme::textColor() const
+{
+    return d->color(this, PlatformThemeData::TextColor);
+}
+
+QColor PlatformTheme::disabledTextColor() const
+{
+    return d->color(this, PlatformThemeData::DisabledTextColor);
+}
+
+QColor PlatformTheme::highlightColor() const
+{
+    return d->color(this, PlatformThemeData::HighlightColor);
+}
+
+QColor PlatformTheme::highlightedTextColor() const
+{
+    return d->color(this, PlatformThemeData::HighlightedTextColor);
+}
+
+QColor PlatformTheme::backgroundColor() const
+{
+    return d->color(this, PlatformThemeData::BackgroundColor);
+}
+
+QColor PlatformTheme::alternateBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::AlternateBackgroundColor);
+}
+
+QColor PlatformTheme::activeTextColor() const
+{
+    return d->color(this, PlatformThemeData::ActiveTextColor);
+}
+
+QColor PlatformTheme::activeBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::ActiveBackgroundColor);
+}
+
+QColor PlatformTheme::linkColor() const
+{
+    return d->color(this, PlatformThemeData::LinkColor);
+}
+
+QColor PlatformTheme::linkBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::LinkBackgroundColor);
+}
+
+QColor PlatformTheme::visitedLinkColor() const
+{
+    return d->color(this, PlatformThemeData::VisitedLinkColor);
+}
+
+QColor PlatformTheme::visitedLinkBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::VisitedLinkBackgroundColor);
+}
+
+QColor PlatformTheme::negativeTextColor() const
+{
+    return d->color(this, PlatformThemeData::NegativeTextColor);
+}
+
+QColor PlatformTheme::negativeBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::NegativeBackgroundColor);
+}
+
+QColor PlatformTheme::neutralTextColor() const
+{
+    return d->color(this, PlatformThemeData::NeutralTextColor);
+}
+
+QColor PlatformTheme::neutralBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::NeutralBackgroundColor);
+}
+
+QColor PlatformTheme::positiveTextColor() const
+{
+    return d->color(this, PlatformThemeData::PositiveTextColor);
+}
+
+QColor PlatformTheme::positiveBackgroundColor() const
+{
+    return d->color(this, PlatformThemeData::PositiveBackgroundColor);
+}
+
+QColor PlatformTheme::focusColor() const
+{
+    return d->color(this, PlatformThemeData::FocusColor);
+}
+
+QColor PlatformTheme::hoverColor() const
+{
+    return d->color(this, PlatformThemeData::HoverColor);
+}
+
+// setters for theme implementations
+void PlatformTheme::setTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::TextColor, color);
+}
+
+void PlatformTheme::setDisabledTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::DisabledTextColor, color);
+}
+
+void PlatformTheme::setBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::BackgroundColor, color);
+}
+
+void PlatformTheme::setAlternateBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::AlternateBackgroundColor, color);
+}
+
+void PlatformTheme::setHighlightColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::HighlightColor, color);
+}
+
+void PlatformTheme::setHighlightedTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::HighlightedTextColor, color);
+}
+
+void PlatformTheme::setActiveTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::ActiveTextColor, color);
+}
+
+void PlatformTheme::setActiveBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::ActiveBackgroundColor, color);
+}
+
+void PlatformTheme::setLinkColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::LinkColor, color);
+}
+
+void PlatformTheme::setLinkBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::LinkBackgroundColor, color);
+}
+
+void PlatformTheme::setVisitedLinkColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::VisitedLinkColor, color);
+}
+
+void PlatformTheme::setVisitedLinkBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::VisitedLinkBackgroundColor, color);
+}
+
+void PlatformTheme::setNegativeTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::NegativeTextColor, color);
+}
+
+void PlatformTheme::setNegativeBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::NegativeBackgroundColor, color);
+}
+
+void PlatformTheme::setNeutralTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::NeutralTextColor, color);
+}
+
+void PlatformTheme::setNeutralBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::NeutralBackgroundColor, color);
+}
+
+void PlatformTheme::setPositiveTextColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::PositiveTextColor, color);
+}
+
+void PlatformTheme::setPositiveBackgroundColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::PositiveBackgroundColor, color);
+}
+
+void PlatformTheme::setHoverColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::HoverColor, color);
+}
+
+void PlatformTheme::setFocusColor(const QColor &color)
+{
+    d->setDataColor(this, PlatformThemeData::FocusColor, color);
+}
+
+QFont PlatformTheme::defaultFont() const
+{
+    return d->data ? d->data->defaultFont : QFont{};
+}
+
+void PlatformTheme::setDefaultFont(const QFont &font)
+{
+    if (d->data) {
+        d->data->setDefaultFont(this, font);
+    }
+}
+
+QFont PlatformTheme::smallFont() const
+{
+    return d->data ? d->data->smallFont : QFont{};
+}
+
+void PlatformTheme::setSmallFont(const QFont &font)
+{
+    if (d->data) {
+        d->data->setSmallFont(this, font);
+    }
+}
+
+// setters for QML clients
+void PlatformTheme::setCustomTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::TextColor, color);
+}
+
+void PlatformTheme::setCustomDisabledTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::DisabledTextColor, color);
+}
+
+void PlatformTheme::setCustomBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::BackgroundColor, color);
+}
+
+void PlatformTheme::setCustomAlternateBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::AlternateBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomHighlightColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::HighlightColor, color);
+}
+
+void PlatformTheme::setCustomHighlightedTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::HighlightedTextColor, color);
+}
+
+void PlatformTheme::setCustomActiveTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::ActiveTextColor, color);
+}
+
+void PlatformTheme::setCustomActiveBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::ActiveBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomLinkColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::LinkColor, color);
+}
+
+void PlatformTheme::setCustomLinkBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::LinkBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomVisitedLinkColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::TextColor, color);
+}
+
+void PlatformTheme::setCustomVisitedLinkBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::VisitedLinkBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomNegativeTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::NegativeTextColor, color);
+}
+
+void PlatformTheme::setCustomNegativeBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::NegativeBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomNeutralTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::NeutralTextColor, color);
+}
+
+void PlatformTheme::setCustomNeutralBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::NeutralBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomPositiveTextColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::PositiveTextColor, color);
+}
+
+void PlatformTheme::setCustomPositiveBackgroundColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::PositiveBackgroundColor, color);
+}
+
+void PlatformTheme::setCustomHoverColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::HoverColor, color);
+}
+
+void PlatformTheme::setCustomFocusColor(const QColor &color)
+{
+    d->setColor(this, PlatformThemeData::FocusColor, color);
+}
+
+QPalette PlatformTheme::palette() const
+{
+    if (!d->data) {
+        return QPalette{};
+    }
+
+    auto palette = d->data->palette;
+
+    if (d->localOverrides) {
+        PlatformThemeData::updatePalette(palette, *d->localOverrides);
+    }
+
+    return palette;
+}
+
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 80)
+void PlatformTheme::setPalette(const QPalette &palette)
+{
+    Q_UNUSED(palette);
+}
+#endif
+
+QIcon PlatformTheme::iconFromTheme(const QString &name, const QColor &customColor)
+{
+    Q_UNUSED(customColor);
+    QIcon icon = QIcon::fromTheme(name);
+    return icon;
+}
+
+bool PlatformTheme::supportsIconColoring() const
+{
+    return d->supportsIconColoring;
+}
+
+void PlatformTheme::setSupportsIconColoring(bool support)
+{
+    d->supportsIconColoring = support;
+}
+
+PlatformTheme *PlatformTheme::qmlAttachedProperties(QObject *object)
+{
+    auto plugin = KirigamiPluginFactory::findPlugin();
+    if (plugin) {
+        if (auto theme = plugin->createPlatformTheme(object)) {
+            return theme;
+        }
+    }
+
+    return new BasicTheme(object);
+}
+
+bool PlatformTheme::event(QEvent *event)
+{
+    if (event->type() == PlatformThemeEvents::DataChangedEvent::type) {
+        auto changeEvent = static_cast<PlatformThemeEvents::DataChangedEvent *>(event);
+
+        if (changeEvent->sender != this) {
+            return false;
+        }
+
+        if (changeEvent->oldValue) {
+            changeEvent->oldValue->removeChangeWatcher(this);
+        }
+
+        if (changeEvent->newValue) {
+            auto data = changeEvent->newValue;
+            data->addChangeWatcher(this);
+
+            Q_EMIT colorSetChanged(data->colorSet);
+            Q_EMIT colorGroupChanged(data->colorGroup);
+            Q_EMIT defaultFontChanged(data->defaultFont);
+            Q_EMIT smallFontChanged(data->smallFont);
+            d->emitCompressedColorChanged(this);
+        }
+
+        return true;
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) {
+        if (d->data) {
+            Q_EMIT colorSetChanged(d->data->colorSet);
+        }
+        return true;
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) {
+        if (d->data) {
+            Q_EMIT colorGroupChanged(d->data->colorGroup);
+        }
+        return true;
+    }
+
+    if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) {
+        d->emitCompressedColorChanged(this);
+        return true;
+    }
+
+    if (event->type() == PlatformThemeEvents::FontChangedEvent::type) {
+        if (d->data) {
+            Q_EMIT defaultFontChanged(d->data->defaultFont);
+            Q_EMIT smallFontChanged(d->data->smallFont);
+        }
+        return true;
+    }
+
+    return QObject::event(event);
+}
+
+void PlatformTheme::update()
+{
+    d->queueChildUpdate(this);
+
+    auto oldData = d->data;
+
+    if (d->inherit) {
+        QObject *candidate = parent();
+        while (true) {
+            candidate = determineParent(candidate);
+            if (!candidate) {
+                break;
+            }
+
+            auto t = static_cast<PlatformTheme *>(qmlAttachedPropertiesObject<PlatformTheme>(candidate, false));
+            if (t && t->d->data && t->d->data->owner == t) {
+                if (d->data == t->d->data) {
+                    // Inheritance is already correct, do nothing.
+                    return;
+                }
+
+                d->data = t->d->data;
+
+                PlatformThemeEvents::DataChangedEvent event{this, oldData, t->d->data};
+                QCoreApplication::sendEvent(this, &event);
+
+                return;
+            }
+        }
+    } else if (d->data->owner != this) {
+        // Inherit has changed and we no longer want to inherit, clear the data
+        // so it is recreated below.
+        d->data = nullptr;
+    }
+
+    if (!d->data) {
+        d->data = std::make_shared<PlatformThemeData>();
+        d->data->owner = this;
+        d->data->setColorSet(this, static_cast<ColorSet>(d->colorSet));
+        d->data->setColorGroup(this, static_cast<ColorGroup>(d->colorGroup));
+    }
+
+    if (d->localOverrides) {
+        for (auto entry : *d->localOverrides) {
+            d->data->setColor(this, PlatformThemeData::ColorRole(entry.first), entry.second);
+        }
+    }
+
+    PlatformThemeEvents::DataChangedEvent event{this, oldData, d->data};
+    QCoreApplication::sendEvent(this, &event);
+}
+
+void PlatformTheme::updateChildren(QObject *object)
+{
+    if (!object) {
+        return;
+    }
+
+    const auto children = object->children();
+    for (auto child : children) {
+        auto t = static_cast<PlatformTheme *>(qmlAttachedPropertiesObject<PlatformTheme>(child, false));
+        if (t) {
+            t->update();
+        } else {
+            updateChildren(child);
+        }
+    }
+}
+
+void PlatformTheme::emitColorChanged()
+{
+    if (d->data) {
+        Q_EMIT paletteChanged(d->data->palette);
+    }
+
+    Q_EMIT colorsChanged();
+    d->pendingColorChange = false;
+}
+
+// We sometimes set theme properties on non-visual objects. However, if an item
+// has a visual and a non-visual parent that are different, we should prefer the
+// visual parent, so we need to apply some extra logic.
+QObject *PlatformTheme::determineParent(QObject *object)
+{
+    if (!object) {
+        return nullptr;
+    }
+
+    auto item = qobject_cast<QQuickItem *>(object);
+    if (item) {
+        return item->parentItem();
+    } else {
+        return object->parent();
+    }
+}
+
+}
+
+#include "platformtheme.moc"
diff --git a/src/libkirigami/platformtheme.h b/src/libkirigami/platformtheme.h
new file mode 100644 (file)
index 0000000..97ac858
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMI_PLATFORMTHEME_H
+#define KIRIGAMI_PLATFORMTHEME_H
+
+#include <QColor>
+#include <QIcon>
+#include <QObject>
+#include <QPalette>
+#include <QQuickItem>
+
+#include "kirigami2_export.h"
+
+namespace Kirigami
+{
+class PlatformThemeData;
+class PlatformThemePrivate;
+
+/**
+ * @class PlatformTheme platformtheme.h <Kirigami/PlatformTheme>
+ *
+ * This class is the base for color management in Kirigami,
+ * different platforms can reimplement this class to integrate with
+ * system platform colors of a given platform
+ *
+ * @see <a href="https://develop.kde.org/docs/getting-started/kirigami/style-colors">Colors and Themes in Kirigami</a>
+ * @see <a href="https://develop.kde.org/hig/style/color">Human Interface Guidelines on Colors</a>
+ */
+class KIRIGAMI2_EXPORT PlatformTheme : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * This enumeration describes the color set for which a color is being selected.
+     *
+     * Color sets define a color "environment", suitable for drawing all parts of a
+     * given region. Colors from different sets should not be combined.
+     */
+    Q_PROPERTY(ColorSet colorSet READ colorSet WRITE setColorSet NOTIFY colorSetChanged)
+
+    /**
+     * This enumeration describes the color group used to generate the colors.
+     * 
+     * The enum value is based upon QPalette::ColorGroup and has the same values.
+     * 
+     * It's redefined here in order to make it work with QML
+     * @since KDE Frameworks 4.43
+     */
+    Q_PROPERTY(ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged)
+
+    /**
+     * If true, the ::colorSet will be inherited from the colorset of a theme of one
+     * of the ancestor items
+     * 
+     * default: true
+     */
+    Q_PROPERTY(bool inherit READ inherit WRITE setInherit NOTIFY inheritChanged)
+
+    // foreground colors
+    /**
+     * Color for normal foregrounds, usually text, but not limited to it,
+     * anything that should be painted with a clear contrast should use this color
+     */
+    Q_PROPERTY(QColor textColor READ textColor WRITE setCustomTextColor RESET setCustomTextColor NOTIFY colorsChanged)
+
+    /**
+     * Foreground color for disabled areas, usually a mid-gray
+     */
+    Q_PROPERTY(QColor disabledTextColor READ disabledTextColor WRITE setCustomDisabledTextColor RESET setCustomDisabledTextColor NOTIFY colorsChanged)
+
+    /**
+     * Color for text that has been highlighted, often is a light color while normal text is dark
+     */
+    Q_PROPERTY(
+        QColor highlightedTextColor READ highlightedTextColor WRITE setCustomHighlightedTextColor RESET setCustomHighlightedTextColor NOTIFY colorsChanged)
+
+    /**
+     * Foreground for areas that are active or requesting attention
+     */
+    Q_PROPERTY(QColor activeTextColor READ activeTextColor WRITE setCustomActiveTextColor RESET setCustomActiveTextColor NOTIFY colorsChanged)
+
+    /**
+     * Color for links
+     */
+    Q_PROPERTY(QColor linkColor READ linkColor WRITE setCustomLinkColor RESET setCustomLinkColor NOTIFY colorsChanged)
+
+    /**
+     * Color for visited links, usually a bit darker than linkColor
+     */
+    Q_PROPERTY(QColor visitedLinkColor READ visitedLinkColor WRITE setCustomVisitedLinkColor RESET setCustomVisitedLinkColor NOTIFY colorsChanged)
+
+    /**
+     * Foreground color for negative areas, such as critical error text
+     */
+    Q_PROPERTY(QColor negativeTextColor READ negativeTextColor WRITE setCustomNegativeTextColor RESET setCustomNegativeTextColor NOTIFY colorsChanged)
+
+    /**
+     * Foreground color for neutral areas, such as warning texts (but not critical)
+     */
+    Q_PROPERTY(QColor neutralTextColor READ neutralTextColor WRITE setCustomNeutralTextColor RESET setCustomNeutralTextColor NOTIFY colorsChanged)
+
+    /**
+     * Success messages, trusted content
+     */
+    Q_PROPERTY(QColor positiveTextColor READ positiveTextColor WRITE setCustomPositiveTextColor RESET setCustomPositiveTextColor NOTIFY colorsChanged)
+
+    // background colors
+    /**
+     * The generic background color
+     */
+    Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setCustomBackgroundColor RESET setCustomBackgroundColor NOTIFY colorsChanged)
+
+    /**
+     * The generic background color
+     * Alternate background; for example, for use in lists.
+     * This color may be the same as BackgroundNormal,
+     * especially in sets other than View and Window.
+     */
+    Q_PROPERTY(QColor alternateBackgroundColor READ alternateBackgroundColor WRITE setCustomAlternateBackgroundColor RESET setCustomAlternateBackgroundColor
+                   NOTIFY colorsChanged)
+
+    /**
+     * The background color for selected areas
+     */
+    Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setCustomHighlightColor RESET setCustomHighlightColor NOTIFY colorsChanged)
+
+    /**
+     * Background for areas that are active or requesting attention
+     */
+    Q_PROPERTY(
+        QColor activeBackgroundColor READ activeBackgroundColor WRITE setCustomActiveBackgroundColor RESET setCustomActiveBackgroundColor NOTIFY colorsChanged)
+
+    /**
+     * Background color for links
+     */
+    Q_PROPERTY(QColor linkBackgroundColor READ linkBackgroundColor WRITE setCustomLinkBackgroundColor RESET setCustomLinkBackgroundColor NOTIFY colorsChanged)
+
+    /**
+     * Background color for visited links, usually a bit darker than linkBackgroundColor
+     */
+    Q_PROPERTY(QColor visitedLinkBackgroundColor READ visitedLinkBackgroundColor WRITE setCustomVisitedLinkBackgroundColor RESET
+                   setCustomVisitedLinkBackgroundColor NOTIFY colorsChanged)
+
+    /**
+     * Background color for negative areas, such as critical errors and destructive actions
+     */
+    Q_PROPERTY(QColor negativeBackgroundColor READ negativeBackgroundColor WRITE setCustomNegativeBackgroundColor RESET setCustomNegativeBackgroundColor NOTIFY
+                   colorsChanged)
+
+    /**
+     * Background color for neutral areas, such as warnings (but not critical)
+     */
+    Q_PROPERTY(QColor neutralBackgroundColor READ neutralBackgroundColor WRITE setCustomNeutralBackgroundColor RESET setCustomNeutralBackgroundColor NOTIFY
+                   colorsChanged)
+
+    /**
+     * Background color for positive areas, such as success messages and trusted content
+     */
+    Q_PROPERTY(QColor positiveBackgroundColor READ positiveBackgroundColor WRITE setCustomPositiveBackgroundColor RESET setCustomPositiveBackgroundColor NOTIFY
+                   colorsChanged)
+
+    // decoration colors
+    /**
+     * A decoration color that indicates active focus
+     */
+    Q_PROPERTY(QColor focusColor READ focusColor WRITE setCustomFocusColor RESET setCustomFocusColor NOTIFY colorsChanged)
+
+    /**
+     * A decoration color that indicates mouse hovering
+     */
+    Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setCustomHoverColor RESET setCustomHoverColor NOTIFY colorsChanged)
+
+    // font and palette
+    Q_PROPERTY(QFont defaultFont READ defaultFont NOTIFY defaultFontChanged)
+
+    // small font
+    Q_PROPERTY(QFont smallFont READ smallFont NOTIFY defaultFontChanged)
+
+    // Active palette
+    Q_PROPERTY(QPalette palette READ palette NOTIFY paletteChanged)
+
+public:
+    enum ColorSet {
+        View = 0, /** Color set for item views, usually the lightest of all */
+        Window, /** Default Color set for windows and "chrome" areas */
+        Button, /** Color set used by buttons */
+        Selection, /** Color set used by selectged areas */
+        Tooltip, /** Color set used by tooltips */
+        Complementary, /** Color set meant to be complementary to Window: usually is a dark theme for light themes */
+        Header, /** Color set to be used by heading areas of applications, such as toolbars */
+
+        ColorSetCount, // Number of items in this enum, this should always be the last item.
+    };
+    Q_ENUM(ColorSet)
+
+    enum ColorGroup {
+        Disabled = QPalette::Disabled,
+        Active = QPalette::Active,
+        Inactive = QPalette::Inactive,
+        Normal = QPalette::Normal,
+
+        ColorGroupCount, // Number of items in this enum, this should always be the last item.
+    };
+    Q_ENUM(ColorGroup)
+
+    explicit PlatformTheme(QObject *parent = nullptr);
+    ~PlatformTheme() override;
+
+    void setColorSet(PlatformTheme::ColorSet);
+    PlatformTheme::ColorSet colorSet() const;
+
+    void setColorGroup(PlatformTheme::ColorGroup);
+    PlatformTheme::ColorGroup colorGroup() const;
+
+    bool inherit() const;
+    void setInherit(bool inherit);
+
+    // foreground colors
+    QColor textColor() const;
+    QColor disabledTextColor() const;
+    QColor highlightedTextColor() const;
+    QColor activeTextColor() const;
+    QColor linkColor() const;
+    QColor visitedLinkColor() const;
+    QColor negativeTextColor() const;
+    QColor neutralTextColor() const;
+    QColor positiveTextColor() const;
+
+    // background colors
+    QColor backgroundColor() const;
+    QColor alternateBackgroundColor() const;
+    QColor highlightColor() const;
+    QColor activeBackgroundColor() const;
+    QColor linkBackgroundColor() const;
+    QColor visitedLinkBackgroundColor() const;
+    QColor negativeBackgroundColor() const;
+    QColor neutralBackgroundColor() const;
+    QColor positiveBackgroundColor() const;
+
+    // decoration colors
+    QColor focusColor() const;
+    QColor hoverColor() const;
+
+    QFont defaultFont() const;
+    QFont smallFont() const;
+
+    // this may is used by the desktop QQC2 to set the styleoption palettes
+    QPalette palette() const;
+
+    // this will be used by desktopicon to fetch icons with KIconLoader
+    virtual Q_INVOKABLE QIcon iconFromTheme(const QString &name, const QColor &customColor = Qt::transparent);
+
+    bool supportsIconColoring() const;
+
+    // foreground colors
+    void setCustomTextColor(const QColor &color = QColor());
+    void setCustomDisabledTextColor(const QColor &color = QColor());
+    void setCustomHighlightedTextColor(const QColor &color = QColor());
+    void setCustomActiveTextColor(const QColor &color = QColor());
+    void setCustomLinkColor(const QColor &color = QColor());
+    void setCustomVisitedLinkColor(const QColor &color = QColor());
+    void setCustomNegativeTextColor(const QColor &color = QColor());
+    void setCustomNeutralTextColor(const QColor &color = QColor());
+    void setCustomPositiveTextColor(const QColor &color = QColor());
+    // background colors
+    void setCustomBackgroundColor(const QColor &color = QColor());
+    void setCustomAlternateBackgroundColor(const QColor &color = QColor());
+    void setCustomHighlightColor(const QColor &color = QColor());
+    void setCustomActiveBackgroundColor(const QColor &color = QColor());
+    void setCustomLinkBackgroundColor(const QColor &color = QColor());
+    void setCustomVisitedLinkBackgroundColor(const QColor &color = QColor());
+    void setCustomNegativeBackgroundColor(const QColor &color = QColor());
+    void setCustomNeutralBackgroundColor(const QColor &color = QColor());
+    void setCustomPositiveBackgroundColor(const QColor &color = QColor());
+    // decoration colors
+    void setCustomFocusColor(const QColor &color = QColor());
+    void setCustomHoverColor(const QColor &color = QColor());
+
+    // QML attached property
+    static PlatformTheme *qmlAttachedProperties(QObject *object);
+
+Q_SIGNALS:
+    // TODO: parameters to signals as this is also a c++ api
+    void colorsChanged();
+    void defaultFontChanged(const QFont &font);
+    void smallFontChanged(const QFont &font);
+    void colorSetChanged(Kirigami::PlatformTheme::ColorSet colorSet);
+    void colorGroupChanged(Kirigami::PlatformTheme::ColorGroup colorGroup);
+    void paletteChanged(const QPalette &pal);
+    void inheritChanged(bool inherit);
+
+protected:
+    // Setters, not accessible from QML but from implementations
+    void setSupportsIconColoring(bool support);
+
+    // foreground colors
+    void setTextColor(const QColor &color);
+    void setDisabledTextColor(const QColor &color);
+    void setHighlightedTextColor(const QColor &color);
+    void setActiveTextColor(const QColor &color);
+    void setLinkColor(const QColor &color);
+    void setVisitedLinkColor(const QColor &color);
+    void setNegativeTextColor(const QColor &color);
+    void setNeutralTextColor(const QColor &color);
+    void setPositiveTextColor(const QColor &color);
+
+    // background colors
+    void setBackgroundColor(const QColor &color);
+    void setAlternateBackgroundColor(const QColor &color);
+    void setHighlightColor(const QColor &color);
+    void setActiveBackgroundColor(const QColor &color);
+    void setLinkBackgroundColor(const QColor &color);
+    void setVisitedLinkBackgroundColor(const QColor &color);
+    void setNegativeBackgroundColor(const QColor &color);
+    void setNeutralBackgroundColor(const QColor &color);
+    void setPositiveBackgroundColor(const QColor &color);
+
+    // decoration colors
+    void setFocusColor(const QColor &color);
+    void setHoverColor(const QColor &color);
+
+    void setDefaultFont(const QFont &defaultFont);
+    void setSmallFont(const QFont &smallFont);
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 80)
+    KIRIGAMI2_DEPRECATED_VERSION(5, 80, "setPalette is unused, the palette is autogenerated from PlatformTheme colors now")
+    void setPalette(const QPalette &palette);
+#endif
+
+    bool event(QEvent *event) override;
+
+private:
+    KIRIGAMI2_NO_EXPORT void update();
+    KIRIGAMI2_NO_EXPORT void updateChildren(QObject *item);
+    KIRIGAMI2_NO_EXPORT void emitSignals();
+    KIRIGAMI2_NO_EXPORT void emitColorChanged();
+    KIRIGAMI2_NO_EXPORT QObject *determineParent(QObject *object);
+
+    PlatformThemePrivate *d;
+    friend class PlatformThemePrivate;
+    friend class PlatformThemeData;
+};
+
+namespace PlatformThemeEvents
+{
+// To avoid the overhead of Qt's signal/slot connections, we use custom events
+// to communicate with subclasses. This way, we can indicate what actually
+// changed without needing to add new virtual functions to PlatformTheme which
+// would break binary compatibility.
+//
+// To handle these events in your subclass, override QObject::event() and check
+// if you receive one of these events, then do what is needed. Finally, make
+// sure to call PlatformTheme::event() since that will also do some processing
+// of these events.
+
+template<typename T>
+class KIRIGAMI2_EXPORT PropertyChangedEvent : public QEvent
+{
+public:
+    PropertyChangedEvent(PlatformTheme *theme, const T &previous, const T &current)
+        : QEvent(PropertyChangedEvent<T>::type)
+        , sender(theme)
+        , oldValue(previous)
+        , newValue(current)
+    {
+    }
+
+    PlatformTheme *sender;
+    T oldValue;
+    T newValue;
+
+    static QEvent::Type type;
+};
+
+using DataChangedEvent = PropertyChangedEvent<std::shared_ptr<PlatformThemeData>>;
+using ColorSetChangedEvent = PropertyChangedEvent<PlatformTheme::ColorSet>;
+using ColorGroupChangedEvent = PropertyChangedEvent<PlatformTheme::ColorGroup>;
+using ColorChangedEvent = PropertyChangedEvent<QColor>;
+using FontChangedEvent = PropertyChangedEvent<QFont>;
+
+}
+
+} // namespace Kirigami
+
+QML_DECLARE_TYPEINFO(Kirigami::PlatformTheme, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // PLATFORMTHEME_H
diff --git a/src/libkirigami/styleselector.cpp b/src/libkirigami/styleselector.cpp
new file mode 100644 (file)
index 0000000..943faa9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "styleselector_p.h"
+
+#include <QDir>
+#include <QFile>
+#include <QQuickStyle>
+
+namespace Kirigami
+{
+QUrl StyleSelector::s_baseUrl;
+QStringList StyleSelector::s_styleChain;
+
+QString StyleSelector::style()
+{
+    if (qEnvironmentVariableIntValue("KIRIGAMI_FORCE_STYLE") == 1) {
+        return QQuickStyle::name();
+    } else {
+        return styleChain().first();
+    }
+}
+
+QStringList StyleSelector::styleChain()
+{
+    if (qEnvironmentVariableIntValue("KIRIGAMI_FORCE_STYLE") == 1) {
+        return {QQuickStyle::name()};
+    }
+
+    if (!s_styleChain.isEmpty()) {
+        return s_styleChain;
+    }
+
+    auto style = QQuickStyle::name();
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+    // org.kde.desktop.plasma is a couple of files that fall back to desktop by purpose
+    if (style.isEmpty() || style == QStringLiteral("org.kde.desktop.plasma")) {
+        auto path = resolveFilePath(QStringLiteral("/styles/org.kde.desktop"));
+        if (QFile::exists(path)) {
+            s_styleChain.prepend(QStringLiteral("org.kde.desktop"));
+        }
+    }
+#elif defined(Q_OS_ANDROID)
+    s_styleChain.prepend(QStringLiteral("Material"));
+#else // do we have an iOS specific style?
+    s_styleChain.prepend(QStringLiteral("Material"));
+#endif
+
+    auto stylePath = resolveFilePath(QStringLiteral("/styles/") + style);
+    if (!style.isEmpty() && QFile::exists(stylePath) && !s_styleChain.contains(style)) {
+        s_styleChain.prepend(style);
+        // if we have plasma deps installed, use them for extra integration
+        auto plasmaPath = resolveFilePath(QStringLiteral("/styles/org.kde.desktop.plasma"));
+        if (style == QStringLiteral("org.kde.desktop") && QFile::exists(plasmaPath)) {
+            s_styleChain.prepend(QStringLiteral("org.kde.desktop.plasma"));
+        }
+    } else {
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+        s_styleChain.prepend(QStringLiteral("org.kde.desktop"));
+#endif
+    }
+
+    return s_styleChain;
+}
+
+QUrl StyleSelector::componentUrl(const QString &fileName)
+{
+    const auto chain = styleChain();
+    for (const QString &style : chain) {
+        const QString candidate = QStringLiteral("styles/") + style + QLatin1Char('/') + fileName;
+        if (QFile::exists(resolveFilePath(candidate))) {
+            return QUrl(resolveFileUrl(candidate));
+        }
+    }
+
+    return QUrl(resolveFileUrl(fileName));
+}
+
+void StyleSelector::setBaseUrl(const QUrl &baseUrl)
+{
+    s_baseUrl = baseUrl;
+}
+
+QString StyleSelector::resolveFilePath(const QString &path)
+{
+#if defined(KIRIGAMI_BUILD_TYPE_STATIC)
+    return QStringLiteral(":/qt-project.org/imports/org/kde/kirigami.2/") + path;
+#elif defined(Q_OS_ANDROID)
+    return QStringLiteral(":/android_rcc_bundle/qml/org/kde/kirigami.2/") + path;
+#else
+    if (s_baseUrl.isValid()) {
+        return s_baseUrl.toLocalFile() + QLatin1Char('/') + path;
+    } else {
+        return QDir::currentPath() + QLatin1Char('/') + path;
+    }
+#endif
+}
+
+QString StyleSelector::resolveFileUrl(const QString &path)
+{
+#if defined(KIRIGAMI_BUILD_TYPE_STATIC)
+    return QStringLiteral("qrc:/qt-project.org/imports/org/kde/kirigami.2/") + path;
+#elif defined(Q_OS_ANDROID)
+    return QStringLiteral("qrc:/android_rcc_bundle/qml/org/kde/kirigami.2/") + path;
+#else
+    return s_baseUrl.toString() + QLatin1Char('/') + path;
+#endif
+}
+
+}
diff --git a/src/libkirigami/styleselector_p.h b/src/libkirigami/styleselector_p.h
new file mode 100644 (file)
index 0000000..80c5581
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef STYLESELECTOR_H
+#define STYLESELECTOR_H
+
+#include <QStringList>
+
+#include "kirigami2_export.h"
+
+class QUrl;
+
+namespace Kirigami
+{
+class KIRIGAMI2_EXPORT StyleSelector
+{
+public:
+    static QString style();
+    static QStringList styleChain();
+
+    static QUrl componentUrl(const QString &fileName);
+
+    static void setBaseUrl(const QUrl &baseUrl);
+
+    static QString resolveFilePath(const QString &path);
+    static QString resolveFileUrl(const QString &path);
+
+private:
+    static QUrl s_baseUrl;
+    static QStringList s_styleChain;
+};
+
+}
+
+#endif // STYLESELECTOR_H
diff --git a/src/libkirigami/tabletmodewatcher.cpp b/src/libkirigami/tabletmodewatcher.cpp
new file mode 100644 (file)
index 0000000..a5bb8dd
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "tabletmodewatcher.h"
+#include <QCoreApplication>
+
+#if defined(KIRIGAMI_ENABLE_DBUS)
+#include "tabletmodemanager_interface.h"
+#include <QDBusConnection>
+#endif
+
+// TODO: All the dbus stuff should be conditional, optional win32 support
+
+namespace Kirigami
+{
+KIRIGAMI2_EXPORT QEvent::Type TabletModeChangedEvent::type = QEvent::None;
+
+class TabletModeWatcherSingleton
+{
+public:
+    TabletModeWatcher self;
+};
+
+Q_GLOBAL_STATIC(TabletModeWatcherSingleton, privateTabletModeWatcherSelf)
+
+class TabletModeWatcherPrivate
+{
+public:
+    TabletModeWatcherPrivate(TabletModeWatcher *watcher)
+        : q(watcher)
+    {
+        // Called here to avoid collisions with application event types so we should use
+        // registerEventType for generating the event types.
+        TabletModeChangedEvent::type = QEvent::Type(QEvent::registerEventType());
+#if !defined(KIRIGAMI_ENABLE_DBUS) && (defined(Q_OS_ANDROID) || defined(Q_OS_IOS))
+        isTabletModeAvailable = true;
+        isTabletMode = true;
+#elif defined(KIRIGAMI_ENABLE_DBUS)
+        // Mostly for debug purposes and for platforms which are always mobile,
+        // such as Plasma Mobile
+        if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE") || qEnvironmentVariableIsSet("KDE_KIRIGAMI_TABLET_MODE")) {
+            /* clang-format off */
+            isTabletMode =
+                (QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("1")
+                    || QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("true"))
+                || (QString::fromLatin1(qgetenv("KDE_KIRIGAMI_TABLET_MODE")) == QStringLiteral("1")
+                    || QString::fromLatin1(qgetenv("KDE_KIRIGAMI_TABLET_MODE")) == QStringLiteral("true"));
+            /* clang-format on */
+            isTabletModeAvailable = isTabletMode;
+        } else {
+            m_interface =
+                new OrgKdeKWinTabletModeManagerInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/org/kde/KWin"), QDBusConnection::sessionBus(), q);
+
+            if (m_interface->isValid()) {
+                // NOTE: the initial call is actually sync, because is better a tiny freeze than having the ui always recalculated and changed at the start
+                QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
+                                                                      QStringLiteral("/org/kde/KWin"),
+                                                                      QStringLiteral("org.freedesktop.DBus.Properties"),
+                                                                      QStringLiteral("GetAll"));
+                message.setArguments({m_interface->interface()});
+                QDBusReply<QVariantMap> propsReply = QDBusConnection::sessionBus().call(message);
+                isTabletModeAvailable = propsReply.value()[QLatin1String("tabletModeAvailable")].toBool();
+                isTabletMode = propsReply.value()[QLatin1String("tabletMode")].toBool();
+                QObject::connect(m_interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeChanged, q, [this](bool tabletMode) {
+                    setIsTablet(tabletMode);
+                });
+                QObject::connect(m_interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeAvailableChanged, q, [this](bool avail) {
+                    isTabletModeAvailable = avail;
+                    Q_EMIT q->tabletModeAvailableChanged(avail);
+                });
+            } else {
+                isTabletModeAvailable = false;
+                isTabletMode = false;
+            }
+        }
+// TODO: case for Windows
+#else
+        isTabletModeAvailable = false;
+        isTabletMode = false;
+#endif
+    }
+    ~TabletModeWatcherPrivate(){};
+    void setIsTablet(bool tablet);
+
+    TabletModeWatcher *q;
+#if defined(KIRIGAMI_ENABLE_DBUS)
+    OrgKdeKWinTabletModeManagerInterface *m_interface = nullptr;
+#endif
+    QVector<QObject *> watchers;
+    bool isTabletModeAvailable = false;
+    bool isTabletMode = false;
+};
+
+void TabletModeWatcherPrivate::setIsTablet(bool tablet)
+{
+    if (isTabletMode == tablet) {
+        return;
+    }
+
+    isTabletMode = tablet;
+    TabletModeChangedEvent event{tablet};
+    Q_EMIT q->tabletModeChanged(tablet);
+    for (auto *w : watchers) {
+        QCoreApplication::sendEvent(w, &event);
+    }
+}
+
+TabletModeWatcher::TabletModeWatcher(QObject *parent)
+    : QObject(parent)
+    , d(new TabletModeWatcherPrivate(this))
+{
+}
+
+TabletModeWatcher::~TabletModeWatcher()
+{
+    delete d;
+}
+
+TabletModeWatcher *TabletModeWatcher::self()
+{
+    return &privateTabletModeWatcherSelf()->self;
+}
+
+bool TabletModeWatcher::isTabletModeAvailable() const
+{
+    return d->isTabletModeAvailable;
+}
+
+bool TabletModeWatcher::isTabletMode() const
+{
+    return d->isTabletMode;
+}
+
+void TabletModeWatcher::addWatcher(QObject *watcher)
+{
+    d->watchers.append(watcher);
+}
+
+void TabletModeWatcher::removeWatcher(QObject *watcher)
+{
+    d->watchers.removeAll(watcher);
+}
+}
+
+#include "moc_tabletmodewatcher.cpp"
diff --git a/src/libkirigami/tabletmodewatcher.h b/src/libkirigami/tabletmodewatcher.h
new file mode 100644 (file)
index 0000000..41a267c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMI_TABLETMODEWATCHER_H
+#define KIRIGAMI_TABLETMODEWATCHER_H
+
+#include <QEvent>
+#include <QObject>
+
+#include "kirigami2_export.h"
+
+namespace Kirigami
+{
+class TabletModeWatcherPrivate;
+
+class KIRIGAMI2_EXPORT TabletModeChangedEvent : public QEvent
+{
+public:
+    TabletModeChangedEvent(bool tablet)
+        : QEvent(TabletModeChangedEvent::type)
+        , tabletMode(tablet)
+    {
+    }
+
+    bool tabletMode = false;
+
+    static QEvent::Type type;
+};
+
+/**
+ * @class TabletModeWatcher tabletmodewatcher.h <Kirigami/TabletModeWatcher>
+ *
+ * This class reports on the status of certain transformable
+ * devices which can be both tablets and laptops at the same time,
+ * with a detachable keyboard.
+ * It reports whether the device supports a tablet mode and if
+ * the device is currently in such mode or not, emitting a signal
+ * when the user switches.
+ */
+#ifdef KIRIGAMI_BUILD_TYPE_STATIC
+class TabletModeWatcher : public QObject
+#else
+class KIRIGAMI2_EXPORT TabletModeWatcher : public QObject
+#endif
+{
+    Q_OBJECT
+    Q_PROPERTY(bool tabletModeAvailable READ isTabletModeAvailable NOTIFY tabletModeAvailableChanged)
+    Q_PROPERTY(bool tabletMode READ isTabletMode NOTIFY tabletModeChanged)
+
+public:
+    ~TabletModeWatcher() override;
+    static TabletModeWatcher *self();
+
+    /**
+     * @returns @c true if the device supports a tablet mode and has a switch
+     * to report when the device has been transformed.
+     * For debug purposes, if either the environment variable QT_QUICK_CONTROLS_MOBILE
+     * or KDE_KIRIGAMI_TABLET_MODE are set to true, isTabletModeAvailable will be true
+     */
+    bool isTabletModeAvailable() const;
+
+    /**
+     * @returns @c true if the machine is now in tablet mode, such as the
+     * laptop keyboard flipped away or detached.
+     * Note that this doesn't mean exactly a tablet form factor, but
+     * that the preferred input mode for the device is the touch screen
+     * and that pointer and keyboard are either secondary or not available.
+     *
+     * For debug purposes, if either the environment variable QT_QUICK_CONTROLS_MOBILE
+     * or KDE_KIRIGAMI_TABLET_MODE are set to true, isTabletMode will be true
+     */
+    bool isTabletMode() const;
+
+    /**
+     * Register an arbitrary QObject to send events from this.
+     * At the moment only one event will be sent: TabletModeChangedEvent
+     */
+    void addWatcher(QObject *watcher);
+
+    /*
+     * Unsubscribe watcher from receiving events from TabletModeWatcher.
+     */
+    void removeWatcher(QObject *watcher);
+
+Q_SIGNALS:
+    void tabletModeAvailableChanged(bool tabletModeAvailable);
+    void tabletModeChanged(bool tabletMode);
+
+private:
+    KIRIGAMI2_NO_EXPORT explicit TabletModeWatcher(QObject *parent = nullptr);
+    TabletModeWatcherPrivate *d;
+    friend class TabletModeWatcherSingleton;
+};
+}
+
+#endif // KIRIGAMI_TABLETMODEWATCHER
diff --git a/src/libkirigami/units.cpp b/src/libkirigami/units.cpp
new file mode 100644 (file)
index 0000000..34f391e
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Jonah Brüchert <jbb@kaidan.im>
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "units.h"
+
+#include <QFont>
+#include <QFontMetrics>
+#include <QGuiApplication>
+#include <QQmlComponent>
+#include <QQmlEngine>
+#include <QStyleHints>
+
+#include <chrono>
+#include <cmath>
+
+#include "loggingcategory.h"
+
+namespace Kirigami {
+using clock = std::chrono::steady_clock;
+
+const clock::duration rateLimit = std::chrono::seconds(1);
+
+/* Print a deprecation warning that is rate limited to only display once in
+ * every time period as determined by rateLimit. We keep track of how often this
+ * is called and display that if it is larger than 0.
+ *
+ * This is done to prevent flooding the logs with "X is deprecated" messages
+ * that are all the same and don't provide any new information after the first.
+ */
+void rateLimitWarning(const char *method, const char *since, const char *message)
+{
+    static QMap<QString, QPair<clock::time_point, int>> messages;
+
+    auto methodString = QString::fromUtf8(method);
+
+    if (!messages.contains(methodString)) {
+        messages.insert(methodString, qMakePair(clock::time_point{}, 0));
+    }
+
+    auto entry = messages.value(methodString);
+    if (clock::now() - entry.first < rateLimit) {
+        messages[methodString].second += 1;
+        return;
+    }
+
+    qCWarning(KirigamiLog).nospace() << method << " is deprecated (since " << since << "): " << message;
+
+    if (entry.second > 0) {
+        qCWarning(KirigamiLog) << "Previous message repeats" << entry.second << "times.";
+    }
+
+    messages[methodString] = qMakePair(clock::now(), 0);
+}
+
+class UnitsPrivate
+{
+    Q_DISABLE_COPY(UnitsPrivate)
+
+public:
+    explicit UnitsPrivate(Units *units)
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+        : qmlFontMetrics(nullptr)
+#endif
+        // Cache font so we don't have to go through QVariant and property every time
+        , fontMetrics(QFontMetricsF(QGuiApplication::font()))
+        , gridUnit(fontMetrics.height())
+        , smallSpacing(std::floor(gridUnit / 4))
+        , mediumSpacing(std::round(smallSpacing * 1.5))
+        , largeSpacing(smallSpacing * 2)
+        , veryLongDuration(400)
+        , longDuration(200)
+        , shortDuration(100)
+        , veryShortDuration(50)
+        , humanMoment(2000)
+        , toolTipDelay(700)
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+        , wheelScrollLines(QGuiApplication::styleHints()->wheelScrollLines())
+#endif
+        , iconSizes(new IconSizes(units))
+    {
+    }
+
+    // Only stored for QML API compatiblity
+    // TODO KF6 drop
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+    std::unique_ptr<QObject> qmlFontMetrics = nullptr;
+#endif
+
+    // Font metrics used for Units.
+    // TextMetrics uses QFontMetricsF internally, so this should do the same
+    QFontMetricsF fontMetrics;
+
+    // units
+    int gridUnit;
+    int smallSpacing;
+    int mediumSpacing;
+    int largeSpacing;
+
+    // durations
+    int veryLongDuration;
+    int longDuration;
+    int shortDuration;
+    int veryShortDuration;
+    int humanMoment;
+    int toolTipDelay;
+
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+    int wheelScrollLines;
+#endif
+
+    IconSizes *const iconSizes;
+
+    // To prevent overriding custom set units if the font changes
+    bool customUnitsSet = false;
+    bool customWheelScrollLinesSet = false;
+
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+    std::unique_ptr<QObject> createQmlFontMetrics(QQmlEngine *engine)
+    {
+        QQmlComponent component(engine);
+        component.setData(QByteArrayLiteral(
+            "import QtQuick 2.14\n"
+            "import org.kde.kirigami 2.0\n"
+            "FontMetrics {\n"
+            "  function roundedIconSize(size) {\n"
+            R"(                console.warn("Units.fontMetrics.roundedIconSize is deprecated, use Units.iconSizes.roundedIconSize instead.");)"
+            "          return Units.iconSizes.roundedIconSize(size)\n"
+            "  }\n"
+            "}\n"
+        ), QUrl(QStringLiteral("units.cpp")));
+
+        return std::unique_ptr<QObject>(component.create());
+    }
+#endif
+};
+
+Units::~Units() = default;
+
+Units::Units(QObject *parent)
+    : QObject(parent)
+    , d(std::make_unique<UnitsPrivate>(this))
+{
+    connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, [this](int scrollLines) {
+        if (d->customWheelScrollLinesSet) {
+            return;
+        }
+
+        setWheelScrollLines(scrollLines);
+    });
+    connect(qGuiApp, &QGuiApplication::fontChanged, this, [this](const QFont &font) {
+        d->fontMetrics = QFontMetricsF(font);
+
+        if (d->customUnitsSet) {
+            return;
+        }
+
+        d->gridUnit = d->fontMetrics.height();
+        Q_EMIT gridUnitChanged();
+        d->smallSpacing = std::floor(d->gridUnit / 4);
+        Q_EMIT smallSpacingChanged();
+        d->mediumSpacing = std::round(d->smallSpacing * 1.5);
+        Q_EMIT mediumSpacingChanged();
+        d->largeSpacing = d->smallSpacing * 2;
+        Q_EMIT largeSpacingChanged();
+        Q_EMIT d->iconSizes->sizeForLabelsChanged();
+    });
+}
+
+qreal Units::devicePixelRatio() const
+{
+    rateLimitWarning("Units.devicePixelRatio", "5.86", "This returns 1 when using Qt HiDPI scaling.");
+    const int pixelSize = QGuiApplication::font().pixelSize();
+    const qreal pointSize = QGuiApplication::font().pointSize();
+
+    return std::fmax(1, (pixelSize * 0.75 / pointSize));
+}
+
+int Units::gridUnit() const
+{
+    return d->gridUnit;
+}
+
+void Kirigami::Units::setGridUnit(int size)
+{
+    if (d->gridUnit == size) {
+        return;
+    }
+
+    d->gridUnit = size;
+    d->customUnitsSet = true;
+    Q_EMIT gridUnitChanged();
+}
+
+int Units::smallSpacing() const
+{
+    return d->smallSpacing;
+}
+
+void Kirigami::Units::setSmallSpacing(int size)
+{
+    if (d->smallSpacing == size) {
+        return;
+    }
+
+    d->smallSpacing = size;
+    d->customUnitsSet = true;
+    Q_EMIT smallSpacingChanged();
+}
+
+int Units::mediumSpacing() const
+{
+    return d->mediumSpacing;
+}
+
+void Kirigami::Units::setMediumSpacing(int size)
+{
+    if (d->mediumSpacing == size) {
+        return;
+    }
+
+    d->mediumSpacing = size;
+    d->customUnitsSet = true;
+    Q_EMIT mediumSpacingChanged();
+}
+
+int Units::largeSpacing() const
+{
+    return d->largeSpacing;
+}
+
+void Kirigami::Units::setLargeSpacing(int size)
+{
+    if (d->largeSpacing) {
+        return;
+    }
+
+    d->largeSpacing = size;
+    d->customUnitsSet = true;
+    Q_EMIT largeSpacingChanged();
+}
+
+int Units::veryLongDuration() const
+{
+    return d->veryLongDuration;
+}
+
+void Units::setVeryLongDuration(int duration)
+{
+    if (d->veryLongDuration == duration) {
+        return;
+    }
+
+    d->veryLongDuration = duration;
+    Q_EMIT veryLongDurationChanged();
+}
+
+int Units::longDuration() const
+{
+    return d->longDuration;
+}
+
+void Units::setLongDuration(int duration)
+{
+    if (d->longDuration == duration) {
+        return;
+    }
+
+    d->longDuration = duration;
+    Q_EMIT longDurationChanged();
+}
+
+int Units::shortDuration() const
+{
+    return d->shortDuration;
+}
+
+void Units::setShortDuration(int duration)
+{
+    if (d->shortDuration == duration) {
+        return;
+    }
+
+    d->shortDuration = duration;
+    Q_EMIT shortDurationChanged();
+}
+
+int Units::veryShortDuration() const
+{
+    return d->veryShortDuration;
+}
+
+void Units::setVeryShortDuration(int duration)
+{
+    if (d->veryShortDuration == duration) {
+        return;
+    }
+
+    d->veryShortDuration = duration;
+    Q_EMIT veryShortDurationChanged();
+}
+
+int Units::humanMoment() const
+{
+    return d->humanMoment;
+}
+
+void Units::setHumanMoment(int duration)
+{
+    if (d->humanMoment == duration) {
+        return;
+    }
+
+    d->humanMoment = duration;
+    Q_EMIT humanMomentChanged();
+}
+
+int Units::toolTipDelay() const
+{
+    return d->toolTipDelay;
+}
+
+void Units::setToolTipDelay(int delay)
+{
+    if (d->toolTipDelay == delay) {
+        return;
+    }
+
+    d->toolTipDelay = delay;
+    Q_EMIT toolTipDelayChanged();
+}
+
+int Units::maximumInteger() const
+{
+    return std::numeric_limits<int>::max();
+}
+
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+int Units::wheelScrollLines() const
+{
+    rateLimitWarning("Units.wheelScrollLines", "5.86", "Use Qt.styleHints.wheelScrollLines instead");
+    return d->wheelScrollLines;
+}
+
+void Units::setWheelScrollLines(int lines)
+{
+    if (d->wheelScrollLines == lines) {
+        return;
+    }
+
+    d->wheelScrollLines = lines;
+    d->customWheelScrollLinesSet = true;
+    Q_EMIT wheelScrollLinesChanged();
+}
+#endif
+
+IconSizes *Units::iconSizes() const
+{
+    return d->iconSizes;
+}
+
+#if KIRIGAMI2_BUILD_DEPRECATED_SINCE(5, 86)
+QObject *Units::fontMetrics() const
+{
+    rateLimitWarning("Units.fontMetrics", "5.86", "Create your own FontMetrics object instead.");
+    if (!d->qmlFontMetrics) {
+        d->qmlFontMetrics = d->createQmlFontMetrics(qmlEngine(this));
+    }
+    return d->qmlFontMetrics.get();
+}
+#endif
+
+IconSizes::IconSizes(Units *units)
+    : QObject(units)
+    , m_units(units)
+{
+}
+
+int IconSizes::roundedIconSize(int size) const
+{
+    if (size < 16) {
+        return size;
+    }
+
+    if (size < 22) {
+        return 16;
+    }
+
+    if (size < 32) {
+        return 22;
+    }
+
+    if (size < 48) {
+        return 32;
+    }
+
+    if (size < 64) {
+        return 48;
+    }
+
+    return size;
+}
+
+int IconSizes::sizeForLabels() const
+{
+    // gridUnit is the height of textMetrics
+    return roundedIconSize(m_units->d->fontMetrics.height());
+}
+
+int IconSizes::small() const
+{
+    return 16;
+}
+
+int IconSizes::smallMedium() const
+{
+    return 22;
+}
+
+int IconSizes::medium() const
+{
+    return 32;
+}
+
+int IconSizes::large() const
+{
+    return 48;
+}
+
+int IconSizes::huge() const
+{
+    return 64;
+}
+
+int IconSizes::enormous() const
+{
+    return 128;
+}
+
+}
diff --git a/src/libkirigami/units.h b/src/libkirigami/units.h
new file mode 100644 (file)
index 0000000..ceb5da1
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Jonah Brüchert <jbb@kaidan.im>
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMI_UNITS_H
+#define KIRIGAMI_UNITS_H
+
+#include <QObject>
+#include <memory>
+
+#include "kirigami2_export.h"
+
+class QQmlEngine;
+
+namespace Kirigami {
+class Units;
+class UnitsPrivate;
+
+/**
+ * @class IconSizes units.h <Kirigami/Units>
+ *
+ * Provides access to platform-dependent icon sizing
+ */
+class KIRIGAMI2_EXPORT IconSizes : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(int sizeForLabels READ sizeForLabels NOTIFY sizeForLabelsChanged)
+    Q_PROPERTY(int small READ small NOTIFY smallChanged)
+    Q_PROPERTY(int smallMedium READ smallMedium NOTIFY smallMediumChanged)
+    Q_PROPERTY(int medium READ medium NOTIFY mediumChanged)
+    Q_PROPERTY(int large READ large NOTIFY largeChanged)
+    Q_PROPERTY(int huge READ huge NOTIFY hugeChanged)
+    Q_PROPERTY(int enormous READ enormous NOTIFY enormousChanged)
+
+public:
+    IconSizes(Units *units);
+
+    int sizeForLabels() const;
+    int small() const;
+    int smallMedium() const;
+    int medium() const;
+    int large() const;
+    int huge() const;
+    int enormous() const;
+
+    Q_INVOKABLE int roundedIconSize(int size) const;
+
+private:
+    KIRIGAMI2_NO_EXPORT float iconScaleFactor() const;
+
+    Units *m_units;
+
+Q_SIGNALS:
+    void sizeForLabelsChanged();
+    void smallChanged();
+    void smallMediumChanged();
+    void mediumChanged();
+    void largeChanged();
+    void hugeChanged();
+    void enormousChanged();
+};
+
+/**
+ * @class Units units.h <Kirigami/Units>
+ *
+ * A set of values to define semantically sizes and durations.
+ */
+class KIRIGAMI2_EXPORT Units : public QObject
+{
+    Q_OBJECT
+
+    friend class IconSizes;
+
+    /**
+     * The fundamental unit of space that should be used for sizes, expressed in pixels.
+     * Given the screen has an accurate DPI settings, it corresponds to the height of
+     * the font's QtGui.FontMetrics.boundingRect</a>.
+     */
+    Q_PROPERTY(int gridUnit READ gridUnit WRITE setGridUnit NOTIFY gridUnitChanged)
+
+    /**
+     * Provides access to platform-dependent icon sizing
+     *
+     * The icon sizes provided are normalized for different DPI, so icons
+     * will scale depending on the DPI.
+     *
+     * * sizeForLabels (the largest icon size that fits within
+     * <a href="https://doc.qt.io/qt-5/qml-qtquick-fontmetrics.html#height-prop">fontMetrics.height</a>)
+     * * small
+     * * smallMedium
+     * * medium
+     * * large
+     * * huge
+     * * enormous
+     * 
+     * @since KDE Frameworks 5.80
+     * @since org.kde.kirigami 2.16
+     */
+    Q_PROPERTY(IconSizes *iconSizes READ iconSizes CONSTANT)
+
+    /**
+     * This property holds the amount of spacing that should be used between smaller UI elements,
+     * such as a small icon and a label in a button.
+     * Internally, this size depends on the size of the default font as rendered on the screen,
+     * so it takes user-configured font size and DPI into account.
+     */
+    Q_PROPERTY(int smallSpacing READ smallSpacing WRITE setSmallSpacing NOTIFY smallSpacingChanged)
+
+    /**
+     * This property holds the amount of spacing that should be used between medium UI elements,
+     * such as buttons and text fields in a toolbar.
+     * Internally, this size depends on the size of the default font as rendered on the screen,
+     * so it takes user-configured font size and DPI into account.
+     */
+    Q_PROPERTY(int mediumSpacing READ mediumSpacing WRITE setMediumSpacing NOTIFY mediumSpacingChanged)
+
+    /**
+     * This property holds the amount of spacing that should be used between bigger UI elements,
+     * such as a large icon and a heading in a card.
+     * Internally, this size depends on the size of the default font as rendered on the screen,
+     * so it takes user-configured font size and DPI into account.
+     */
+    Q_PROPERTY(int largeSpacing READ largeSpacing WRITE setLargeSpacing NOTIFY largeSpacingChanged)
+
+    /**
+     * The ratio between physical and device-independent pixels. This value does not depend on the \
+     * size of the configured font. If you want to take font sizes into account when scaling elements,
+     * use theme.mSize(theme.defaultFont), units.smallSpacing and units.largeSpacing.
+     * The devicePixelRatio follows the definition of "device independent pixel" by Microsoft.
+     *
+     * @deprecated since 5.86. When using Qt's high DPI scaling, all sizes are
+     * considered to be device-independent pixels, so this will simply return 1.
+     */
+    Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged)
+
+    /**
+     * This property should be used for specialty animations that benefit
+     * from being even longer than longDuration.
+     */
+    Q_PROPERTY(int veryLongDuration READ veryLongDuration WRITE setVeryLongDuration NOTIFY veryLongDurationChanged)
+
+    /**
+     * This property should be used for longer, screen-covering animations, for opening and
+     * closing of dialogs and other "not too small" animations
+     */
+    Q_PROPERTY(int longDuration READ longDuration WRITE setLongDuration NOTIFY longDurationChanged)
+
+    /**
+     * This property should be used for short animations, such as accentuating a UI event,
+     * hover events, etc..
+     */
+    Q_PROPERTY(int shortDuration READ shortDuration WRITE setShortDuration NOTIFY shortDurationChanged)
+
+    /**
+     * This property should be used for elements that should have a hint of smoothness,
+     * but otherwise animate near instantly.
+     */
+    Q_PROPERTY(int veryShortDuration READ veryShortDuration WRITE setVeryShortDuration NOTIFY veryShortDurationChanged)
+
+    /**
+     * Time in milliseconds equivalent to the theoretical human moment, which can be used
+     * to determine whether how long to wait until the user should be informed of something,
+     * or can be used as the limit for how long something should wait before being
+     * automatically initiated.
+     *
+     * Some examples:
+     *
+     * - When the user types text in a search field, wait no longer than this duration after
+     *   the user completes typing before starting the search
+     * - When loading data which would commonly arrive rapidly enough to not require interaction,
+     *   wait this long before showing a spinner
+     *
+     * This might seem an arbitrary number, but given the psychological effect that three
+     * seconds seems to be what humans consider a moment (and in the case of waiting for
+     * something to happen, a moment is that time when you think "this is taking a bit long,
+     * isn't it?"), the idea is to postpone for just before such a conceptual moment. The reason
+     * for the two seconds, rather than three, is to function as a middle ground: Not long enough
+     * that the user would think that something has taken too long, for also not so fast as to
+     * happen too soon.
+     *
+     * See also
+     * https://www.psychologytoday.com/blog/all-about-addiction/201101/tick-tock-tick-hugs-and-life-in-3-second-intervals
+     * (the actual paper is hidden behind an academic paywall and consequently not readily
+     * available to us, so the source will have to be the blog entry above)
+     *
+     * \note This should __not__ be used as an animation duration, as it is deliberately not scaled according
+     * to the animation settings. This is specifically for determining when something has taken too long and
+     * the user should expect some kind of feedback. See ::veryShortDuration, ::shortDuration, ::longDuration, and
+     * ::veryLongDuration for animation duration choices.
+     *
+     * @since KDE Frameworks 5.81
+     * @since org.kde.kirigami 2.16
+     */
+    Q_PROPERTY(int humanMoment READ humanMoment WRITE setHumanMoment NOTIFY humanMomentChanged)
+
+    /**
+     * time in ms by which the display of tooltips will be delayed.
+     *
+     * @see <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-tooltip.html#delay-prop">ToolTip.delay</a>
+     */
+    Q_PROPERTY(int toolTipDelay READ toolTipDelay WRITE setToolTipDelay NOTIFY toolTipDelayChanged)
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 86)
+    /**
+     * How much the mouse scroll wheel scrolls, expressed in lines of text.
+     * Note: this is strictly for classical mouse wheels, touchpads 2 figer scrolling won't be affected
+     */
+    Q_PROPERTY(int wheelScrollLines READ wheelScrollLines NOTIFY wheelScrollLinesChanged)
+#endif
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 86)
+    /**
+     * metrics used by the default font
+     *
+     * @deprecated since 5.86.0, Create your own TextMetrics object if needed.
+     * For the roundedIconSize function, use Units.iconSizes.roundedIconSize instead
+     */
+    Q_PROPERTY(QObject *fontMetrics READ fontMetrics CONSTANT)
+#endif
+
+    Q_PROPERTY(int maximumInteger READ maximumInteger CONSTANT)
+
+public:
+    explicit Units(QObject *parent = nullptr);
+    ~Units() override;
+
+    int gridUnit() const;
+    void setGridUnit(int size);
+
+    int smallSpacing() const;
+    void setSmallSpacing(int size);
+
+    int mediumSpacing() const;
+    void setMediumSpacing(int size);
+
+    int largeSpacing() const;
+    void setLargeSpacing(int size);
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 86)
+    // TODO KF6 remove
+    KIRIGAMI2_DEPRECATED_VERSION(5, 86, "When using Qt scaling, this would return a value of 1")
+    qreal devicePixelRatio() const;
+#endif
+
+    int veryLongDuration() const;
+    void setVeryLongDuration(int duration);
+
+    int longDuration() const;
+    void setLongDuration(int duration);
+
+    int shortDuration() const;
+    void setShortDuration(int duration);
+
+    int veryShortDuration() const;
+    void setVeryShortDuration(int duration);
+
+    int humanMoment() const;
+    void setHumanMoment(int duration);
+
+    int toolTipDelay() const;
+    void setToolTipDelay(int delay);
+
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 86)
+    // TODO KF6 remove
+    KIRIGAMI2_DEPRECATED_VERSION(5, 86, "Use Qt.styleHints.wheelScrollLines instead")
+    int wheelScrollLines() const;
+    void setWheelScrollLines(int lines);
+#endif
+
+    IconSizes *iconSizes() const;
+
+    int maximumInteger() const;
+
+Q_SIGNALS:
+    void gridUnitChanged();
+    void smallSpacingChanged();
+    void mediumSpacingChanged();
+    void largeSpacingChanged();
+    void devicePixelRatioChanged();
+    void veryLongDurationChanged();
+    void longDurationChanged();
+    void shortDurationChanged();
+    void veryShortDurationChanged();
+    void humanMomentChanged();
+    void toolTipDelayChanged();
+    void wheelScrollLinesChanged();
+
+private:
+#if KIRIGAMI2_ENABLE_DEPRECATED_SINCE(5, 86)
+    QObject *fontMetrics() const;
+#endif
+
+    std::unique_ptr<UnitsPrivate> d;
+};
+
+}
+
+#endif
diff --git a/src/libkirigami/virtualkeyboardwatcher.cpp b/src/libkirigami/virtualkeyboardwatcher.cpp
new file mode 100644 (file)
index 0000000..852b00c
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "virtualkeyboardwatcher.h"
+
+#ifdef KIRIGAMI_ENABLE_DBUS
+#include "virtualkeyboard_interface.h"
+#include <QDBusConnection>
+#include <QDBusPendingCallWatcher>
+#endif
+
+#include "loggingcategory.h"
+
+namespace Kirigami
+{
+Q_GLOBAL_STATIC(VirtualKeyboardWatcher, virtualKeyboardWatcherSelf)
+
+class Q_DECL_HIDDEN VirtualKeyboardWatcher::Private
+{
+public:
+    Private(VirtualKeyboardWatcher *qq)
+        : q(qq)
+    {
+    }
+
+    VirtualKeyboardWatcher *q;
+
+#ifdef KIRIGAMI_ENABLE_DBUS
+    void getAllProperties();
+    void getProperty(const QString &propertyName);
+    void updateWillShowOnActive();
+
+    OrgKdeKwinVirtualKeyboardInterface *keyboardInterface = nullptr;
+    OrgFreedesktopDBusPropertiesInterface *propertiesInterface = nullptr;
+
+    QDBusPendingCallWatcher *willShowOnActiveCall = nullptr;
+#endif
+
+    bool available = false;
+    bool enabled = false;
+    bool active = false;
+    bool visible = false;
+    bool willShowOnActive = false;
+
+    static const QString serviceName;
+    static const QString objectName;
+    static const QString interfaceName;
+};
+
+const QString VirtualKeyboardWatcher::Private::serviceName = QStringLiteral("org.kde.KWin");
+const QString VirtualKeyboardWatcher::Private::objectName = QStringLiteral("/VirtualKeyboard");
+const QString VirtualKeyboardWatcher::Private::interfaceName = QStringLiteral("org.kde.kwin.VirtualKeyboard");
+
+VirtualKeyboardWatcher::VirtualKeyboardWatcher(QObject *parent)
+    : QObject(parent)
+    , d(std::make_unique<Private>(this))
+{
+#ifdef KIRIGAMI_ENABLE_DBUS
+    d->keyboardInterface = new OrgKdeKwinVirtualKeyboardInterface(Private::serviceName, Private::objectName, QDBusConnection::sessionBus(), this);
+    d->propertiesInterface = new OrgFreedesktopDBusPropertiesInterface(Private::serviceName, Private::objectName, QDBusConnection::sessionBus(), this);
+
+    connect(d->keyboardInterface, &OrgKdeKwinVirtualKeyboardInterface::availableChanged, this, [this]() {
+        d->getProperty(QStringLiteral("available"));
+    });
+    connect(d->keyboardInterface, &OrgKdeKwinVirtualKeyboardInterface::enabledChanged, this, [this]() {
+        d->getProperty(QStringLiteral("enabled"));
+    });
+    connect(d->keyboardInterface, &OrgKdeKwinVirtualKeyboardInterface::activeChanged, this, [this]() {
+        d->getProperty(QStringLiteral("active"));
+    });
+    connect(d->keyboardInterface, &OrgKdeKwinVirtualKeyboardInterface::visibleChanged, this, [this]() {
+        d->getProperty(QStringLiteral("visible"));
+    });
+
+    d->getAllProperties();
+#endif
+}
+
+VirtualKeyboardWatcher::~VirtualKeyboardWatcher() = default;
+
+bool VirtualKeyboardWatcher::available() const
+{
+    return d->available;
+}
+
+bool VirtualKeyboardWatcher::enabled() const
+{
+    return d->enabled;
+}
+
+void VirtualKeyboardWatcher::setEnabled(bool newEnabled)
+{
+    if (newEnabled == d->enabled) {
+        return;
+    }
+
+    d->enabled = newEnabled;
+
+#ifdef KIRIGAMI_ENABLE_DBUS
+    d->propertiesInterface->Set(Private::interfaceName, QStringLiteral("enabled"), QDBusVariant(newEnabled));
+#else
+    Q_EMIT enabledChanged();
+#endif
+}
+
+bool VirtualKeyboardWatcher::active() const
+{
+    return d->active;
+}
+
+void VirtualKeyboardWatcher::setActive(bool newActive)
+{
+    if (newActive == d->active) {
+        return;
+    }
+
+    d->active = newActive;
+
+#ifdef KIRIGAMI_ENABLE_DBUS
+    d->propertiesInterface->Set(Private::interfaceName, QStringLiteral("active"), QDBusVariant(newActive));
+#else
+    Q_EMIT activeChanged();
+#endif
+}
+
+bool VirtualKeyboardWatcher::visible() const
+{
+    return d->visible;
+}
+
+bool VirtualKeyboardWatcher::willShowOnActive() const
+{
+#ifdef KIRIGAMI_ENABLE_DBUS
+    d->updateWillShowOnActive();
+#endif
+    return d->willShowOnActive;
+}
+
+VirtualKeyboardWatcher *VirtualKeyboardWatcher::self()
+{
+    return virtualKeyboardWatcherSelf();
+}
+
+#ifdef KIRIGAMI_ENABLE_DBUS
+
+void VirtualKeyboardWatcher::Private::updateWillShowOnActive()
+{
+    if (willShowOnActiveCall) {
+        return;
+    }
+
+    willShowOnActiveCall = new QDBusPendingCallWatcher(keyboardInterface->willShowOnActive(), q);
+    connect(willShowOnActiveCall, &QDBusPendingCallWatcher::finished, q, [this](auto call) {
+        QDBusPendingReply<bool> reply = *call;
+        if (reply.isError()) {
+            qCDebug(KirigamiLog) << reply.error().message();
+        } else {
+            if (reply.value() != willShowOnActive) {
+                willShowOnActive = reply.value();
+                Q_EMIT q->willShowOnActiveChanged();
+            }
+        }
+        call->deleteLater();
+        willShowOnActiveCall = nullptr;
+    });
+}
+
+void VirtualKeyboardWatcher::Private::getAllProperties()
+{
+    auto call = new QDBusPendingCallWatcher(propertiesInterface->GetAll(interfaceName), q);
+    connect(call, &QDBusPendingCallWatcher::finished, q, [this](auto call) {
+        QDBusPendingReply<QVariantMap> reply = *call;
+        if (reply.isError()) {
+            qCDebug(KirigamiLog) << reply.error().message();
+        } else {
+            auto value = reply.value();
+            available = value.value(QStringLiteral("available")).toBool();
+            enabled = value.value(QStringLiteral("enabled")).toBool();
+            active = value.value(QStringLiteral("active")).toBool();
+            visible = value.value(QStringLiteral("visible")).toBool();
+        }
+        call->deleteLater();
+
+        Q_EMIT q->availableChanged();
+        Q_EMIT q->enabledChanged();
+        Q_EMIT q->activeChanged();
+        Q_EMIT q->visibleChanged();
+    });
+}
+
+void VirtualKeyboardWatcher::Private::getProperty(const QString &propertyName)
+{
+    auto call = new QDBusPendingCallWatcher(propertiesInterface->Get(interfaceName, propertyName), q);
+    connect(call, &QDBusPendingCallWatcher::finished, q, [this, propertyName](auto call) {
+        QDBusPendingReply<QDBusVariant> reply = *call;
+        if (reply.isError()) {
+            qCDebug(KirigamiLog) << reply.error().message();
+        } else {
+            auto value = reply.value();
+            if (propertyName == QStringLiteral("available")) {
+                available = value.variant().toBool();
+                Q_EMIT q->availableChanged();
+            } else if (propertyName == QStringLiteral("enabled")) {
+                enabled = value.variant().toBool();
+                Q_EMIT q->enabledChanged();
+            } else if (propertyName == QStringLiteral("active")) {
+                active = value.variant().toBool();
+                Q_EMIT q->activeChanged();
+            } else if (propertyName == QStringLiteral("visible")) {
+                visible = value.variant().toBool();
+                Q_EMIT q->visibleChanged();
+            }
+        }
+        call->deleteLater();
+    });
+}
+
+#endif
+}
diff --git a/src/libkirigami/virtualkeyboardwatcher.h b/src/libkirigami/virtualkeyboardwatcher.h
new file mode 100644 (file)
index 0000000..aec7b84
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef KIRIGAMI_VIRTUALKEYBOARDWATCHER_H
+#define KIRIGAMI_VIRTUALKEYBOARDWATCHER_H
+
+#include <memory>
+
+#include <QObject>
+
+#include "kirigami2_export.h"
+
+namespace Kirigami
+{
+/**
+ * @class VirtualKeyboardWatcher virtualkeyboardwatcher.h <Kirigami/VirtualKeyboardWatcher>
+ *
+ * This class reports on the status of KWin's VirtualKeyboard DBus interface.
+ *
+ * @since KDE Frameworks 5.91
+ */
+class KIRIGAMI2_EXPORT VirtualKeyboardWatcher : public QObject
+{
+    Q_OBJECT
+
+public:
+    VirtualKeyboardWatcher(QObject *parent = nullptr);
+    ~VirtualKeyboardWatcher();
+
+    Q_PROPERTY(bool available READ available NOTIFY availableChanged)
+    bool available() const;
+    Q_SIGNAL void availableChanged();
+
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+    bool enabled() const;
+    void setEnabled(bool newEnabled);
+    Q_SIGNAL void enabledChanged();
+
+    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+    bool active() const;
+    void setActive(bool newActive);
+    Q_SIGNAL void activeChanged();
+
+    Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
+    bool visible() const;
+    Q_SIGNAL void visibleChanged();
+
+    Q_PROPERTY(bool willShowOnActive READ willShowOnActive NOTIFY willShowOnActiveChanged)
+    bool willShowOnActive() const;
+    Q_SIGNAL void willShowOnActiveChanged();
+
+    static VirtualKeyboardWatcher *self();
+
+private:
+    class Private;
+    const std::unique_ptr<Private> d;
+};
+
+}
+
+#endif // KIRIGAMI_VIRTUALKEYBOARDWATCHER
diff --git a/src/mnemonicattached.cpp b/src/mnemonicattached.cpp
new file mode 100644 (file)
index 0000000..937de64
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "mnemonicattached.h"
+#include <QDebug>
+#include <QQuickItem>
+#include <QQuickRenderControl>
+
+QHash<QKeySequence, MnemonicAttached *> MnemonicAttached::s_sequenceToObject = QHash<QKeySequence, MnemonicAttached *>();
+
+// If pos points to alphanumeric X in "...(X)...", which is preceded or
+// followed only by non-alphanumerics, then "(X)" gets removed.
+static QString removeReducedCJKAccMark(const QString &label, int pos)
+{
+    /* clang-format off */
+    if (pos > 0 && pos + 1 < label.length()
+        && label[pos - 1] == QLatin1Char('(')
+        && label[pos + 1] == QLatin1Char(')')
+        && label[pos].isLetterOrNumber()) { /* clang-format on */
+        // Check if at start or end, ignoring non-alphanumerics.
+        int len = label.length();
+        int p1 = pos - 2;
+        while (p1 >= 0 && !label[p1].isLetterOrNumber()) {
+            --p1;
+        }
+        ++p1;
+        int p2 = pos + 2;
+        while (p2 < len && !label[p2].isLetterOrNumber()) {
+            ++p2;
+        }
+        --p2;
+
+        const QStringView strView(label);
+        if (p1 == 0) {
+            return strView.left(pos - 1) + strView.mid(p2 + 1);
+        } else if (p2 + 1 == len) {
+            return strView.left(p1) + strView.mid(pos + 2);
+        }
+    }
+    return label;
+}
+
+static QString removeAcceleratorMarker(const QString &label_)
+{
+    QString label = label_;
+
+    int p = 0;
+    bool accmarkRemoved = false;
+    while (true) {
+        p = label.indexOf(QLatin1Char('&'), p);
+        if (p < 0 || p + 1 == label.length()) {
+            break;
+        }
+
+        if (label.at(p + 1).isLetterOrNumber()) {
+            // Valid accelerator.
+            const QStringView sv(label);
+            label = sv.left(p) + sv.mid(p + 1);
+
+            // May have been an accelerator in CJK-style "(&X)"
+            // at the start or end of text.
+            label = removeReducedCJKAccMark(label, p);
+
+            accmarkRemoved = true;
+        } else if (label.at(p + 1) == QLatin1Char('&')) {
+            // Escaped accelerator marker.
+            const QStringView sv(label);
+            label = sv.left(p) + sv.mid(p + 1);
+        }
+
+        ++p;
+    }
+
+    // If no marker was removed, and there are CJK characters in the label,
+    // also try to remove reduced CJK marker -- something may have removed
+    // ampersand beforehand.
+    if (!accmarkRemoved) {
+        bool hasCJK = false;
+        for (const QChar c : std::as_const(label)) {
+            if (c.unicode() >= 0x2e00) { // rough, but should be sufficient
+                hasCJK = true;
+                break;
+            }
+        }
+        if (hasCJK) {
+            p = 0;
+            while (true) {
+                p = label.indexOf(QLatin1Char('('), p);
+                if (p < 0) {
+                    break;
+                }
+                label = removeReducedCJKAccMark(label, p + 1);
+                ++p;
+            }
+        }
+    }
+
+    return label;
+}
+
+MnemonicAttached::MnemonicAttached(QObject *parent)
+    : QObject(parent)
+{
+    QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent);
+    if (parentItem) {
+        if (parentItem->window()) {
+            m_window = parentItem->window();
+            m_window->installEventFilter(this);
+        }
+        connect(parentItem, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) {
+            removeEventFilterForWindow(m_window);
+            m_window = window;
+            installEventFilterForWindow(m_window);
+        });
+    }
+}
+
+MnemonicAttached::~MnemonicAttached()
+{
+    s_sequenceToObject.remove(m_sequence);
+}
+
+bool MnemonicAttached::eventFilter(QObject *watched, QEvent *e)
+{
+    Q_UNUSED(watched)
+
+    if (m_richTextLabel.isEmpty()) {
+        return false;
+    }
+
+    if (e->type() == QEvent::KeyPress) {
+        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+        if (ke->key() == Qt::Key_Alt) {
+            m_actualRichTextLabel = m_richTextLabel;
+            Q_EMIT richTextLabelChanged();
+            m_active = true;
+            Q_EMIT activeChanged();
+        }
+
+    } else if (e->type() == QEvent::KeyRelease) {
+        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+        if (ke->key() == Qt::Key_Alt) {
+            m_actualRichTextLabel = removeAcceleratorMarker(m_label);
+            Q_EMIT richTextLabelChanged();
+            m_active = false;
+            Q_EMIT activeChanged();
+        }
+    }
+    return false;
+}
+
+// Algorithm adapted from KAccelString
+void MnemonicAttached::calculateWeights()
+{
+    m_weights.clear();
+
+    int pos = 0;
+    bool start_character = true;
+    bool wanted_character = false;
+
+    while (pos < m_label.length()) {
+        QChar c = m_label[pos];
+
+        // skip non typeable characters
+        if (!c.isLetterOrNumber() && c != QLatin1Char('&')) {
+            start_character = true;
+            ++pos;
+            continue;
+        }
+
+        int weight = 1;
+
+        // add special weight to first character
+        if (pos == 0) {
+            weight += FIRST_CHARACTER_EXTRA_WEIGHT;
+            // add weight to word beginnings
+        } else if (start_character) {
+            weight += WORD_BEGINNING_EXTRA_WEIGHT;
+            start_character = false;
+        }
+
+        // add weight to characters that have an & beforehand
+        if (wanted_character) {
+            weight += WANTED_ACCEL_EXTRA_WEIGHT;
+            wanted_character = false;
+        }
+
+        // add decreasing weight to left characters
+        if (pos < 50) {
+            weight += (50 - pos);
+        }
+
+        // try to preserve the wanted accelerators
+        /* clang-format off */
+        if (c == QLatin1Char('&')
+            && (pos != m_label.length() - 1
+                && m_label[pos + 1] != QLatin1Char('&')
+                && m_label[pos + 1].isLetterOrNumber())) { /* clang-format on */
+            wanted_character = true;
+            ++pos;
+            continue;
+        }
+
+        while (m_weights.contains(weight)) {
+            ++weight;
+        }
+
+        if (c != QLatin1Char('&')) {
+            m_weights[weight] = c;
+        }
+
+        ++pos;
+    }
+
+    // update our maximum weight
+    if (m_weights.isEmpty()) {
+        m_weight = m_baseWeight;
+    } else {
+        m_weight = m_baseWeight + (m_weights.cend() - 1).key();
+    }
+}
+
+bool MnemonicAttached::installEventFilterForWindow(QQuickWindow *wnd)
+{
+    if (!wnd) {
+        return false;
+    }
+    QWindow *renderWindow = QQuickRenderControl::renderWindowFor(wnd);
+    // renderWindow means the widget is rendering somewhere else, like a QQuickWidget
+    if (renderWindow && renderWindow != m_window) {
+        renderWindow->installEventFilter(this);
+    } else {
+        wnd->installEventFilter(this);
+    }
+    return true;
+}
+
+bool MnemonicAttached::removeEventFilterForWindow(QQuickWindow *wnd)
+{
+    if (!wnd) {
+        return false;
+    }
+    QWindow *renderWindow = QQuickRenderControl::renderWindowFor(wnd);
+    if (renderWindow) {
+        renderWindow->removeEventFilter(this);
+    } else {
+        wnd->removeEventFilter(this);
+    }
+    return true;
+}
+
+void MnemonicAttached::updateSequence()
+{
+    if (!m_sequence.isEmpty()) {
+        s_sequenceToObject.remove(m_sequence);
+        m_sequence = {};
+    }
+
+    calculateWeights();
+
+    // Preserve strings like "One & Two" where & is not an accelerator escape
+    const QString text = label().replace(QStringLiteral("& "), QStringLiteral("&& "));
+
+    if (!m_enabled) {
+        m_actualRichTextLabel = removeAcceleratorMarker(text);
+        // was the label already completely plain text? try to limit signal emission
+        if (m_mnemonicLabel != m_actualRichTextLabel) {
+            m_mnemonicLabel = m_actualRichTextLabel;
+            Q_EMIT mnemonicLabelChanged();
+            Q_EMIT richTextLabelChanged();
+        }
+        return;
+    }
+
+    if (!m_weights.isEmpty()) {
+        QMap<int, QChar>::const_iterator i = m_weights.constEnd();
+        do {
+            --i;
+            QChar c = i.value();
+
+            QKeySequence ks(QStringLiteral("Alt+") % c);
+            MnemonicAttached *otherMa = s_sequenceToObject.value(ks);
+            Q_ASSERT(otherMa != this);
+            if (!otherMa || otherMa->m_weight < m_weight) {
+                // the old shortcut is less valuable than the current: remove it
+                if (otherMa) {
+                    s_sequenceToObject.remove(otherMa->sequence());
+                    otherMa->m_sequence = {};
+                }
+
+                s_sequenceToObject[ks] = this;
+                m_sequence = ks;
+                m_richTextLabel = text;
+                m_richTextLabel.replace(QRegularExpression(QLatin1String("\\&([^\\&])")), QStringLiteral("\\1"));
+                m_actualRichTextLabel = m_richTextLabel;
+                m_mnemonicLabel = text;
+                const int mnemonicPos = m_mnemonicLabel.indexOf(c);
+
+                if (mnemonicPos > -1 && (mnemonicPos == 0 || m_mnemonicLabel[mnemonicPos - 1] != QLatin1Char('&'))) {
+                    m_mnemonicLabel.replace(mnemonicPos, 1, QStringLiteral("&") % c);
+                }
+
+                const int richTextPos = m_richTextLabel.indexOf(c);
+                if (richTextPos > -1) {
+                    m_richTextLabel.replace(richTextPos, 1, QLatin1String("<u>") % c % QLatin1String("</u>"));
+                }
+
+                // remap the sequence of the previous shortcut
+                if (otherMa) {
+                    otherMa->updateSequence();
+                }
+
+                break;
+            }
+        } while (i != m_weights.constBegin());
+    }
+
+    if (!m_sequence.isEmpty()) {
+        Q_EMIT sequenceChanged();
+    }
+    m_actualRichTextLabel = removeAcceleratorMarker(text);
+    m_mnemonicLabel = m_actualRichTextLabel;
+
+    Q_EMIT richTextLabelChanged();
+    Q_EMIT mnemonicLabelChanged();
+}
+
+void MnemonicAttached::setLabel(const QString &text)
+{
+    if (m_label == text) {
+        return;
+    }
+
+    m_label = text;
+    updateSequence();
+    Q_EMIT labelChanged();
+}
+
+QString MnemonicAttached::richTextLabel() const
+{
+    if (!m_actualRichTextLabel.isEmpty()) {
+        return m_actualRichTextLabel;
+    } else {
+        return removeAcceleratorMarker(m_label);
+    }
+}
+
+QString MnemonicAttached::mnemonicLabel() const
+{
+    return m_mnemonicLabel;
+}
+
+QString MnemonicAttached::label() const
+{
+    return m_label;
+}
+
+void MnemonicAttached::setEnabled(bool enabled)
+{
+    if (m_enabled == enabled) {
+        return;
+    }
+
+    m_enabled = enabled;
+    updateSequence();
+    Q_EMIT enabledChanged();
+}
+
+bool MnemonicAttached::enabled() const
+{
+    return m_enabled;
+}
+
+void MnemonicAttached::setControlType(MnemonicAttached::ControlType controlType)
+{
+    if (m_controlType == controlType) {
+        return;
+    }
+
+    m_controlType = controlType;
+
+    switch (controlType) {
+    case ActionElement:
+        m_baseWeight = ACTION_ELEMENT_WEIGHT;
+        break;
+    case DialogButton:
+        m_baseWeight = DIALOG_BUTTON_EXTRA_WEIGHT;
+        break;
+    case MenuItem:
+        m_baseWeight = MENU_ITEM_WEIGHT;
+        break;
+    case FormLabel:
+        m_baseWeight = FORM_LABEL_WEIGHT;
+        break;
+    default:
+        m_baseWeight = SECONDARY_CONTROL_WEIGHT;
+        break;
+    }
+    // update our maximum weight
+    if (m_weights.isEmpty()) {
+        m_weight = m_baseWeight;
+    } else {
+        m_weight = m_baseWeight + (m_weights.constEnd() - 1).key();
+    }
+    Q_EMIT controlTypeChanged();
+}
+
+MnemonicAttached::ControlType MnemonicAttached::controlType() const
+{
+    return m_controlType;
+}
+
+QKeySequence MnemonicAttached::sequence()
+{
+    return m_sequence;
+}
+
+bool MnemonicAttached::active() const
+{
+    return m_active;
+}
+
+MnemonicAttached *MnemonicAttached::qmlAttachedProperties(QObject *object)
+{
+    return new MnemonicAttached(object);
+}
+
+void MnemonicAttached::setActive(bool active)
+{
+    // We can't rely on previous value when it's true since it can be
+    // caused by Alt key press and we need to remove the event filter
+    // additionally. False should be ok as it's a default state.
+    if (!m_active && m_active == active) {
+        return;
+    }
+
+    m_active = active;
+
+    if (m_active) {
+        removeEventFilterForWindow(m_window);
+        if (m_actualRichTextLabel != m_richTextLabel) {
+            m_actualRichTextLabel = m_richTextLabel;
+            Q_EMIT richTextLabelChanged();
+        }
+
+    } else {
+        installEventFilterForWindow(m_window);
+        m_actualRichTextLabel = removeAcceleratorMarker(m_label);
+        Q_EMIT richTextLabelChanged();
+    }
+
+    Q_EMIT activeChanged();
+}
+
+#include "moc_mnemonicattached.cpp"
diff --git a/src/mnemonicattached.h b/src/mnemonicattached.h
new file mode 100644 (file)
index 0000000..dfe5532
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef MNEMONICATTACHED_H
+#define MNEMONICATTACHED_H
+
+#include <QObject>
+#include <QQuickWindow>
+#include <QtQml>
+
+/**
+ * @brief This attached property allows to define keyboard sequences to trigger
+ * actions based upon their text.
+ *
+ * A mnemonic, otherwise known as an accelerator, is an accessibility feature to
+ * signal to the user that a certain action (typically in a menu) can be
+ * triggered by pressing Alt + a certain key that is indicated by an ampersand
+ * sign (&). For instance, a File menu could be marked in code as &File and
+ * would be displayed to the user with an underscore under the letter F. This
+ * allows to invoke actions without having to navigate the UI with a mouse.
+ *
+ * This class automates the management of mnemonics, so if a key is already
+ * taken, the next available key is used. Likewise, certain components get
+ * increased priority: an "OK/Cancel" buttons in a Dialog will have priority
+ * over fields of a org::kde::kirigami::FormLayout.
+ *
+ * Mnemonics are already managed by visual QtQuick and Kirigami controls, so
+ * only use this class to implement your own visual QML controls.
+ *
+ * @see ::ControlType
+ * 
+ * @since org.kde.kirigami 2.3
+ */
+class MnemonicAttached : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the label of the control that we want to
+     * compute a mnemonic for.
+     *
+     * For example: ``"Label:"`` or ``"&Ok"``
+     */
+    Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
+
+    /**
+     * @brief This property holds the user-visible final label.
+     *
+     * The user-visible final label, which will have the shortcut letter
+     * underlined, such as "&lt;u&gt;O&lt;/u&gt;k".
+     */
+    Q_PROPERTY(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged)
+
+    /**
+     * @brief This property holds the label with an "&" mnemonic in the place
+     * which defines the shortcut key.
+     *
+     * @note The "&" will be automatically added if it is not set by the
+     * user.
+     */
+    Q_PROPERTY(QString mnemonicLabel READ mnemonicLabel NOTIFY mnemonicLabelChanged)
+
+    /**
+     * @brief This property sets whether this mnemonic is enabled.
+     *
+     * Set this to @c false to disable the accelerator marker (&) and its
+     * respective shortcut.
+     *
+     * default: ``true``
+     */
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+    /**
+     * @brief This property holds the control type that this mnemonic is
+     * attached to.
+     *
+     * @note Different types of controls have different importance and priority
+     * for shortcut assignment.
+     *
+     * @see ::ControlType
+     */
+    Q_PROPERTY(MnemonicAttached::ControlType controlType READ controlType WRITE setControlType NOTIFY controlTypeChanged)
+
+    /**
+     * @brief This property holds the final key sequence.
+     *
+     * @note The final key sequence will be Alt+alphanumeric char.
+     */
+    Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged)
+
+    /**
+     * @brief This property holds whether the user is pressing alt and
+     * accelerators should be shown.
+     *
+     * @since KDE Frameworks 5.72
+     * @since org.kde.kirigami 2.15
+     */
+    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+
+public:
+    enum ControlType {
+        ActionElement, /**< pushbuttons, checkboxes etc */
+        DialogButton, /**< buttons for dialogs */
+        MenuItem, /**< Menu items */
+        FormLabel, /**< Buddy label in a FormLayout*/
+        SecondaryControl, /**< Other controls that are considered not much important and low priority for shortcuts */
+    };
+    Q_ENUM(ControlType)
+
+    explicit MnemonicAttached(QObject *parent = nullptr);
+    ~MnemonicAttached() override;
+
+    void setLabel(const QString &text);
+    QString label() const;
+
+    QString richTextLabel() const;
+    QString mnemonicLabel() const;
+
+    void setEnabled(bool enabled);
+    bool enabled() const;
+
+    void setControlType(MnemonicAttached::ControlType controlType);
+    ControlType controlType() const;
+
+    QKeySequence sequence();
+
+    void setActive(bool active);
+    bool active() const;
+
+    // QML attached property
+    static MnemonicAttached *qmlAttachedProperties(QObject *object);
+
+protected:
+    bool eventFilter(QObject *watched, QEvent *e) override;
+    void updateSequence();
+
+Q_SIGNALS:
+    void labelChanged();
+    void enabledChanged();
+    void sequenceChanged();
+    void richTextLabelChanged();
+    void mnemonicLabelChanged();
+    void controlTypeChanged();
+    void activeChanged();
+
+private:
+    void calculateWeights();
+    bool installEventFilterForWindow(QQuickWindow *wnd);
+    bool removeEventFilterForWindow(QQuickWindow *wnd);
+
+    // TODO: to have support for DIALOG_BUTTON_EXTRA_WEIGHT etc, a type enum should be exported
+    enum {
+        // Additional weight for first character in string
+        FIRST_CHARACTER_EXTRA_WEIGHT = 50,
+        // Additional weight for the beginning of a word
+        WORD_BEGINNING_EXTRA_WEIGHT = 50,
+        // Additional weight for a 'wanted' accelerator ie string with '&'
+        WANTED_ACCEL_EXTRA_WEIGHT = 150,
+        // Default weight for an 'action' widget (ie, pushbuttons)
+        ACTION_ELEMENT_WEIGHT = 50,
+        // Additional weight for the dialog buttons (large, we basically never want these reassigned)
+        DIALOG_BUTTON_EXTRA_WEIGHT = 300,
+        // Weight for FormLayout labels (low)
+        FORM_LABEL_WEIGHT = 20,
+        // Weight for Secondary controls which are considered less important (low)
+        SECONDARY_CONTROL_WEIGHT = 10,
+        // Default weight for menu items
+        MENU_ITEM_WEIGHT = 250,
+    };
+
+    // order word letters by weight
+    int m_weight = 0;
+    int m_baseWeight = 0;
+    ControlType m_controlType = SecondaryControl;
+    QMap<int, QChar> m_weights;
+
+    QString m_label;
+    QString m_actualRichTextLabel;
+    QString m_richTextLabel;
+    QString m_mnemonicLabel;
+    QKeySequence m_sequence;
+    bool m_enabled = true;
+    bool m_active = false;
+
+    QPointer<QQuickWindow> m_window;
+
+    // global mapping of mnemonics
+    // TODO: map by QWindow
+    static QHash<QKeySequence, MnemonicAttached *> s_sequenceToObject;
+};
+
+QML_DECLARE_TYPEINFO(MnemonicAttached, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // MnemonicATTACHED_H
diff --git a/src/pagepool.cpp b/src/pagepool.cpp
new file mode 100644 (file)
index 0000000..89c222a
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "pagepool.h"
+
+#include <QDebug>
+#include <QQmlComponent>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QQmlProperty>
+
+#include "loggingcategory.h"
+
+PagePool::PagePool(QObject *parent)
+    : QObject(parent)
+{
+}
+
+PagePool::~PagePool()
+{
+}
+
+QUrl PagePool::lastLoadedUrl() const
+{
+    return m_lastLoadedUrl;
+}
+
+QQuickItem *PagePool::lastLoadedItem() const
+{
+    return m_lastLoadedItem;
+}
+
+QList<QObject *> PagePool::items() const
+{
+    auto items = m_itemForUrl.values();
+    return QList<QObject *>(items.cbegin(), items.cend());
+}
+
+QList<QUrl> PagePool::urls() const
+{
+    return m_urlForItem.values();
+}
+
+void PagePool::setCachePages(bool cache)
+{
+    if (cache == m_cachePages) {
+        return;
+    }
+
+    if (cache) {
+        clear();
+    }
+
+    m_cachePages = cache;
+    Q_EMIT cachePagesChanged();
+}
+
+bool PagePool::cachePages() const
+{
+    return m_cachePages;
+}
+
+QQuickItem *PagePool::loadPage(const QString &url, QJSValue callback)
+{
+    return loadPageWithProperties(url, QVariantMap(), callback);
+}
+
+QQuickItem *PagePool::loadPageWithProperties(const QString &url, const QVariantMap &properties, QJSValue callback)
+{
+    Q_ASSERT(qmlEngine(this));
+    QQmlContext *ctx = QQmlEngine::contextForObject(this);
+    Q_ASSERT(ctx);
+
+    const QUrl actualUrl = resolvedUrl(url);
+
+    auto found = m_itemForUrl.find(actualUrl);
+    if (found != m_itemForUrl.end()) {
+        m_lastLoadedUrl = found.key();
+        m_lastLoadedItem = found.value();
+
+        if (callback.isCallable()) {
+            QJSValueList args = {qmlEngine(this)->newQObject(found.value())};
+            callback.call(args);
+            Q_EMIT lastLoadedUrlChanged();
+            Q_EMIT lastLoadedItemChanged();
+            // We could return the item, but for api coherence return null
+            return nullptr;
+
+        } else {
+            Q_EMIT lastLoadedUrlChanged();
+            Q_EMIT lastLoadedItemChanged();
+            return found.value();
+        }
+    }
+
+    QQmlComponent *component = m_componentForUrl.value(actualUrl);
+
+    if (!component) {
+        component = new QQmlComponent(qmlEngine(this), actualUrl, QQmlComponent::PreferSynchronous);
+    }
+
+    if (component->status() == QQmlComponent::Loading) {
+        if (!callback.isCallable()) {
+            component->deleteLater();
+            m_componentForUrl.remove(actualUrl);
+            return nullptr;
+        }
+
+        connect(component, &QQmlComponent::statusChanged, this, [this, component, callback, properties](QQmlComponent::Status status) mutable {
+            if (status != QQmlComponent::Ready) {
+                qCWarning(KirigamiLog) << component->errors();
+                m_componentForUrl.remove(component->url());
+                component->deleteLater();
+                return;
+            }
+            QQuickItem *item = createFromComponent(component, properties);
+            if (item) {
+                QJSValueList args = {qmlEngine(this)->newQObject(item)};
+                callback.call(args);
+            }
+
+            if (m_cachePages) {
+                component->deleteLater();
+            } else {
+                m_componentForUrl[component->url()] = component;
+            }
+        });
+
+        return nullptr;
+
+    } else if (component->status() != QQmlComponent::Ready) {
+        qCWarning(KirigamiLog) << component->errors();
+        return nullptr;
+    }
+
+    QQuickItem *item = createFromComponent(component, properties);
+    if (!item) {
+        return nullptr;
+    }
+
+    if (m_cachePages) {
+        component->deleteLater();
+        QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
+        m_itemForUrl[component->url()] = item;
+        m_urlForItem[item] = component->url();
+        Q_EMIT itemsChanged();
+        Q_EMIT urlsChanged();
+
+    } else {
+        m_componentForUrl[component->url()] = component;
+        QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership);
+    }
+
+    m_lastLoadedUrl = actualUrl;
+    m_lastLoadedItem = item;
+    Q_EMIT lastLoadedUrlChanged();
+    Q_EMIT lastLoadedItemChanged();
+
+    if (callback.isCallable()) {
+        QJSValueList args = {qmlEngine(this)->newQObject(item)};
+        callback.call(args);
+        // We could return the item, but for api coherence return null
+        return nullptr;
+    }
+    return item;
+}
+
+QQuickItem *PagePool::createFromComponent(QQmlComponent *component, const QVariantMap &properties)
+{
+    QQmlContext *ctx = QQmlEngine::contextForObject(this);
+    Q_ASSERT(ctx);
+
+    QObject *obj = component->createWithInitialProperties(properties, ctx);
+
+    if (!obj || component->isError()) {
+        qCWarning(KirigamiLog) << component->errors();
+        if (obj) {
+            obj->deleteLater();
+        }
+        return nullptr;
+    }
+
+    QQuickItem *item = qobject_cast<QQuickItem *>(obj);
+    if (!item) {
+        qCWarning(KirigamiLog) << "Storing Non-QQuickItem in PagePool not supported";
+        obj->deleteLater();
+        return nullptr;
+    }
+
+    return item;
+}
+
+QUrl PagePool::resolvedUrl(const QString &stringUrl) const
+{
+    Q_ASSERT(qmlEngine(this));
+    QQmlContext *ctx = QQmlEngine::contextForObject(this);
+    Q_ASSERT(ctx);
+
+    QUrl actualUrl(stringUrl);
+    if (actualUrl.scheme().isEmpty()) {
+        actualUrl = ctx->resolvedUrl(actualUrl);
+    }
+    return actualUrl;
+}
+
+bool PagePool::isLocalUrl(const QUrl &url)
+{
+    return url.isLocalFile() || url.scheme().isEmpty() || url.scheme() == QStringLiteral("qrc");
+}
+
+QUrl PagePool::urlForPage(QQuickItem *item) const
+{
+    return m_urlForItem.value(item);
+}
+
+QQuickItem *PagePool::pageForUrl(const QUrl &url) const
+{
+    return m_itemForUrl.value(resolvedUrl(url.toString()), nullptr);
+}
+
+bool PagePool::contains(const QVariant &page) const
+{
+    if (page.canConvert<QQuickItem *>()) {
+        return m_urlForItem.contains(page.value<QQuickItem *>());
+
+    } else if (page.canConvert<QString>()) {
+        const QUrl actualUrl = resolvedUrl(page.value<QString>());
+        return m_itemForUrl.contains(actualUrl);
+
+    } else {
+        return false;
+    }
+}
+
+void PagePool::deletePage(const QVariant &page)
+{
+    if (!contains(page)) {
+        return;
+    }
+
+    QQuickItem *item;
+    if (page.canConvert<QQuickItem *>()) {
+        item = page.value<QQuickItem *>();
+    } else if (page.canConvert<QString>()) {
+        QString url = page.value<QString>();
+        if (url.isEmpty()) {
+            return;
+        }
+        const QUrl actualUrl = resolvedUrl(page.value<QString>());
+
+        item = m_itemForUrl.value(actualUrl);
+    } else {
+        return;
+    }
+
+    if (!item) {
+        return;
+    }
+
+    const QUrl url = m_urlForItem.value(item);
+
+    if (url.isEmpty()) {
+        return;
+    }
+
+    m_itemForUrl.remove(url);
+    m_urlForItem.remove(item);
+    item->deleteLater();
+
+    Q_EMIT itemsChanged();
+    Q_EMIT urlsChanged();
+}
+
+void PagePool::clear()
+{
+    for (auto *c : std::as_const(m_componentForUrl)) {
+        c->deleteLater();
+    }
+    m_componentForUrl.clear();
+
+    for (auto *i : std::as_const(m_itemForUrl)) {
+        // items that had been deparented are safe to delete
+        if (!i->parentItem()) {
+            i->deleteLater();
+        }
+        QQmlEngine::setObjectOwnership(i, QQmlEngine::JavaScriptOwnership);
+    }
+    m_itemForUrl.clear();
+    m_urlForItem.clear();
+    m_lastLoadedUrl = QUrl();
+    m_lastLoadedItem = nullptr;
+
+    Q_EMIT lastLoadedUrlChanged();
+    Q_EMIT lastLoadedItemChanged();
+    Q_EMIT itemsChanged();
+    Q_EMIT urlsChanged();
+}
+
+#include "moc_pagepool.cpp"
diff --git a/src/pagepool.h b/src/pagepool.h
new file mode 100644 (file)
index 0000000..c5d841f
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#pragma once
+
+#include <QObject>
+#include <QPointer>
+#include <QQuickItem>
+
+/**
+ * @brief A Pool of Page objects, pages will be unique per url and the items
+ * will be kept around unless explicitly deleted.
+ *
+ * Instances are C++ owned and can be deleted only manually using deletePage()
+ * Instance are unique per url: if you need 2 different instances for a page
+ * url, you should instantiate them in the traditional way
+ * or use a different PagePool instance.
+ *
+ * @see PagePoolAction
+ */
+class PagePool : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the last url that was loaded with
+     * ::loadPage().
+     *
+     * This is useful when you need to have a "checked" state for buttons or
+     * list items that load the page when clicked.
+     */
+    Q_PROPERTY(QUrl lastLoadedUrl READ lastLoadedUrl NOTIFY lastLoadedUrlChanged)
+
+    /**
+     * @brief This property holds the last item that was loaded with ::loadPage().
+     */
+    Q_PROPERTY(QQuickItem *lastLoadedItem READ lastLoadedItem NOTIFY lastLoadedItemChanged)
+
+    /**
+     * @brief This property holds a list of all items either loaded or managed
+     * by the PagePool.
+     *
+     * @since KDE Frameworks 5.84
+     */
+    Q_PROPERTY(QList<QObject *> items READ items NOTIFY itemsChanged)
+
+    /**
+     * @brief This property holds a list of all page URLs either loaded or
+     * managed by the PagePool.
+     *
+     * @since KDE Frameworks 5.84
+     */
+    Q_PROPERTY(QList<QUrl> urls READ urls NOTIFY urlsChanged)
+
+    /**
+     * @brief This property sets whether to cache pages.
+     *
+     * If @c true (default) the pages will be kept around, will have C++
+     * ownership and only one instance per page will be created.
+     * If @c false the pages will have Javascript ownership (thus deleted on pop
+     * by the page stacks) and each call to ::loadPage() will create a new page
+     * instance. When ::cachePages is false, Components get cached nevertheless.
+     *
+     * default: ``true``
+     */
+    Q_PROPERTY(bool cachePages READ cachePages WRITE setCachePages NOTIFY cachePagesChanged)
+
+public:
+    PagePool(QObject *parent = nullptr);
+    ~PagePool() override;
+
+    QUrl lastLoadedUrl() const;
+    QQuickItem *lastLoadedItem() const;
+    QList<QObject *> items() const;
+    QList<QUrl> urls() const;
+
+    void setCachePages(bool cache);
+    bool cachePages() const;
+
+    /**
+     * @brief This method loads the specified page and returns the page's instance
+     * defined in the QML file.
+     *
+     * @note If ::cachePages is set to true, only one instance will be made per url.
+     * @note If the url is remote (i.e. http), do not rely on the return value but
+     * use the async callback instead.
+     *
+     * @param url full url of the item: it can be a well formed Url, an
+     * absolute path or a relative one to the path of the qml file the
+     * PagePool is instantiated from.
+     * @param callback If we are loading a remote url, we can't have the
+     * item immediately but will be passed as a parameter to the provided
+     * callback. Normally, don't set a callback, use it only in case of
+     * remote urls.
+     * @returns the page instance that will have been created if necessary.
+     * If the url is remote it will return null, as well will return null
+     * if the callback has been provided.
+     */
+    Q_INVOKABLE QQuickItem *loadPage(const QString &url, QJSValue callback = QJSValue());
+
+    /**
+     * @brief This method loads the specified page and returns the page's instance
+     * defined in the QML file with specified properties.
+     *
+     * @note If ::cachePages is set to true, only one instance will be made per url.
+     * @note If the url is remote (i.e. https), do not rely on the return value but
+     * use the async callback instead.
+     *
+     * @param url The full url of the item: it can be a well formed Url, an
+     * absolute path or a relative path to the QML file the
+     * PagePool is instantiated from.
+     * @param callback If we are loading a remote url, we can't have the
+     * item immediately but it will be passed as a parameter to the provided
+     * callback. Normally, don't set a callback, use it only in case of
+     * remote urls.
+     * @param properties This is a QVariant::QVariantMap object that sets the properties of the page.
+     * @param callback A method that is called after the page instance is created.
+     * @returns The page instance that will be created if necessary.
+     * If the url is remote or if the callback was provided, it will return null.
+     */
+    Q_INVOKABLE QQuickItem *loadPageWithProperties(const QString &url, const QVariantMap &properties, QJSValue callback = QJSValue());
+
+    /**
+     * @brief This method returns the url of the page for the given instance.
+     * 
+     * The returned value will be empty if there is no match.
+     * @param item Page representing the QUrl you want.
+     */
+    Q_INVOKABLE QUrl urlForPage(QQuickItem *item) const;
+
+    /**
+     * @brief This method return the the page associated with a given URL, @c nullptr if there is no correspondence
+     * @param url Url representing the Kirigami.Page you want.
+     */
+    Q_INVOKABLE QQuickItem *pageForUrl(const QUrl &url) const;
+
+    /**
+     * @brief This method returns whether the specified page is managed by the PagePool.
+     * @param the page can be either a QQuickItem or an url
+     */
+    Q_INVOKABLE bool contains(const QVariant &page) const;
+
+    /**
+     * @brief This method deletes the specified page if it is managed by the PagePool.
+     * @param page either the url or the instance of the page
+     */
+    Q_INVOKABLE void deletePage(const QVariant &page);
+
+    /**
+     * @brief This method returns the full url from an absolute or relative path.
+     * @param file File path you want to convert to absolute path.
+     */
+    Q_INVOKABLE QUrl resolvedUrl(const QString &file) const;
+
+    /**
+     * @brief This method returns whether the URL is a local resource.
+     *
+     * The given URL is a local resource when it links to a local file, does
+     * not have a set URL scheme, or when the scheme is set to  "qrc".
+     * 
+     * @see QUrl::scheme
+     *
+     * @param url The url you want to check.
+     */
+    Q_INVOKABLE bool isLocalUrl(const QUrl &url);
+
+    /**
+     * @brief This method deletes all pages managed by the PagePool.
+     */
+    Q_INVOKABLE void clear();
+
+Q_SIGNALS:
+    void lastLoadedUrlChanged();
+    void lastLoadedItemChanged();
+    void itemsChanged();
+    void urlsChanged();
+    void cachePagesChanged();
+
+private:
+    QQuickItem *createFromComponent(QQmlComponent *component, const QVariantMap &properties);
+
+    QUrl m_lastLoadedUrl;
+    QPointer<QQuickItem> m_lastLoadedItem;
+    QHash<QUrl, QQuickItem *> m_itemForUrl;
+    QHash<QUrl, QQmlComponent *> m_componentForUrl;
+    QHash<QQuickItem *, QUrl> m_urlForItem;
+
+    bool m_cachePages = true;
+};
diff --git a/src/pagerouter.cpp b/src/pagerouter.cpp
new file mode 100644 (file)
index 0000000..d4662c5
--- /dev/null
@@ -0,0 +1,782 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "pagerouter.h"
+#include "loggingcategory.h"
+#include <QJSEngine>
+#include <QJSValue>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QQmlProperty>
+#include <QQuickWindow>
+#include <QTimer>
+#include <qqmlpropertymap.h>
+
+ParsedRoute *parseRoute(QJSValue value)
+{
+    if (value.isUndefined()) {
+        return new ParsedRoute{};
+    } else if (value.isString()) {
+        return new ParsedRoute{value.toString(), QVariant()};
+    } else {
+        auto map = value.toVariant().value<QVariantMap>();
+        map.remove(QStringLiteral("route"));
+        map.remove(QStringLiteral("data"));
+        return new ParsedRoute{value.property(QStringLiteral("route")).toString(), //
+                               value.property(QStringLiteral("data")).toVariant(),
+                               map,
+                               false};
+    }
+}
+
+QList<ParsedRoute *> parseRoutes(QJSValue values)
+{
+    QList<ParsedRoute *> ret;
+    if (values.isArray()) {
+        const auto valuesList = values.toVariant().toList();
+        for (const auto &route : valuesList) {
+            if (route.toString() != QString()) {
+                ret << new ParsedRoute{route.toString(), QVariant(), QVariantMap(), false, nullptr};
+            } else if (route.canConvert<QVariantMap>()) {
+                auto map = route.value<QVariantMap>();
+                auto copy = map;
+                copy.remove(QStringLiteral("route"));
+                copy.remove(QStringLiteral("data"));
+
+                ret << new ParsedRoute{map.value(QStringLiteral("route")).toString(), map.value(QStringLiteral("data")), copy, false, nullptr};
+            }
+        }
+    } else {
+        ret << parseRoute(values);
+    }
+    return ret;
+}
+
+PageRouter::PageRouter(QQuickItem *parent)
+    : QObject(parent)
+    , m_paramMap(new QQmlPropertyMap)
+    , m_cache()
+    , m_preload()
+{
+    connect(this, &PageRouter::pageStackChanged, [=]() {
+        connect(m_pageStack, &ColumnView::currentIndexChanged, this, &PageRouter::currentIndexChanged);
+    });
+}
+
+QQmlListProperty<PageRoute> PageRouter::routes()
+{
+    return QQmlListProperty<PageRoute>(this, nullptr, appendRoute, routeCount, route, clearRoutes);
+}
+
+void PageRouter::appendRoute(QQmlListProperty<PageRoute> *prop, PageRoute *route)
+{
+    auto router = qobject_cast<PageRouter *>(prop->object);
+    router->m_routes.append(route);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+int PageRouter::routeCount(QQmlListProperty<PageRoute> *prop)
+#else
+qsizetype PageRouter::routeCount(QQmlListProperty<PageRoute> *prop)
+#endif
+{
+    auto router = qobject_cast<PageRouter *>(prop->object);
+    return router->m_routes.length();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+PageRoute *PageRouter::route(QQmlListProperty<PageRoute> *prop, int index)
+#else
+PageRoute *PageRouter::route(QQmlListProperty<PageRoute> *prop, qsizetype index)
+#endif
+{
+    auto router = qobject_cast<PageRouter *>(prop->object);
+    return router->m_routes[index];
+}
+
+void PageRouter::clearRoutes(QQmlListProperty<PageRoute> *prop)
+{
+    auto router = qobject_cast<PageRouter *>(prop->object);
+    router->m_routes.clear();
+}
+
+PageRouter::~PageRouter()
+{
+}
+
+void PageRouter::classBegin()
+{
+}
+
+void PageRouter::componentComplete()
+{
+    if (m_pageStack == nullptr) {
+        qCCritical(KirigamiLog)
+            << "PageRouter should be created with a ColumnView. Not doing so is undefined behaviour, and is likely to result in a crash upon further "
+               "interaction.";
+    } else {
+        Q_EMIT pageStackChanged();
+        m_currentRoutes.clear();
+        push(parseRoute(initialRoute()));
+    }
+}
+
+bool PageRouter::routesContainsKey(const QString &key) const
+{
+    for (auto route : m_routes) {
+        if (route->name() == key) {
+            return true;
+        }
+    }
+    return false;
+}
+
+QQmlComponent *PageRouter::routesValueForKey(const QString &key) const
+{
+    for (auto route : m_routes) {
+        if (route->name() == key) {
+            return route->component();
+        }
+    }
+    return nullptr;
+}
+
+bool PageRouter::routesCacheForKey(const QString &key) const
+{
+    for (auto route : m_routes) {
+        if (route->name() == key) {
+            return route->cache();
+        }
+    }
+    return false;
+}
+
+int PageRouter::routesCostForKey(const QString &key) const
+{
+    for (auto route : m_routes) {
+        if (route->name() == key) {
+            return route->cost();
+        }
+    }
+    return -1;
+}
+
+// It would be nice if this could surgically update the
+// param map instead of doing this brute force approach,
+// but this seems to work well enough, and prematurely
+// optimising stuff is pretty bad if it isn't found as
+// a performance bottleneck.
+void PageRouter::reevaluateParamMapProperties()
+{
+    QStringList currentKeys;
+
+    for (auto item : m_currentRoutes) {
+        for (auto key : item->properties.keys()) {
+            currentKeys << key;
+
+            auto &value = item->properties[key];
+            m_paramMap->insert(key, value);
+        }
+    }
+
+    for (auto key : m_paramMap->keys()) {
+        if (!currentKeys.contains(key)) {
+            m_paramMap->clear(key);
+        }
+    }
+}
+
+void PageRouter::push(ParsedRoute *route)
+{
+    Q_ASSERT(route);
+    if (!routesContainsKey(route->name)) {
+        qCCritical(KirigamiLog) << "Route" << route->name << "not defined";
+        return;
+    }
+    if (routesCacheForKey(route->name)) {
+        auto push = [route, this](ParsedRoute *item) {
+            m_currentRoutes << item;
+
+            for (auto it = route->properties.begin(); it != route->properties.end(); it++) {
+                item->item->setProperty(qUtf8Printable(it.key()), it.value());
+                item->properties[it.key()] = it.value();
+            }
+            reevaluateParamMapProperties();
+
+            m_pageStack->addItem(item->item);
+        };
+        auto item = m_cache.take(qMakePair(route->name, route->hash()));
+        if (item && item->item) {
+            push(item);
+            return;
+        }
+        item = m_preload.take(qMakePair(route->name, route->hash()));
+        if (item && item->item) {
+            push(item);
+            return;
+        }
+    }
+    auto context = qmlContext(this);
+    auto component = routesValueForKey(route->name);
+    auto createAndPush = [component, context, route, this]() {
+        // We use beginCreate and completeCreate to allow
+        // for a PageRouterAttached to find its parent
+        // on construction time.
+        auto item = component->beginCreate(context);
+        if (item == nullptr) {
+            return;
+        }
+        item->setParent(this);
+        auto qqItem = qobject_cast<QQuickItem *>(item);
+        if (!qqItem) {
+            qCCritical(KirigamiLog) << "Route" << route->name << "is not an item! This is undefined behaviour and will likely crash your application.";
+        }
+        for (auto it = route->properties.begin(); it != route->properties.end(); it++) {
+            qqItem->setProperty(qUtf8Printable(it.key()), it.value());
+        }
+        route->setItem(qqItem);
+        route->cache = routesCacheForKey(route->name);
+        m_currentRoutes << route;
+        reevaluateParamMapProperties();
+
+        auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item, true));
+        attached->m_router = this;
+        component->completeCreate();
+        m_pageStack->addItem(qqItem);
+        m_pageStack->setCurrentIndex(m_currentRoutes.length() - 1);
+    };
+
+    if (component->status() == QQmlComponent::Ready) {
+        createAndPush();
+    } else if (component->status() == QQmlComponent::Loading) {
+        connect(component, &QQmlComponent::statusChanged, [=](QQmlComponent::Status status) {
+            // Loading can only go to Ready or Error.
+            if (status != QQmlComponent::Ready) {
+                qCCritical(KirigamiLog) << "Failed to push route:" << component->errors();
+            }
+            createAndPush();
+        });
+    } else {
+        qCCritical(KirigamiLog) << "Failed to push route:" << component->errors();
+    }
+}
+
+QJSValue PageRouter::initialRoute() const
+{
+    return m_initialRoute;
+}
+
+void PageRouter::setInitialRoute(QJSValue value)
+{
+    m_initialRoute = value;
+}
+
+void PageRouter::navigateToRoute(QJSValue route)
+{
+    auto incomingRoutes = parseRoutes(route);
+    QList<ParsedRoute *> resolvedRoutes;
+
+    if (incomingRoutes.length() <= m_currentRoutes.length()) {
+        resolvedRoutes = m_currentRoutes.mid(0, incomingRoutes.length());
+    } else {
+        resolvedRoutes = m_currentRoutes;
+        resolvedRoutes.reserve(incomingRoutes.length() - m_currentRoutes.length());
+    }
+
+    for (int i = 0; i < incomingRoutes.length(); i++) {
+        auto incoming = incomingRoutes.at(i);
+        Q_ASSERT(incoming);
+        if (i >= resolvedRoutes.length()) {
+            resolvedRoutes.append(incoming);
+        } else {
+            auto current = resolvedRoutes.value(i);
+            Q_ASSERT(current);
+            auto props = incoming->properties;
+            if (current->name != incoming->name || current->data != incoming->data) {
+                resolvedRoutes.replace(i, incoming);
+            }
+            resolvedRoutes[i]->properties.clear();
+            for (auto it = props.constBegin(); it != props.constEnd(); it++) {
+                resolvedRoutes[i]->properties.insert(it.key(), it.value());
+            }
+        }
+    }
+
+    for (const auto &route : std::as_const(m_currentRoutes)) {
+        if (!resolvedRoutes.contains(route)) {
+            placeInCache(route);
+        }
+    }
+
+    m_pageStack->clear();
+    m_currentRoutes.clear();
+    for (auto toPush : std::as_const(resolvedRoutes)) {
+        push(toPush);
+    }
+    reevaluateParamMapProperties();
+    Q_EMIT navigationChanged();
+}
+
+void PageRouter::bringToView(QJSValue route)
+{
+    if (route.isNumber()) {
+        auto index = route.toNumber();
+        m_pageStack->setCurrentIndex(index);
+    } else {
+        auto parsed = parseRoute(route);
+        auto index = 0;
+        for (auto currentRoute : std::as_const(m_currentRoutes)) {
+            if (currentRoute->name == parsed->name && currentRoute->data == parsed->data) {
+                m_pageStack->setCurrentIndex(index);
+                return;
+            }
+            index++;
+        }
+        qCWarning(KirigamiLog) << "Route" << parsed->name << "with data" << parsed->data << "is not on the current stack of routes.";
+    }
+}
+
+bool PageRouter::routeActive(QJSValue route)
+{
+    auto parsed = parseRoutes(route);
+    if (parsed.length() > m_currentRoutes.length()) {
+        return false;
+    }
+    for (int i = 0; i < parsed.length(); i++) {
+        if (parsed[i]->name != m_currentRoutes[i]->name) {
+            return false;
+        }
+        if (parsed[i]->data.isValid()) {
+            if (parsed[i]->data != m_currentRoutes[i]->data) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+void PageRouter::pushRoute(QJSValue route)
+{
+    push(parseRoute(route));
+    Q_EMIT navigationChanged();
+}
+
+void PageRouter::popRoute()
+{
+    m_pageStack->pop(m_currentRoutes.last()->item);
+    placeInCache(m_currentRoutes.last());
+    m_currentRoutes.removeLast();
+    reevaluateParamMapProperties();
+    Q_EMIT navigationChanged();
+}
+
+QVariant PageRouter::dataFor(QObject *object)
+{
+    auto pointer = object;
+    auto qqiPointer = qobject_cast<QQuickItem *>(object);
+    QHash<QQuickItem *, ParsedRoute *> routes;
+    for (auto route : std::as_const(m_cache.items)) {
+        routes[route->item] = route;
+    }
+    for (auto route : std::as_const(m_preload.items)) {
+        routes[route->item] = route;
+    }
+    for (auto route : std::as_const(m_currentRoutes)) {
+        routes[route->item] = route;
+    }
+    while (qqiPointer != nullptr) {
+        const auto keys = routes.keys();
+        for (auto item : keys) {
+            if (item == qqiPointer) {
+                return routes[item]->data;
+            }
+        }
+        qqiPointer = qqiPointer->parentItem();
+    }
+    while (pointer != nullptr) {
+        const auto keys = routes.keys();
+        for (auto item : keys) {
+            if (item == pointer) {
+                return routes[item]->data;
+            }
+        }
+        pointer = pointer->parent();
+    }
+    return QVariant();
+}
+
+bool PageRouter::isActive(QObject *object)
+{
+    auto pointer = object;
+    while (pointer != nullptr) {
+        auto index = 0;
+        for (auto route : std::as_const(m_currentRoutes)) {
+            if (route->item == pointer) {
+                return m_pageStack->currentIndex() == index;
+            }
+            index++;
+        }
+        pointer = pointer->parent();
+    }
+    qCWarning(KirigamiLog) << "Object" << object << "not in current routes";
+    return false;
+}
+
+PageRouterAttached *PageRouter::qmlAttachedProperties(QObject *object)
+{
+    auto attached = new PageRouterAttached(object);
+    return attached;
+}
+
+QSet<QObject *> flatParentTree(QObject *object)
+{
+    // See below comment in Climber::climbObjectParents for why this is here.
+    static const QMetaObject *metaObject = QMetaType::metaObjectForType(QMetaType::type("QQuickItem*"));
+    QSet<QObject *> ret;
+    // Use an inline struct type so that climbItemParents and climbObjectParents
+    // can call each other
+    struct Climber {
+        void climbItemParents(QSet<QObject *> &out, QQuickItem *item)
+        {
+            auto parent = item->parentItem();
+            while (parent != nullptr) {
+                out << parent;
+                climbObjectParents(out, parent);
+                parent = parent->parentItem();
+            }
+        }
+        void climbObjectParents(QSet<QObject *> &out, QObject *object)
+        {
+            auto parent = object->parent();
+            while (parent != nullptr) {
+                out << parent;
+                // We manually call metaObject()->inherits() and
+                // use a reinterpret cast because qobject_cast seems
+                // to have stability issues here due to mutable
+                // pointer mechanics.
+                if (parent->metaObject()->inherits(metaObject)) {
+                    climbItemParents(out, reinterpret_cast<QQuickItem *>(parent));
+                }
+                parent = parent->parent();
+            }
+        }
+    };
+    Climber climber;
+    if (qobject_cast<QQuickItem *>(object)) {
+        climber.climbItemParents(ret, qobject_cast<QQuickItem *>(object));
+    }
+    climber.climbObjectParents(ret, object);
+    return ret;
+}
+
+void PageRouter::preload(ParsedRoute *route)
+{
+    for (auto preloaded : std::as_const(m_preload.items)) {
+        if (preloaded->equals(route)) {
+            delete route;
+            return;
+        }
+    }
+    if (!routesContainsKey(route->name)) {
+        qCCritical(KirigamiLog) << "Route" << route->name << "not defined";
+        delete route;
+        return;
+    }
+    auto context = qmlContext(this);
+    auto component = routesValueForKey(route->name);
+    auto createAndCache = [component, context, route, this]() {
+        auto item = component->beginCreate(context);
+        item->setParent(this);
+        auto qqItem = qobject_cast<QQuickItem *>(item);
+        if (!qqItem) {
+            qCCritical(KirigamiLog) << "Route" << route->name << "is not an item! This is undefined behaviour and will likely crash your application.";
+        }
+        for (auto it = route->properties.begin(); it != route->properties.end(); it++) {
+            qqItem->setProperty(qUtf8Printable(it.key()), it.value());
+        }
+        route->setItem(qqItem);
+        route->cache = routesCacheForKey(route->name);
+        auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item, true));
+        attached->m_router = this;
+        component->completeCreate();
+        if (!route->cache) {
+            qCCritical(KirigamiLog) << "Route" << route->name << "is being preloaded despite it not having caching enabled.";
+            delete route;
+            return;
+        }
+        auto string = route->name;
+        auto hash = route->hash();
+        m_preload.insert(qMakePair(string, hash), route, routesCostForKey(route->name));
+    };
+
+    if (component->status() == QQmlComponent::Ready) {
+        createAndCache();
+    } else if (component->status() == QQmlComponent::Loading) {
+        connect(component, &QQmlComponent::statusChanged, [=](QQmlComponent::Status status) {
+            // Loading can only go to Ready or Error.
+            if (status != QQmlComponent::Ready) {
+                qCCritical(KirigamiLog) << "Failed to push route:" << component->errors();
+            }
+            createAndCache();
+        });
+    } else {
+        qCCritical(KirigamiLog) << "Failed to push route:" << component->errors();
+    }
+}
+
+void PageRouter::unpreload(ParsedRoute *route)
+{
+    ParsedRoute *toDelete = nullptr;
+    for (auto preloaded : std::as_const(m_preload.items)) {
+        if (preloaded->equals(route)) {
+            toDelete = preloaded;
+        }
+    }
+    if (toDelete != nullptr) {
+        m_preload.take(qMakePair(toDelete->name, toDelete->hash()));
+        delete toDelete;
+    }
+    delete route;
+}
+
+void PreloadRouteGroup::handleChange()
+{
+    if (!(m_parent->m_router)) {
+        qCCritical(KirigamiLog) << "PreloadRouteGroup does not have a parent PageRouter";
+        return;
+    }
+    auto r = m_parent->m_router;
+    auto parsed = parseRoute(m_route);
+    if (m_when) {
+        r->preload(parsed);
+    } else {
+        r->unpreload(parsed);
+    }
+}
+
+PreloadRouteGroup::~PreloadRouteGroup()
+{
+    if (m_parent->m_router) {
+        m_parent->m_router->unpreload(parseRoute(m_route));
+    }
+}
+
+void PageRouterAttached::findParent()
+{
+    QQuickItem *parent = qobject_cast<QQuickItem *>(this->parent());
+    while (parent != nullptr) {
+        auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(parent, false));
+        if (attached != nullptr && attached->m_router != nullptr) {
+            m_router = attached->m_router;
+            Q_EMIT routerChanged();
+            Q_EMIT dataChanged();
+            Q_EMIT isCurrentChanged();
+            Q_EMIT navigationChanged();
+            break;
+        }
+        parent = parent->parentItem();
+    }
+}
+
+void PageRouterAttached::navigateToRoute(QJSValue route)
+{
+    if (m_router) {
+        m_router->navigateToRoute(route);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return;
+    }
+}
+
+bool PageRouterAttached::routeActive(QJSValue route)
+{
+    if (m_router) {
+        return m_router->routeActive(route);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return false;
+    }
+}
+
+void PageRouterAttached::pushRoute(QJSValue route)
+{
+    if (m_router) {
+        m_router->pushRoute(route);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return;
+    }
+}
+
+void PageRouterAttached::popRoute()
+{
+    if (m_router) {
+        m_router->popRoute();
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return;
+    }
+}
+
+void PageRouterAttached::bringToView(QJSValue route)
+{
+    if (m_router) {
+        m_router->bringToView(route);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return;
+    }
+}
+
+QVariant PageRouterAttached::data() const
+{
+    if (m_router) {
+        return m_router->dataFor(parent());
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return QVariant();
+    }
+}
+
+bool PageRouterAttached::isCurrent() const
+{
+    if (m_router) {
+        return m_router->isActive(parent());
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return false;
+    }
+}
+
+bool PageRouterAttached::watchedRouteActive()
+{
+    if (m_router) {
+        return m_router->routeActive(m_watchedRoute);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+        return false;
+    }
+}
+
+void PageRouterAttached::setWatchedRoute(QJSValue route)
+{
+    m_watchedRoute = route;
+    Q_EMIT watchedRouteChanged();
+}
+
+QJSValue PageRouterAttached::watchedRoute()
+{
+    return m_watchedRoute;
+}
+
+void PageRouterAttached::pushFromHere(QJSValue route)
+{
+    if (m_router) {
+        m_router->pushFromObject(parent(), route);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+    }
+}
+
+void PageRouterAttached::replaceFromHere(QJSValue route)
+{
+    if (m_router) {
+        m_router->pushFromObject(parent(), route, true);
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+    }
+}
+
+void PageRouterAttached::popFromHere()
+{
+    if (m_router) {
+        m_router->pushFromObject(parent(), QJSValue());
+    } else {
+        qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter";
+    }
+}
+
+void PageRouter::placeInCache(ParsedRoute *route)
+{
+    Q_ASSERT(route);
+    if (!route->cache) {
+        delete route;
+        return;
+    }
+    auto string = route->name;
+    auto hash = route->hash();
+    m_cache.insert(qMakePair(string, hash), route, routesCostForKey(route->name));
+}
+
+void PageRouter::pushFromObject(QObject *object, QJSValue inputRoute, bool replace)
+{
+    const auto parsed = parseRoutes(inputRoute);
+    const auto objects = flatParentTree(object);
+
+    for (const auto &obj : objects) {
+        bool popping = false;
+        for (auto route : std::as_const(m_currentRoutes)) {
+            if (popping) {
+                m_currentRoutes.removeAll(route);
+                reevaluateParamMapProperties();
+                placeInCache(route);
+                continue;
+            }
+            if (route->item == obj) {
+                m_pageStack->pop(route->item);
+                if (replace) {
+                    m_currentRoutes.removeAll(route);
+                    reevaluateParamMapProperties();
+                    m_pageStack->removeItem(route->item);
+                }
+                popping = true;
+            }
+        }
+        if (popping) {
+            if (!inputRoute.isUndefined()) {
+                for (auto route : parsed) {
+                    push(route);
+                }
+            }
+            Q_EMIT navigationChanged();
+            return;
+        }
+    }
+    qCWarning(KirigamiLog) << "Object" << object << "not in current routes";
+}
+
+QJSValue PageRouter::currentRoutes() const
+{
+    auto engine = qjsEngine(this);
+    auto ret = engine->newArray(m_currentRoutes.length());
+    for (int i = 0; i < m_currentRoutes.length(); ++i) {
+        auto object = engine->newObject();
+        object.setProperty(QStringLiteral("route"), m_currentRoutes[i]->name);
+        object.setProperty(QStringLiteral("data"), engine->toScriptValue(m_currentRoutes[i]->data));
+        auto keys = m_currentRoutes[i]->properties.keys();
+        for (auto key : keys) {
+            object.setProperty(key, engine->toScriptValue(m_currentRoutes[i]->properties[key]));
+        }
+        ret.setProperty(i, object);
+    }
+    return ret;
+}
+
+PageRouterAttached::PageRouterAttached(QObject *parent)
+    : QObject(parent)
+    , m_preload(new PreloadRouteGroup(this))
+{
+    findParent();
+    auto item = qobject_cast<QQuickItem *>(parent);
+    if (item != nullptr) {
+        connect(item, &QQuickItem::windowChanged, this, [this]() {
+            findParent();
+        });
+        connect(item, &QQuickItem::parentChanged, this, [this]() {
+            findParent();
+        });
+    }
+}
diff --git a/src/pagerouter.h b/src/pagerouter.h
new file mode 100644 (file)
index 0000000..9a9506b
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "columnview.h"
+#include <QCache>
+#include <QQmlPropertyMap>
+#include <QQuickItem>
+#include <QRandomGenerator>
+
+static std::map<quint32, QVariant> s_knownVariants;
+class PageRouter;
+
+class ParsedRoute : public QObject
+{
+    Q_OBJECT
+
+public:
+    QString name;
+    QVariant data;
+    QVariantMap properties;
+    bool cache;
+    QQuickItem *item = nullptr;
+    void itemDestroyed()
+    {
+        item = nullptr;
+    }
+    QQuickItem *setItem(QQuickItem *newItem)
+    {
+        auto ret = item;
+        if (ret != nullptr) {
+            disconnect(ret, &QObject::destroyed, this, &ParsedRoute::itemDestroyed);
+        }
+        item = newItem;
+        if (newItem != nullptr) {
+            connect(newItem, &QObject::destroyed, this, &ParsedRoute::itemDestroyed);
+        }
+        return ret;
+    }
+    explicit ParsedRoute(const QString &name = QString(),
+                         QVariant data = QVariant(),
+                         QVariantMap properties = QVariantMap(),
+                         bool cache = false,
+                         QQuickItem *item = nullptr)
+        : name(name)
+        , data(data)
+        , properties(properties)
+        , cache(cache)
+    {
+        setItem(item);
+    }
+    ~ParsedRoute() override
+    {
+        if (item) {
+            item->deleteLater();
+        }
+    }
+    quint32 hash()
+    {
+        for (auto i = s_knownVariants.begin(); i != s_knownVariants.end(); i++) {
+            if (i->second == data) {
+                return i->first;
+            }
+        }
+        auto number = QRandomGenerator::system()->generate();
+        while (s_knownVariants.count(number) > 0) {
+            number = QRandomGenerator::system()->generate();
+        }
+        s_knownVariants[number] = data;
+        return number;
+    }
+    bool equals(const ParsedRoute *rhs, bool countItem = false)
+    {
+        /* clang-format off */
+        return name == rhs->name
+            && data == rhs->data
+            && (!countItem || item == rhs->item)
+            && cache == rhs->cache;
+        /* clang-format on */
+    }
+};
+
+struct LRU {
+    int size = 10;
+    QList<QPair<QString, quint32>> evictionList;
+    QMap<QPair<QString, quint32>, int> costs;
+    QMap<QPair<QString, quint32>, ParsedRoute *> items;
+
+    ParsedRoute *take(QPair<QString, quint32> key)
+    {
+        auto ret = items.take(key);
+        evictionList.removeAll(key);
+        return ret;
+    }
+    int totalCosts()
+    {
+        int ret = 0;
+        for (auto cost : std::as_const(costs)) {
+            ret += cost;
+        }
+        return ret;
+    }
+    void setSize(int size = 10)
+    {
+        this->size = size;
+        prune();
+    }
+    void prune()
+    {
+        while (size < totalCosts()) {
+            auto key = evictionList.last();
+            auto item = items.take(key);
+            delete item;
+            costs.take(key);
+            evictionList.takeLast();
+        }
+    }
+    void insert(QPair<QString, quint32> key, ParsedRoute *newItem, int cost)
+    {
+        if (items.contains(key)) {
+            auto item = items.take(key);
+            evictionList.removeAll(key);
+            if (item != newItem) {
+                delete item;
+            }
+        }
+        costs[key] = cost;
+        items[key] = newItem;
+        evictionList.prepend(key);
+        prune();
+    }
+};
+
+class PageRouterAttached;
+
+/**
+ * Item holding data about when to preload a route.
+ *
+ * @code{.qml}
+ *  [...]
+ * preload {
+ *   route: "updates-page"
+ *   when: updatesCount > 0
+ * }
+ * @endcode
+ */
+class PreloadRouteGroup : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the route to preload.
+     */
+    Q_PROPERTY(QJSValue route MEMBER m_route WRITE setRoute NOTIFY changed)
+    QJSValue m_route;
+
+    /**
+     * @brief This property sets whether the route should be preloaded.
+     */
+    Q_PROPERTY(bool when MEMBER m_when NOTIFY changed)
+    bool m_when;
+
+    void handleChange();
+    PageRouterAttached *m_parent;
+
+public:
+    ~PreloadRouteGroup() override;
+    void setRoute(QJSValue route)
+    {
+        m_route = route;
+        Q_EMIT changed();
+    }
+    PreloadRouteGroup(QObject *parent)
+        : QObject(parent)
+    {
+        m_parent = qobject_cast<PageRouterAttached *>(parent);
+        Q_ASSERT(m_parent);
+        connect(this, &PreloadRouteGroup::changed, this, &PreloadRouteGroup::handleChange);
+    }
+    Q_SIGNAL void changed();
+};
+
+/**
+ * Item representing a route the PageRouter can navigate to.
+ *
+ * @include pagerouter/PageRoute.qml
+ *
+ * @see kirigami::PageRouter
+ */
+class PageRoute : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the name of this route.
+     *
+     * This name should be unique per PageRoute in a PageRouter.
+     * When two PageRoutes have the same name, the one listed first
+     * in the PageRouter will be used.
+     */
+    Q_PROPERTY(QString name MEMBER m_name READ name)
+
+    /**
+     * @brief This property holds the page component of this route.
+     *
+     * This should be an instance of Component with a Kirigami::Page inside
+     * of it.
+     */
+    Q_PROPERTY(QQmlComponent *component MEMBER m_component READ component)
+
+    /**
+     * @brief This property sets whether pages generated by this route should be cached.
+     *
+     * This should be an instance of Component with a Kirigami::Page inside
+     * of it.
+     *
+     * This will not work:
+     *
+     * @include pagerouter/PageRouterCachePagesDont.qml
+     *
+     * This will work:
+     *
+     * @include pagerouter/PageRouterCachePagesDo.qml
+     *
+     */
+    Q_PROPERTY(bool cache MEMBER m_cache READ cache)
+
+    /**
+     * @brief This property specifies how expensive this route is on memory.
+     *
+     * This affects caching, as the sum of costs of routes in the cache
+     * can never exceed the cache's cap.
+     */
+    Q_PROPERTY(int cost MEMBER m_cost)
+
+    Q_CLASSINFO("DefaultProperty", "component")
+
+Q_SIGNALS:
+    void preloadDataChanged();
+    void preloadChanged();
+
+private:
+    QString m_name;
+    QQmlComponent *m_component = nullptr;
+    bool m_cache = false;
+    int m_cost = 1;
+
+public:
+    QQmlComponent *component()
+    {
+        return m_component;
+    };
+    QString name()
+    {
+        return m_name;
+    };
+    bool cache()
+    {
+        return m_cache;
+    };
+    int cost()
+    {
+        return m_cost;
+    };
+};
+
+/**
+ * An item managing pages and data of a ColumnView using named routes.
+ *
+ * <br> <br>
+ *
+ * ## Using a PageRouter
+ *
+ * Applications typically manage their contents via elements called "pages" or "screens."
+ * In Kirigami, these are called @link kirigami::Page  Pages @endlink and are
+ * arranged in @link PageRoute routes @endlink using a PageRouter to manage them. The PageRouter
+ * manages a stack of @link kirigami::Page Pages @endlink created from a pool of potential
+ * @link PageRoute PageRoutes @endlink.
+ *
+ * Unlike most traditional stacks, a PageRouter provides functions for random access to its pages
+ * with navigateToRoute and routeActive.
+ *
+ * When your user interface fits the stack paradigm and is likely to use random access navigation,
+ * using the PageRouter is appropriate. For simpler navigation, it is more appropriate to avoid
+ * the overhead of a PageRouter by using a @link kirigami::PageRow PageRow  @endlink
+ * instead.
+ *
+ * <br> <br>
+ *
+ * ## Navigation Model
+ *
+ * A PageRouter draws from a pool of @link PageRoute PageRoutes @endlink in order to construct
+ * its stack.
+ *
+ * @image html PageRouterModel.svg width=50%
+ *
+ * <br> <br>
+ *
+ * You can push pages onto this stack...
+ *
+ * @image html PageRouterPush.svg width=50%
+ *
+ * ...or pop them off...
+ *
+ * @image html PageRouterPop.svg width=50%
+ *
+ * ...or navigate to an arbitrary collection of pages.
+ *
+ * @image html PageRouterNavigate.svg width=50%
+ *
+ * <br> <br>
+ *
+ * Components are able to query the PageRouter about the currently active routes
+ * on the stack. This is useful for e.g. a card indicating that the page it takes
+ * the user to is currently active.
+ *
+ * <br> <br>
+ *
+ * ## Example
+ *
+ * @include pagerouter/PageRouter.qml
+ *
+ * @see kirigami::PageRouterAttached
+ * @see kirigami::PageRoute
+ */
+class PageRouter : public QObject, public QQmlParserStatus
+{
+    Q_OBJECT
+    Q_INTERFACES(QQmlParserStatus)
+
+    /**
+     * @brief This property holds a list of named routes a PageRouter can navigate to.
+     *
+     * @include pagerouter/PageRouterRoutes.qml
+     */
+    Q_PROPERTY(QQmlListProperty<PageRoute> routes READ routes)
+
+    Q_CLASSINFO("DefaultProperty", "routes")
+
+    /**
+     * @brief This property sets the initial route.
+     *
+     * `initialRoute` is the page that the PageRouter will push upon
+     * creation. Changing it after creation will cause the PageRouter to reset
+     * its state. Not providing an `initialRoute` will result in undefined
+     * behavior.
+     *
+     * @see kirigami::PageRoute::name
+     * @include pagerouter/PageRouterInitialRoute.qml
+     */
+    Q_PROPERTY(QJSValue initialRoute READ initialRoute WRITE setInitialRoute NOTIFY initialRouteChanged)
+
+    /**
+     * @brief This property holds the ColumnView being puppeted by the PageRouter.
+     *
+     * All PageRouters should be created with a ColumnView, and creating one without
+     * a ColumnView is undefined behaviour.
+     *
+     * @warning You should **not** directly interact with a ColumnView being puppeted
+     * by a PageRouter. Instead, use a PageRouter's functions to manipulate the
+     * ColumnView.
+     *
+     * @include pagerouter/PageRouterColumnView.qml
+     */
+    Q_PROPERTY(ColumnView *pageStack MEMBER m_pageStack NOTIFY pageStackChanged)
+
+    /**
+     * @brief This property holds how large the cache can be.
+     *
+     * The combined costs of cached routes will never exceed the cache capacity.
+     */
+    Q_PROPERTY(int cacheCapacity READ cacheCapacity WRITE setCacheCapacity)
+
+    /**
+     * @brief This property holds how large the preloaded pool can be.
+     *
+     * The combined costs of preloaded routes will never exceed the pool capacity.
+     */
+    Q_PROPERTY(int preloadedPoolCapacity READ preloadedPoolCapacity WRITE setPreloadedPoolCapacity)
+
+    /**
+     * Exposes the data of all pages on the stack, preferring pages on the top
+     * (e.g. most recently pushed) to pages pushed on the bottom (least recently
+     * pushed).
+     */
+    Q_PROPERTY(QQmlPropertyMap *params READ params CONSTANT)
+
+private:
+    /**
+     * The map exposing to QML all the params of the stack. This is a
+     * QQmlPropertyMap to allow binding to param values. This *does* lack
+     * the ability to drop items, but the amount of all params in an app
+     * is overwhelmingly likely to be fixed, not dynamic.
+     */
+    QSharedPointer<QQmlPropertyMap> m_paramMap;
+
+    /**
+     * @brief This method reevaluates the properties of the param map by going through all of the
+     * routes on the stack to determine the topmost value for every parameter.
+     *
+     * Should be called for every time a route is pushed, popped, or modified.
+     */
+    void reevaluateParamMapProperties();
+
+    /**
+     * @brief This property holds a list of routes the PageRouter is aware of.
+     *
+     * Generally, this should not be mutated from C++, only read.
+     */
+    QList<PageRoute *> m_routes;
+
+    /**
+     * @brief The PageRouter being puppeted.
+     *
+     * m_pageRow is the ColumnView this PageRouter puppets.
+     */
+    ColumnView *m_pageStack = nullptr;
+
+    /**
+     * @brief The route that the PageRouter will load on completion.
+     *
+     * m_initialRoute is the raw QJSValue from QML that will be
+     * parsed into a ParsedRoute struct on construction.
+     * Generally, this should not be mutated from C++, only read.
+     */
+    QJSValue m_initialRoute;
+
+    /**
+     * @brief The current routes pushed on the PageRow.
+     *
+     * Generally, the state of m_pageRow and m_currentRoutes
+     * should be kept in sync. Undesirable behaviour will result
+     * from desynchronisation of the two.
+     */
+    QList<ParsedRoute *> m_currentRoutes;
+
+    /**
+     * @brief Cached routes.
+     *
+     * An LRU cache of ParsedRoutes with items that were previously on the stack.
+     */
+    LRU m_cache;
+
+    /** @brief Preloaded routes.
+     *
+     * A LRU cache of ParsedRoutes with items that may be on the stack in the future,
+     * but were not on the stack before.
+     */
+    LRU m_preload;
+
+    /**
+     * @brief Helper function to push a route.
+     *
+     * This function has the shared logic between
+     * navigateToRoute and pushRoute.
+     */
+    void push(ParsedRoute *route);
+
+    /**
+     * @brief This method returns whether m_routes has a key.
+     *
+     * This function abstracts the QJSValue.
+     */
+    bool routesContainsKey(const QString &key) const;
+
+    /**
+     * @brief This method returns the component of the specified key for m_routes.
+     *
+     * The return value will be a @c nullptr if @p key does not exist in
+     * m_routes.
+     */
+    QQmlComponent *routesValueForKey(const QString &key) const;
+
+    /**
+     * @brief This method returns the cache status of a key for m_routes.
+     *
+     * The return value will be @c false if @p key does not exist in
+     * m_routes.
+     */
+    bool routesCacheForKey(const QString &key) const;
+
+    /**
+     * @brief This method return the cost of a key for m_routes.
+     *
+     * The return value will be -1 if @p key does not exist in
+     * m_routes.
+     */
+    int routesCostForKey(const QString &key) const;
+
+    void preload(ParsedRoute *route);
+    void unpreload(ParsedRoute *route);
+
+    void placeInCache(ParsedRoute *route);
+
+    static void appendRoute(QQmlListProperty<PageRoute> *list, PageRoute *);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    static int routeCount(QQmlListProperty<PageRoute> *list);
+    static PageRoute *route(QQmlListProperty<PageRoute> *list, int);
+#else
+    static qsizetype routeCount(QQmlListProperty<PageRoute> *list);
+    static PageRoute *route(QQmlListProperty<PageRoute> *list, qsizetype);
+#endif
+    static void clearRoutes(QQmlListProperty<PageRoute> *list);
+
+    QVariant dataFor(QObject *object);
+    bool isActive(QObject *object);
+    void pushFromObject(QObject *object, QJSValue route, bool replace = false);
+
+    friend class PageRouterAttached;
+    friend class PreloadRouteGroup;
+    friend class ParsedRoute;
+
+protected:
+    void classBegin() override;
+    void componentComplete() override;
+
+public:
+    PageRouter(QQuickItem *parent = nullptr);
+    ~PageRouter() override;
+
+    QQmlListProperty<PageRoute> routes();
+
+    QQmlPropertyMap *params()
+    {
+        return m_paramMap.data();
+    }
+
+    QJSValue initialRoute() const;
+    void setInitialRoute(QJSValue initialRoute);
+
+    int cacheCapacity() const
+    {
+        return m_cache.size;
+    };
+    void setCacheCapacity(int size)
+    {
+        m_cache.setSize(size);
+    };
+
+    int preloadedPoolCapacity() const
+    {
+        return m_preload.size;
+    };
+    void setPreloadedPoolCapacity(int size)
+    {
+        m_preload.setSize(size);
+    };
+
+    /**
+     * @brief This method makes the PageRouter navigate to the specified route.
+     *
+     * Calling `navigateToRoute` causes the PageRouter to replace currently
+     * active pages with the new route.
+     *
+     * @param route The given route for the PageRouter to navigate to.
+     * A route is an array of variants or a single item. A string item will be interpreted
+     * as a page without associated data. An object item will be interpreted
+     * as follows:
+     * @code{.js}
+     * {
+     *     "route": "/home" // The named page of the route.
+     *     "data": QtObject {} // The data to pass to the page.
+     * }
+     * @endcode
+     * Navigating to a route not defined in a PageRouter's routes is undefined
+     * behavior.
+     *
+     * @code{.qml}
+     * Button {
+     *     text: "Login"
+     *     onClicked: {
+     *         Kirigami.PageRouter.navigateToRoute(["/home", "/login"])
+     *     }
+     * }
+     * @endcode
+     */
+    Q_INVOKABLE void navigateToRoute(QJSValue route);
+
+    /**
+     * @brief This method Checs whether the current route is on the stack.
+     *
+     * `routeActive` will return @c true if the given route
+     * is on the stack.
+     *
+     * @param route The given route to check for.
+     *
+     * `routeActive` returns @c true for partial routes like
+     * the following:
+     *
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/login", "/google"])
+     * Kirigami.PageRouter.routeActive(["/home", "/login"]) // returns true
+     * @endcode
+     *
+     * This only works from the root page, e.g. the following will return false:
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/login", "/google"])
+     * Kirigami.PageRouter.routeActive(["/login", "/google"]) // returns false
+     * @endcode
+     */
+    Q_INVOKABLE bool routeActive(QJSValue route);
+
+    /**
+     * @brief This method appends a route to the currently navigated route.
+     *
+     * Calling `pushRoute` will append the given @p route to the currently navigated
+     * routes. See navigateToRoute() if you want to replace the items currently on
+     * the PageRouter.
+     *
+     * @param route The given route to push.
+     *
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/login"])
+     * // The PageRouter is navigated to /home/login
+     * Kirigami.PageRouter.pushRoute("/google")
+     * // The PageRouter is navigated to /home/login/google
+     * @endcode
+     */
+    Q_INVOKABLE void pushRoute(QJSValue route);
+
+    /**
+     * @brief This method pops the last page on the router.
+     *
+     * Calling `popRoute` will result in the last page on the router getting popped.
+     * You should not call this function when there is only one page on the router.
+     *
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/login"])
+     * // The PageRouter is navigated to /home/login
+     * Kirigami.PageRouter.popRoute()
+     * // The PageRouter is navigated to /home
+     * @endcode
+     */
+    Q_INVOKABLE void popRoute();
+
+    /**
+     * @brief This method shifts keyboard focus and view to a given index on the PageRouter's stack.
+     *
+     * @param view The view to bring to focus. If this is an integer index, the PageRouter will
+     * navigate to the given index. If it's a route specifier, the PageRouter will navigate
+     * to the first route matching it.
+     *
+     * Navigating to route by index:
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/browse", "/apps", "/login"])
+     * Kirigami.PageRouter.bringToView(1)
+     * @endcode
+     *
+     * Navigating to route by name:
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute(["/home", "/browse", "/apps", "/login"])
+     * Kirigami.PageRouter.bringToView("/browse")
+     * @endcode
+     *
+     * Navigating to route by data:
+     * @code{.js}
+     * Kirigami.PageRouter.navigateToRoute([{"route": "/page", "data": "red"},
+     *                                      {"route": "/page", "data": "blue"},
+     *                                      {"route": "/page", "data": "green"},
+     *                                      {"route": "/page", "data": "yellow"}])
+     * Kirigami.PageRouter.bringToView({"route": "/page", "data": "blue"})
+     * @endcode
+     */
+    Q_INVOKABLE void bringToView(QJSValue route);
+
+    /**
+     * @brief This method returns a QJSValue corresponding to the current pages on the stack.
+     *
+     * The returned value is in the same form as the input to navigateToRoute.
+     */
+    Q_INVOKABLE QJSValue currentRoutes() const;
+
+    static PageRouterAttached *qmlAttachedProperties(QObject *object);
+
+Q_SIGNALS:
+    void routesChanged();
+    void initialRouteChanged();
+    void pageStackChanged();
+    void currentIndexChanged();
+    void navigationChanged();
+};
+
+/**
+ * Attached object allowing children of a PageRouter to access its functions
+ * without requiring the children to have the parent PageRouter's id.
+ *
+ * @see kirigami::PageRouter
+ */
+class PageRouterAttached : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(PageRouter *router READ router WRITE setRouter NOTIFY routerChanged)
+    /**
+     * @brief This property holds data for the page this item belongs to.
+     * @note Accessing this property outside of a PageRouter will result in undefined behaviour.
+     */
+    Q_PROPERTY(QVariant data READ data MEMBER m_data NOTIFY dataChanged)
+
+    /**
+     * @brief This property sets whether the page this item belongs to is the current index of the ColumnView.
+     * @note Accessing this property outside of a PageRouter will result in undefined behaviour.
+     */
+    Q_PROPERTY(bool isCurrent READ isCurrent NOTIFY isCurrentChanged)
+
+    /**
+     * @brief This property holds which route this PageRouterAttached should watch for.
+     * @include pagerouter/PageRouterWatchedRoute.qml
+     */
+    Q_PROPERTY(QJSValue watchedRoute READ watchedRoute WRITE setWatchedRoute NOTIFY watchedRouteChanged)
+
+    /**
+     * @brief This property holds route preloading settings.
+     */
+    Q_PROPERTY(PreloadRouteGroup *preload READ preload)
+
+    /**
+     * Ths property sets whether the watchedRoute is currently active.
+     */
+    Q_PROPERTY(bool watchedRouteActive READ watchedRouteActive NOTIFY navigationChanged)
+
+private:
+    explicit PageRouterAttached(QObject *parent = nullptr);
+
+    QPointer<PageRouter> m_router;
+    PreloadRouteGroup *m_preload;
+    QVariant m_data;
+    QJSValue m_watchedRoute;
+
+    void findParent();
+
+    friend class PageRouter;
+    friend class PreloadRouteGroup;
+    friend class ParsedRoute;
+
+public:
+    PreloadRouteGroup *preload() const
+    {
+        return m_preload;
+    };
+    PageRouter *router() const
+    {
+        return m_router;
+    };
+    void setRouter(PageRouter *router)
+    {
+        m_router = router;
+        Q_EMIT routerChanged();
+    }
+    QVariant data() const;
+    bool isCurrent() const;
+    /// @see PageRouter::navigateToRoute()
+    Q_INVOKABLE void navigateToRoute(QJSValue route);
+    /// @see PageRouter::routeActive()
+    Q_INVOKABLE bool routeActive(QJSValue route);
+    /// @see PageRouter::pushRoute()
+    Q_INVOKABLE void pushRoute(QJSValue route);
+    /// @see PageRouter::popRoute()
+    Q_INVOKABLE void popRoute();
+    // @see PageRouter::bringToView()
+    Q_INVOKABLE void bringToView(QJSValue route);
+    /**
+     * @brief Push a route from this route on the stack.
+     *
+     * Replace the routes after the route this is invoked on
+     * with the provided @p route.
+     *
+     * For example, if you invoke this method on the second route
+     * in the PageRouter's stack, routes after the second
+     * route will be replaced with the provided routes.
+     */
+    Q_INVOKABLE void pushFromHere(QJSValue route);
+    /**
+     * @brief Pop routes after this route on the stack.
+     *
+     * Pop the routes after the route this is invoked on with
+     * the provided @p route.
+     *
+     * For example, if you invoke this method on the second route
+     * in the PageRouter's stack, routes after the second route
+     * will be removed from the stack.
+     */
+    Q_INVOKABLE void popFromHere();
+    /**
+     * @brief Replaces this route with the given routes on the stack.
+     *
+     * Behaves like pushFromHere, except the current route is also
+     * popped.
+     */
+    Q_INVOKABLE void replaceFromHere(QJSValue route);
+    bool watchedRouteActive();
+    void setWatchedRoute(QJSValue route);
+    QJSValue watchedRoute();
+
+Q_SIGNALS:
+    void routerChanged();
+    void dataChanged();
+    void isCurrentChanged();
+    void navigationChanged();
+    void watchedRouteChanged();
+};
+
+QML_DECLARE_TYPEINFO(PageRouter, QML_HAS_ATTACHED_PROPERTIES)
diff --git a/src/plugins.qmltypes b/src/plugins.qmltypes
new file mode 100644 (file)
index 0000000..bbfca8c
--- /dev/null
@@ -0,0 +1,2158 @@
+import QtQuick.tooling 1.2
+
+// This file describes the plugin-supplied types contained in the library.
+// It is used for QML tooling purposes only.
+//
+// This file was auto-generated by:
+// 'qmlplugindump -noinstantiate -notrelocatable org.kde.kirigami 2.0 /opt/kde5/lib/x86_64-linux-gnu/qml/'
+
+Module {
+    dependencies: [
+        "QtGraphicalEffects 1.12",
+        "QtQml 2.7",
+        "QtQml.Models 2.2",
+        "QtQml.WorkerScript 2.15",
+        "QtQuick 2.9",
+        "QtQuick.Controls 2.5",
+        "QtQuick.Controls.Material 2.15",
+        "QtQuick.Controls.Styles 1.4",
+        "QtQuick.Controls.Styles.Plasma 2.0",
+        "QtQuick.Layouts 1.4",
+        "QtQuick.Templates 2.4",
+        "QtQuick.Window 2.6",
+        "org.kde.breeze 1.0",
+        "org.kde.kconfig 1.0",
+        "org.kde.kquickcontrolsaddons 2.0",
+        "org.kde.plasma.components 3.0",
+        "org.kde.plasma.core 2.0",
+        "org.kde.plasma.extras 2.0",
+        "org.kde.quickcharts 1.0",
+        "org.kde.quickcharts.controls 1.0"
+    ]
+    Component {
+        name: "ApplicationHeaderStyle"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/ApplicationHeaderStyle 2.0"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Status"
+            values: {
+                "Auto": 0,
+                "Breadcrumb": 1,
+                "Titles": 2,
+                "TabBar": 3,
+                "ToolBar": 4,
+                "None": 5
+            }
+        }
+        Enum {
+            name: "NavigationButton"
+            values: {
+                "NoNavigationButtons": 0,
+                "ShowBackButton": 1,
+                "ShowForwardButton": 2
+            }
+        }
+    }
+    Component {
+        name: "AvatarGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami.private/AvatarGroup 2.14"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "main"; type: "QVariant" }
+        Property { name: "secondary"; type: "QVariant" }
+        Signal { name: "mainActionChanged" }
+        Signal { name: "secondaryActionChanged" }
+    }
+    Component {
+        name: "BorderGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/BorderGroup 2.12"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "width"; type: "double" }
+        Property { name: "color"; type: "QColor" }
+        Signal { name: "changed" }
+    }
+    Component {
+        name: "ColorUtils"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/ColorUtils 2.12"]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Brightness"
+            values: {
+                "Dark": 0,
+                "Light": 1
+            }
+        }
+        Method {
+            name: "brightnessForColor"
+            type: "ColorUtils::Brightness"
+            Parameter { name: "color"; type: "QColor" }
+        }
+        Method {
+            name: "grayForColor"
+            type: "double"
+            Parameter { name: "color"; type: "QColor" }
+        }
+        Method {
+            name: "alphaBlend"
+            type: "QColor"
+            Parameter { name: "foreground"; type: "QColor" }
+            Parameter { name: "background"; type: "QColor" }
+        }
+        Method {
+            name: "linearInterpolation"
+            type: "QColor"
+            Parameter { name: "one"; type: "QColor" }
+            Parameter { name: "two"; type: "QColor" }
+            Parameter { name: "balance"; type: "double" }
+        }
+        Method {
+            name: "adjustColor"
+            type: "QColor"
+            Parameter { name: "color"; type: "QColor" }
+            Parameter { name: "adjustments"; type: "QJSValue" }
+        }
+        Method {
+            name: "scaleColor"
+            type: "QColor"
+            Parameter { name: "color"; type: "QColor" }
+            Parameter { name: "adjustments"; type: "QJSValue" }
+        }
+        Method {
+            name: "tintWithAlpha"
+            type: "QColor"
+            Parameter { name: "targetColor"; type: "QColor" }
+            Parameter { name: "tintColor"; type: "QColor" }
+            Parameter { name: "alpha"; type: "double" }
+        }
+        Method {
+            name: "chroma"
+            type: "double"
+            Parameter { name: "color"; type: "QColor" }
+        }
+    }
+    Component {
+        name: "ColumnView"
+        defaultProperty: "contentData"
+        prototype: "QQuickItem"
+        exports: ["org.kde.kirigami/ColumnView 2.7"]
+        exportMetaObjectRevisions: [0]
+        attachedType: "ColumnViewAttached"
+        Enum {
+            name: "ColumnResizeMode"
+            values: {
+                "FixedColumns": 0,
+                "DynamicColumns": 1,
+                "SingleColumn": 2
+            }
+        }
+        Property { name: "columnResizeMode"; type: "ColumnResizeMode" }
+        Property { name: "columnWidth"; type: "double" }
+        Property { name: "count"; type: "int"; isReadonly: true }
+        Property { name: "currentIndex"; type: "int" }
+        Property { name: "currentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "contentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "contentX"; type: "double" }
+        Property { name: "contentWidth"; type: "double"; isReadonly: true }
+        Property { name: "topPadding"; type: "double" }
+        Property { name: "bottomPadding"; type: "double" }
+        Property { name: "scrollDuration"; type: "int" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "visibleItems"; type: "QList<QObject*>"; isReadonly: true }
+        Property { name: "firstVisibleItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "lastVisibleItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "dragging"; type: "bool"; isReadonly: true }
+        Property { name: "moving"; type: "bool"; isReadonly: true }
+        Property { name: "interactive"; type: "bool" }
+        Property { name: "acceptsMouse"; type: "bool" }
+        Property { name: "contentChildren"; type: "QQuickItem"; isList: true; isReadonly: true }
+        Property { name: "contentData"; type: "QObject"; isList: true; isReadonly: true }
+        Signal {
+            name: "itemInserted"
+            Parameter { name: "position"; type: "int" }
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Signal {
+            name: "itemRemoved"
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "addItem"
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "insertItem"
+            Parameter { name: "pos"; type: "int" }
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "replaceItem"
+            Parameter { name: "pos"; type: "int" }
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "moveItem"
+            Parameter { name: "from"; type: "int" }
+            Parameter { name: "to"; type: "int" }
+        }
+        Method {
+            name: "removeItem"
+            type: "QQuickItem*"
+            Parameter { name: "item"; type: "QVariant" }
+        }
+        Method {
+            name: "pop"
+            type: "QQuickItem*"
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method { name: "clear" }
+        Method {
+            name: "containsItem"
+            type: "bool"
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "itemAt"
+            type: "QQuickItem*"
+            Parameter { name: "x"; type: "double" }
+            Parameter { name: "y"; type: "double" }
+        }
+    }
+    Component {
+        name: "ColumnViewAttached"
+        prototype: "QObject"
+        Property { name: "index"; type: "int" }
+        Property { name: "fillWidth"; type: "bool" }
+        Property { name: "reservedSpace"; type: "double" }
+        Property { name: "preventStealing"; type: "bool" }
+        Property { name: "pinned"; type: "bool" }
+        Property { name: "view"; type: "ColumnView"; isReadonly: true; isPointer: true }
+        Property { name: "inViewport"; type: "bool"; isReadonly: true }
+        Signal {
+            name: "scrollIntention"
+            Parameter { name: "event"; type: "ScrollIntentionEvent"; isPointer: true }
+        }
+    }
+    Component {
+        name: "CopyHelperPrivate"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami.private/CopyHelperPrivate 2.6"]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0]
+        Method {
+            name: "copyTextToClipboard"
+            Parameter { name: "text"; type: "string" }
+        }
+    }
+    Component {
+        name: "CornersGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/CornersGroup 2.12"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "topLeftRadius"; type: "double" }
+        Property { name: "topRightRadius"; type: "double" }
+        Property { name: "bottomLeftRadius"; type: "double" }
+        Property { name: "bottomRightRadius"; type: "double" }
+        Signal { name: "changed" }
+    }
+    Component {
+        name: "DelegateRecycler"
+        defaultProperty: "data"
+        prototype: "QQuickItem"
+        exports: ["org.kde.kirigami/DelegateRecycler 2.4"]
+        exportMetaObjectRevisions: [0]
+        attachedType: "DelegateRecyclerAttached"
+        Property { name: "sourceComponent"; type: "QQmlComponent"; isPointer: true }
+    }
+    Component {
+        name: "DelegateRecyclerAttached"
+        prototype: "QObject"
+        Signal { name: "pooled" }
+        Signal { name: "reused" }
+    }
+    Component {
+        name: "DisplayHint"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/DisplayHint 2.14"]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Hint"
+            values: {
+                "NoPreference": 0,
+                "IconOnly": 1,
+                "KeepVisible": 2,
+                "AlwaysHide": 4,
+                "HideChildIndicator": 8
+            }
+        }
+        Enum {
+            name: "DisplayHints"
+            values: {
+                "NoPreference": 0,
+                "IconOnly": 1,
+                "KeepVisible": 2,
+                "AlwaysHide": 4,
+                "HideChildIndicator": 8
+            }
+        }
+        Method {
+            name: "displayHintSet"
+            type: "bool"
+            Parameter { name: "values"; type: "DisplayHints" }
+            Parameter { name: "hint"; type: "Hint" }
+        }
+        Method {
+            name: "displayHintSet"
+            type: "bool"
+            Parameter { name: "object"; type: "QObject"; isPointer: true }
+            Parameter { name: "hint"; type: "Hint" }
+        }
+    }
+    Component {
+        name: "FormLayoutAttached"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/FormData 2.3"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "label"; type: "string" }
+        Property { name: "labelAlignment"; type: "int" }
+        Property { name: "isSection"; type: "bool" }
+        Property { name: "checkable"; type: "bool" }
+        Property { name: "checked"; type: "bool" }
+        Property { name: "enabled"; type: "bool" }
+        Property { name: "buddyFor"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        name: "Icon"
+        defaultProperty: "data"
+        prototype: "QQuickItem"
+        exports: ["org.kde.kirigami/Icon 2.0"]
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Status"
+            values: {
+                "Null": 0,
+                "Ready": 1,
+                "Loading": 2,
+                "Error": 3
+            }
+        }
+        Property { name: "source"; type: "QVariant" }
+        Property { name: "fallback"; type: "string" }
+        Property { name: "placeholder"; type: "string" }
+        Property { name: "active"; type: "bool" }
+        Property { name: "valid"; type: "bool"; isReadonly: true }
+        Property { name: "selected"; type: "bool" }
+        Property { name: "isMask"; type: "bool" }
+        Property { name: "color"; type: "QColor" }
+        Property { name: "status"; type: "Icon::Status"; isReadonly: true }
+        Property { name: "paintedWidth"; type: "double"; isReadonly: true }
+        Property { name: "paintedHeight"; type: "double"; isReadonly: true }
+        Signal {
+            name: "fallbackChanged"
+            Parameter { name: "fallback"; type: "string" }
+        }
+        Signal {
+            name: "placeholderChanged"
+            Parameter { name: "placeholder"; type: "string" }
+        }
+        Signal { name: "paintedAreaChanged" }
+    }
+    Component {
+        name: "ImageColors"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/ImageColors 2.13"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "source"; type: "QVariant" }
+        Property { name: "palette"; type: "QVariantList"; isReadonly: true }
+        Property { name: "paletteBrightness"; type: "ColorUtils::Brightness"; isReadonly: true }
+        Property { name: "average"; type: "QColor"; isReadonly: true }
+        Property { name: "dominant"; type: "QColor"; isReadonly: true }
+        Property { name: "dominantContrast"; type: "QColor"; isReadonly: true }
+        Property { name: "highlight"; type: "QColor"; isReadonly: true }
+        Property { name: "foreground"; type: "QColor"; isReadonly: true }
+        Property { name: "background"; type: "QColor"; isReadonly: true }
+        Property { name: "closestToWhite"; type: "QColor"; isReadonly: true }
+        Property { name: "closestToBlack"; type: "QColor"; isReadonly: true }
+        Property { name: "fallbackPalette"; type: "QVariantList" }
+        Property { name: "fallbackPaletteBrightness"; type: "ColorUtils::Brightness" }
+        Property { name: "fallbackAverage"; type: "QColor" }
+        Property { name: "fallbackDominant"; type: "QColor" }
+        Property { name: "fallbackDominantContrasting"; type: "QColor" }
+        Property { name: "fallbackHighlight"; type: "QColor" }
+        Property { name: "fallbackForeground"; type: "QColor" }
+        Property { name: "fallbackBackground"; type: "QColor" }
+        Method { name: "update" }
+    }
+    Component {
+        name: "Kirigami::BasicThemeDefinition"
+        prototype: "QObject"
+        exports: [
+            "org.kde.kirigami/BasicThemeDefinition 2.16",
+            "org.kde.kirigami/Theme 2.0"
+        ]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0, 0]
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "disabledTextColor"; type: "QColor" }
+        Property { name: "highlightColor"; type: "QColor" }
+        Property { name: "highlightedTextColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "focusColor"; type: "QColor" }
+        Property { name: "hoverColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "linkColor"; type: "QColor" }
+        Property { name: "linkBackgroundColor"; type: "QColor" }
+        Property { name: "visitedLinkColor"; type: "QColor" }
+        Property { name: "visitedLinkBackgroundColor"; type: "QColor" }
+        Property { name: "negativeTextColor"; type: "QColor" }
+        Property { name: "negativeBackgroundColor"; type: "QColor" }
+        Property { name: "neutralTextColor"; type: "QColor" }
+        Property { name: "neutralBackgroundColor"; type: "QColor" }
+        Property { name: "positiveTextColor"; type: "QColor" }
+        Property { name: "positiveBackgroundColor"; type: "QColor" }
+        Property { name: "buttonTextColor"; type: "QColor" }
+        Property { name: "buttonBackgroundColor"; type: "QColor" }
+        Property { name: "buttonAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "buttonHoverColor"; type: "QColor" }
+        Property { name: "buttonFocusColor"; type: "QColor" }
+        Property { name: "viewTextColor"; type: "QColor" }
+        Property { name: "viewBackgroundColor"; type: "QColor" }
+        Property { name: "viewAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "viewHoverColor"; type: "QColor" }
+        Property { name: "viewFocusColor"; type: "QColor" }
+        Property { name: "selectionTextColor"; type: "QColor" }
+        Property { name: "selectionBackgroundColor"; type: "QColor" }
+        Property { name: "selectionAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "selectionHoverColor"; type: "QColor" }
+        Property { name: "selectionFocusColor"; type: "QColor" }
+        Property { name: "tooltipTextColor"; type: "QColor" }
+        Property { name: "tooltipBackgroundColor"; type: "QColor" }
+        Property { name: "tooltipAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "tooltipHoverColor"; type: "QColor" }
+        Property { name: "tooltipFocusColor"; type: "QColor" }
+        Property { name: "complementaryTextColor"; type: "QColor" }
+        Property { name: "complementaryBackgroundColor"; type: "QColor" }
+        Property { name: "complementaryAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "complementaryHoverColor"; type: "QColor" }
+        Property { name: "complementaryFocusColor"; type: "QColor" }
+        Property { name: "headerTextColor"; type: "QColor" }
+        Property { name: "headerBackgroundColor"; type: "QColor" }
+        Property { name: "headerAlternateBackgroundColor"; type: "QColor" }
+        Property { name: "headerHoverColor"; type: "QColor" }
+        Property { name: "headerFocusColor"; type: "QColor" }
+        Property { name: "defaultFont"; type: "QFont" }
+        Property { name: "smallFont"; type: "QFont" }
+        Signal { name: "changed" }
+        Signal {
+            name: "sync"
+            Parameter { name: "object"; type: "QQuickItem"; isPointer: true }
+        }
+    }
+    Component {
+        name: "Kirigami::PlatformTheme"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/Theme 2.2"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "ColorSet"
+            values: {
+                "View": 0,
+                "Window": 1,
+                "Button": 2,
+                "Selection": 3,
+                "Tooltip": 4,
+                "Complementary": 5,
+                "Header": 6,
+                "ColorSetCount": 7
+            }
+        }
+        Enum {
+            name: "ColorGroup"
+            values: {
+                "Disabled": 1,
+                "Active": 0,
+                "Inactive": 2,
+                "Normal": 0,
+                "ColorGroupCount": 1
+            }
+        }
+        Property { name: "colorSet"; type: "ColorSet" }
+        Property { name: "colorGroup"; type: "ColorGroup" }
+        Property { name: "inherit"; type: "bool" }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "disabledTextColor"; type: "QColor" }
+        Property { name: "highlightedTextColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "linkColor"; type: "QColor" }
+        Property { name: "visitedLinkColor"; type: "QColor" }
+        Property { name: "negativeTextColor"; type: "QColor" }
+        Property { name: "neutralTextColor"; type: "QColor" }
+        Property { name: "positiveTextColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "highlightColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "linkBackgroundColor"; type: "QColor" }
+        Property { name: "visitedLinkBackgroundColor"; type: "QColor" }
+        Property { name: "negativeBackgroundColor"; type: "QColor" }
+        Property { name: "neutralBackgroundColor"; type: "QColor" }
+        Property { name: "positiveBackgroundColor"; type: "QColor" }
+        Property { name: "focusColor"; type: "QColor" }
+        Property { name: "hoverColor"; type: "QColor" }
+        Property { name: "defaultFont"; type: "QFont"; isReadonly: true }
+        Property { name: "smallFont"; type: "QFont"; isReadonly: true }
+        Property { name: "palette"; type: "QPalette"; isReadonly: true }
+        Signal { name: "colorsChanged" }
+        Signal {
+            name: "defaultFontChanged"
+            Parameter { name: "font"; type: "QFont" }
+        }
+        Signal {
+            name: "smallFontChanged"
+            Parameter { name: "font"; type: "QFont" }
+        }
+        Signal {
+            name: "colorSetChanged"
+            Parameter { name: "colorSet"; type: "Kirigami::PlatformTheme::ColorSet" }
+        }
+        Signal {
+            name: "colorGroupChanged"
+            Parameter { name: "colorGroup"; type: "Kirigami::PlatformTheme::ColorGroup" }
+        }
+        Signal {
+            name: "paletteChanged"
+            Parameter { name: "pal"; type: "QPalette" }
+        }
+        Signal {
+            name: "inheritChanged"
+            Parameter { name: "inherit"; type: "bool" }
+        }
+        Method {
+            name: "iconFromTheme"
+            type: "QIcon"
+            Parameter { name: "name"; type: "string" }
+            Parameter { name: "customColor"; type: "QColor" }
+        }
+        Method {
+            name: "iconFromTheme"
+            type: "QIcon"
+            Parameter { name: "name"; type: "string" }
+        }
+    }
+    Component {
+        name: "KirigamiWheelEvent"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/WheelEvent 2.9"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "x"; type: "double"; isReadonly: true }
+        Property { name: "y"; type: "double"; isReadonly: true }
+        Property { name: "angleDelta"; type: "QPointF"; isReadonly: true }
+        Property { name: "pixelDelta"; type: "QPointF"; isReadonly: true }
+        Property { name: "buttons"; type: "int"; isReadonly: true }
+        Property { name: "modifiers"; type: "int"; isReadonly: true }
+        Property { name: "inverted"; type: "bool"; isReadonly: true }
+        Property { name: "accepted"; type: "bool" }
+    }
+    Component {
+        name: "MessageType"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/MessageType 2.4"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Type"
+            values: {
+                "Information": 0,
+                "Positive": 1,
+                "Warning": 2,
+                "Error": 3
+            }
+        }
+    }
+    Component {
+        name: "MnemonicAttached"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/MnemonicData 2.3"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "ControlType"
+            values: {
+                "ActionElement": 0,
+                "DialogButton": 1,
+                "MenuItem": 2,
+                "FormLabel": 3,
+                "SecondaryControl": 4
+            }
+        }
+        Property { name: "label"; type: "string" }
+        Property { name: "richTextLabel"; type: "string"; isReadonly: true }
+        Property { name: "mnemonicLabel"; type: "string"; isReadonly: true }
+        Property { name: "enabled"; type: "bool" }
+        Property { name: "controlType"; type: "MnemonicAttached::ControlType" }
+        Property { name: "sequence"; type: "QKeySequence"; isReadonly: true }
+        Property { name: "active"; type: "bool"; isReadonly: true }
+    }
+    Component {
+        name: "NameUtils"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/NameUtils 2.14"]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0]
+        Method {
+            name: "initialsFromString"
+            type: "string"
+            Parameter { name: "name"; type: "string" }
+        }
+        Method {
+            name: "colorsFromString"
+            type: "QColor"
+            Parameter { name: "name"; type: "string" }
+        }
+        Method {
+            name: "isStringUnsuitableForInitials"
+            type: "bool"
+            Parameter { name: "name"; type: "string" }
+        }
+    }
+    Component {
+        name: "PagePool"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/PagePool 2.11"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "lastLoadedUrl"; type: "QUrl"; isReadonly: true }
+        Property { name: "lastLoadedItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "cachePages"; type: "bool" }
+        Method {
+            name: "loadPage"
+            type: "QQuickItem*"
+            Parameter { name: "url"; type: "string" }
+            Parameter { name: "callback"; type: "QJSValue" }
+        }
+        Method {
+            name: "loadPage"
+            type: "QQuickItem*"
+            Parameter { name: "url"; type: "string" }
+        }
+        Method {
+            name: "loadPageWithProperties"
+            type: "QQuickItem*"
+            Parameter { name: "url"; type: "string" }
+            Parameter { name: "properties"; type: "QVariantMap" }
+            Parameter { name: "callback"; type: "QJSValue" }
+        }
+        Method {
+            name: "loadPageWithProperties"
+            type: "QQuickItem*"
+            Parameter { name: "url"; type: "string" }
+            Parameter { name: "properties"; type: "QVariantMap" }
+        }
+        Method {
+            name: "urlForPage"
+            type: "QUrl"
+            Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "pageForUrl"
+            type: "QQuickItem*"
+            Parameter { name: "url"; type: "QUrl" }
+        }
+        Method {
+            name: "contains"
+            type: "bool"
+            Parameter { name: "page"; type: "QVariant" }
+        }
+        Method {
+            name: "deletePage"
+            Parameter { name: "page"; type: "QVariant" }
+        }
+        Method {
+            name: "resolvedUrl"
+            type: "QUrl"
+            Parameter { name: "file"; type: "string" }
+        }
+        Method {
+            name: "isLocalUrl"
+            type: "bool"
+            Parameter { name: "url"; type: "QUrl" }
+        }
+        Method { name: "clear" }
+    }
+    Component {
+        name: "PageRoute"
+        defaultProperty: "component"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/PageRoute 2.12"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "name"; type: "string" }
+        Property { name: "component"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "cache"; type: "bool" }
+        Property { name: "cost"; type: "int" }
+        Signal { name: "preloadDataChanged" }
+        Signal { name: "preloadChanged" }
+    }
+    Component {
+        name: "PageRouter"
+        defaultProperty: "routes"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/PageRouter 2.12"]
+        exportMetaObjectRevisions: [0]
+        attachedType: "PageRouterAttached"
+        Property { name: "routes"; type: "PageRoute"; isList: true; isReadonly: true }
+        Property { name: "initialRoute"; type: "QJSValue" }
+        Property { name: "pageStack"; type: "ColumnView"; isPointer: true }
+        Property { name: "cacheCapacity"; type: "int" }
+        Property { name: "preloadedPoolCapacity"; type: "int" }
+        Property { name: "params"; type: "QQmlPropertyMap"; isReadonly: true; isPointer: true }
+        Signal { name: "currentIndexChanged" }
+        Signal { name: "navigationChanged" }
+        Method {
+            name: "navigateToRoute"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method {
+            name: "routeActive"
+            type: "bool"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method {
+            name: "pushRoute"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method { name: "popRoute" }
+        Method {
+            name: "bringToView"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method { name: "currentRoutes"; type: "QJSValue" }
+    }
+    Component {
+        name: "PageRouterAttached"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/PageRouterAttached 2.12"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "router"; type: "PageRouter"; isPointer: true }
+        Property { name: "data"; type: "QVariant" }
+        Property { name: "isCurrent"; type: "bool"; isReadonly: true }
+        Property { name: "watchedRoute"; type: "QJSValue" }
+        Property { name: "preload"; type: "PreloadRouteGroup"; isReadonly: true; isPointer: true }
+        Property { name: "watchedRouteActive"; type: "bool"; isReadonly: true }
+        Signal { name: "navigationChanged" }
+        Method {
+            name: "navigateToRoute"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method {
+            name: "routeActive"
+            type: "bool"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method {
+            name: "pushRoute"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method { name: "popRoute" }
+        Method {
+            name: "bringToView"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method {
+            name: "pushFromHere"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+        Method { name: "popFromHere" }
+        Method {
+            name: "replaceFromHere"
+            Parameter { name: "route"; type: "QJSValue" }
+        }
+    }
+    Component {
+        name: "PreloadRouteGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/PreloadRouteGroup 2.14"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "route"; type: "QJSValue" }
+        Property { name: "when"; type: "bool" }
+        Signal { name: "changed" }
+    }
+    Component {
+        name: "ScenePositionAttached"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/ScenePosition 2.5"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "x"; type: "int"; isReadonly: true }
+        Property { name: "y"; type: "int"; isReadonly: true }
+    }
+    Component {
+        name: "Settings"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/Settings 2.0"]
+        isCreatable: false
+        isSingleton: true
+        exportMetaObjectRevisions: [0]
+        Property { name: "tabletModeAvailable"; type: "bool"; isReadonly: true }
+        Property { name: "isMobile"; type: "bool"; isReadonly: true }
+        Property { name: "tabletMode"; type: "bool"; isReadonly: true }
+        Property { name: "hasTransientTouchInput"; type: "bool"; isReadonly: true }
+        Property { name: "style"; type: "string"; isReadonly: true }
+        Property { name: "mouseWheelScrollLines"; type: "int"; isReadonly: true }
+        Property { name: "information"; type: "QStringList"; isReadonly: true }
+        Property { name: "applicationWindowIcon"; type: "QVariant"; isReadonly: true }
+    }
+    Component {
+        name: "ShadowGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/ShadowGroup 2.12"]
+        isCreatable: false
+        exportMetaObjectRevisions: [0]
+        Property { name: "size"; type: "double" }
+        Property { name: "xOffset"; type: "double" }
+        Property { name: "yOffset"; type: "double" }
+        Property { name: "color"; type: "QColor" }
+        Signal { name: "changed" }
+    }
+    Component {
+        name: "ShadowedRectangle"
+        defaultProperty: "data"
+        prototype: "QQuickItem"
+        exports: ["org.kde.kirigami/ShadowedRectangle 2.12"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "radius"; type: "double" }
+        Property { name: "color"; type: "QColor" }
+        Property { name: "border"; type: "BorderGroup"; isReadonly: true; isPointer: true }
+        Property { name: "shadow"; type: "ShadowGroup"; isReadonly: true; isPointer: true }
+        Property { name: "corners"; type: "CornersGroup"; isReadonly: true; isPointer: true }
+        Property { name: "softwareRendering"; type: "bool"; isReadonly: true }
+    }
+    Component {
+        name: "ShadowedTexture"
+        defaultProperty: "data"
+        prototype: "ShadowedRectangle"
+        exports: ["org.kde.kirigami/ShadowedTexture 2.12"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "source"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        name: "SizeGroup"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/SizeGroup 2.14"]
+        exportMetaObjectRevisions: [0]
+        Enum {
+            name: "Mode"
+            values: {
+                "None": 0,
+                "Width": 1,
+                "Height": 2,
+                "Both": 3
+            }
+        }
+        Property { name: "mode"; type: "Mode" }
+        Property { name: "items"; type: "QQuickItem"; isList: true; isReadonly: true }
+        Method { name: "relayout" }
+    }
+    Component {
+        name: "ToolBarLayout"
+        defaultProperty: "data"
+        prototype: "QQuickItem"
+        exports: ["org.kde.kirigami/ToolBarLayout 2.14"]
+        exportMetaObjectRevisions: [0]
+        attachedType: "ToolBarLayoutAttached"
+        Enum {
+            name: "HeightMode"
+            values: {
+                "AlwaysCenter": 0,
+                "AlwaysFill": 1,
+                "ConstrainIfLarger": 2
+            }
+        }
+        Property { name: "actions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "hiddenActions"; type: "QList<QObject*>"; isReadonly: true }
+        Property { name: "fullDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "iconDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "moreButton"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "spacing"; type: "double" }
+        Property { name: "alignment"; type: "Qt::Alignment" }
+        Property { name: "visibleWidth"; type: "double"; isReadonly: true }
+        Property { name: "minimumWidth"; type: "double"; isReadonly: true }
+        Property { name: "layoutDirection"; type: "Qt::LayoutDirection" }
+        Property { name: "heightMode"; type: "HeightMode" }
+        Method { name: "relayout" }
+    }
+    Component {
+        name: "ToolBarLayoutAttached"
+        prototype: "QObject"
+        Property { name: "action"; type: "QObject"; isReadonly: true; isPointer: true }
+    }
+    Component {
+        name: "WheelHandler"
+        prototype: "QObject"
+        exports: ["org.kde.kirigami/WheelHandler 2.9"]
+        exportMetaObjectRevisions: [0]
+        Property { name: "target"; type: "QQuickItem"; isPointer: true }
+        Property { name: "blockTargetWheel"; type: "bool" }
+        Property { name: "scrollFlickableTarget"; type: "bool" }
+        Signal {
+            name: "wheel"
+            Parameter { name: "wheel"; type: "KirigamiWheelEvent"; isPointer: true }
+        }
+    }
+    Component {
+        prototype: "QQuickPage"
+        name: "org.kde.kirigami/AboutPage 2.6"
+        exports: ["org.kde.kirigami/AboutPage 2.6"]
+        exportMetaObjectRevisions: [6]
+        isComposite: true
+        defaultProperty: "mainItem"
+        Property { name: "aboutData"; type: "QVariant" }
+        Property { name: "mainItem"; type: "QObject"; isPointer: true }
+        Property { name: "keyboardNavigationEnabled"; type: "bool" }
+        Property { name: "refreshing"; type: "bool" }
+        Property { name: "supportsRefreshing"; type: "bool" }
+        Property { name: "flickable"; type: "QQuickFlickable"; isPointer: true }
+        Property { name: "verticalScrollBarPolicy"; type: "int" }
+        Property { name: "horizontalScrollBarPolicy"; type: "int" }
+        Property { name: "isCurrentPage"; type: "bool"; isReadonly: true }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "needsAttention"; type: "bool" }
+        Property { name: "progress"; type: "QVariant" }
+        Property { name: "titleDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "globalToolBarItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "globalToolBarStyle"; type: "int" }
+        Property { name: "contextualActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "mainAction"; type: "QObject"; isPointer: true }
+        Property { name: "leftAction"; type: "QObject"; isPointer: true }
+        Property { name: "rightAction"; type: "QObject"; isPointer: true }
+        Property {
+            name: "actions"
+            type: "PageActionPropertyGroup_QMLTYPE_30"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Signal { name: "contextualActionsAboutToShow" }
+        Signal {
+            name: "backRequested"
+            Parameter { name: "event"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickPage"
+        name: "org.kde.kirigami/CategorizedSettings 2.18"
+        exports: ["org.kde.kirigami/CategorizedSettings 2.18"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "mainItem"
+        Property { name: "mainItem"; type: "QObject"; isPointer: true }
+        Property { name: "keyboardNavigationEnabled"; type: "bool" }
+        Property { name: "refreshing"; type: "bool" }
+        Property { name: "supportsRefreshing"; type: "bool" }
+        Property { name: "flickable"; type: "QQuickFlickable"; isPointer: true }
+        Property { name: "verticalScrollBarPolicy"; type: "int" }
+        Property { name: "horizontalScrollBarPolicy"; type: "int" }
+        Property { name: "isCurrentPage"; type: "bool"; isReadonly: true }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "needsAttention"; type: "bool" }
+        Property { name: "progress"; type: "QVariant" }
+        Property { name: "titleDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "globalToolBarItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "globalToolBarStyle"; type: "int" }
+        Property { name: "contextualActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "mainAction"; type: "QObject"; isPointer: true }
+        Property { name: "leftAction"; type: "QObject"; isPointer: true }
+        Property { name: "rightAction"; type: "QObject"; isPointer: true }
+        Property {
+            name: "actions"
+            type: "PageActionPropertyGroup_QMLTYPE_30"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Signal { name: "contextualActionsAboutToShow" }
+        Signal {
+            name: "backRequested"
+            Parameter { name: "event"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickPage"
+        name: "org.kde.kirigami/SettingAction 2.18"
+        exports: ["org.kde.kirigami/SettingAction 2.18"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "mainItem"
+        Property { name: "mainItem"; type: "QObject"; isPointer: true }
+        Property { name: "keyboardNavigationEnabled"; type: "bool" }
+        Property { name: "refreshing"; type: "bool" }
+        Property { name: "supportsRefreshing"; type: "bool" }
+        Property { name: "flickable"; type: "QQuickFlickable"; isPointer: true }
+        Property { name: "verticalScrollBarPolicy"; type: "int" }
+        Property { name: "horizontalScrollBarPolicy"; type: "int" }
+        Property { name: "isCurrentPage"; type: "bool"; isReadonly: true }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "needsAttention"; type: "bool" }
+        Property { name: "progress"; type: "QVariant" }
+        Property { name: "titleDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "globalToolBarItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "globalToolBarStyle"; type: "int" }
+        Property { name: "contextualActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "mainAction"; type: "QObject"; isPointer: true }
+        Property { name: "leftAction"; type: "QObject"; isPointer: true }
+        Property { name: "rightAction"; type: "QObject"; isPointer: true }
+        Property {
+            name: "actions"
+            type: "PageActionPropertyGroup_QMLTYPE_30"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Signal { name: "contextualActionsAboutToShow" }
+        Signal {
+            name: "backRequested"
+            Parameter { name: "event"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/AbstractApplicationHeader 2.0"
+        exports: ["org.kde.kirigami/AbstractApplicationHeader 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentItem"
+        Property { name: "minimumHeight"; type: "int" }
+        Property { name: "preferredHeight"; type: "int" }
+        Property { name: "maximumHeight"; type: "int" }
+        Property { name: "position"; type: "int" }
+        Property { name: "pageRow"; type: "PageRow_QMLTYPE_23"; isPointer: true }
+        Property { name: "page"; type: "Page_QMLTYPE_32"; isPointer: true }
+        Property { name: "paintedHeight"; type: "int"; isReadonly: true }
+        Property { name: "leftPadding"; type: "int" }
+        Property { name: "topPadding"; type: "int" }
+        Property { name: "rightPadding"; type: "int" }
+        Property { name: "bottomPadding"; type: "int" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "__appWindow"; type: "QObject"; isPointer: true }
+        Property { name: "background"; type: "QQuickItem"; isPointer: true }
+        Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/AbstractApplicationItem 2.1"
+        exports: ["org.kde.kirigami/AbstractApplicationItem 2.1"]
+        exportMetaObjectRevisions: [1]
+        isComposite: true
+        defaultProperty: "__data"
+        Property { name: "pageStack"; type: "QQuickItem"; isPointer: true }
+        Property { name: "activeFocusItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "font"; type: "QFont" }
+        Property { name: "palette"; type: "QVariant" }
+        Property { name: "locale"; type: "QQmlLocale"; isPointer: true }
+        Property { name: "menuBar"; type: "QQuickItem"; isPointer: true }
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "footer"; type: "QQuickItem"; isPointer: true }
+        Property { name: "controlsVisible"; type: "bool" }
+        Property { name: "globalDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "wideScreen"; type: "bool" }
+        Property { name: "contextDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "reachableMode"; type: "bool" }
+        Property { name: "reachableModeEnabled"; type: "bool" }
+        Property { name: "contentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "color"; type: "QColor" }
+        Property { name: "background"; type: "QQuickItem"; isPointer: true }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "__data"; type: "QObject"; isList: true; isReadonly: true }
+        Method {
+            name: "showPassiveNotification"
+            type: "QVariant"
+            Parameter { name: "message"; type: "QVariant" }
+            Parameter { name: "timeout"; type: "QVariant" }
+            Parameter { name: "actionText"; type: "QVariant" }
+            Parameter { name: "callBack"; type: "QVariant" }
+        }
+        Method { name: "hidePassiveNotification"; type: "QVariant" }
+        Method { name: "applicationWindow"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickApplicationWindow"
+        name: "org.kde.kirigami/AbstractApplicationWindow 2.0"
+        exports: ["org.kde.kirigami/AbstractApplicationWindow 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentData"
+        Property { name: "pageStack"; type: "QQuickItem"; isPointer: true }
+        Property { name: "controlsVisible"; type: "bool" }
+        Property { name: "globalDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "wideScreen"; type: "bool" }
+        Property { name: "contextDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "reachableMode"; type: "bool" }
+        Property { name: "reachableModeEnabled"; type: "bool" }
+        Property { name: "quitAction"; type: "Action_QMLTYPE_13"; isReadonly: true; isPointer: true }
+        Method {
+            name: "showPassiveNotification"
+            type: "QVariant"
+            Parameter { name: "message"; type: "QVariant" }
+            Parameter { name: "timeout"; type: "QVariant" }
+            Parameter { name: "actionText"; type: "QVariant" }
+            Parameter { name: "callBack"; type: "QVariant" }
+        }
+        Method { name: "hidePassiveNotification"; type: "QVariant" }
+        Method { name: "applicationWindow"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/AbstractCard 2.4"
+        exports: ["org.kde.kirigami/AbstractCard 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "headerOrientation"; type: "int" }
+        Property { name: "footer"; type: "QQuickItem"; isPointer: true }
+        Property { name: "showClickFeedback"; type: "bool" }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/AbstractItemViewHeader 2.1"
+        exports: ["org.kde.kirigami/AbstractItemViewHeader 2.1"]
+        exportMetaObjectRevisions: [1]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "minimumHeight"; type: "int" }
+        Property { name: "maximumHeight"; type: "int" }
+        Property { name: "view"; type: "QQuickListView"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/AbstractListItem 2.0"
+        exports: ["org.kde.kirigami/AbstractListItem 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "_default"
+        Property { name: "supportsMouseEvents"; type: "bool" }
+        Property { name: "alternatingBackground"; type: "bool" }
+        Property { name: "sectionDelegate"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "action"; type: "QQuickAction"; isPointer: true }
+        Property { name: "containsMouse"; type: "bool"; isReadonly: true }
+        Property { name: "_default"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickAction"
+        name: "org.kde.kirigami/Action 2.0"
+        exports: ["org.kde.kirigami/Action 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "children"
+        Enum {
+            name: "DisplayHint"
+            values: {
+                "NoPreference": 0,
+                "IconOnly": 1,
+                "KeepVisible": 2,
+                "AlwaysHide": 4,
+                "HideChildIndicator": 8
+            }
+        }
+        Property { name: "visible"; type: "bool" }
+        Property { name: "tooltip"; type: "string" }
+        Property { name: "separator"; type: "bool" }
+        Property { name: "expandible"; type: "bool" }
+        Property { name: "parent"; type: "Action_QMLTYPE_12"; isPointer: true }
+        Property { name: "displayHint"; type: "int" }
+        Property { name: "displayComponent"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "__children"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "visibleChildren"; type: "QVariant"; isReadonly: true }
+        Property { name: "iconName"; type: "string" }
+        Property { name: "iconSource"; type: "QUrl" }
+        Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
+        Method {
+            name: "displayHintSet"
+            type: "QVariant"
+            Parameter { name: "hint"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickTextField"
+        name: "org.kde.kirigami/ActionTextField 2.7"
+        exports: ["org.kde.kirigami/ActionTextField 2.7"]
+        exportMetaObjectRevisions: [7]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "focusSequence"; type: "string" }
+        Property { name: "leftActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "rightActions"; type: "QObject"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/ActionToolBar 2.5"
+        exports: ["org.kde.kirigami/ActionToolBar 2.5"]
+        exportMetaObjectRevisions: [5]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "hiddenActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "flat"; type: "bool" }
+        Property { name: "display"; type: "int" }
+        Property { name: "position"; type: "int" }
+        Property { name: "overflowIconName"; type: "string" }
+        Property { name: "actions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "alignment"; type: "int" }
+        Property { name: "maximumContentWidth"; type: "double"; isReadonly: true }
+        Property { name: "visibleWidth"; type: "double"; isReadonly: true }
+        Property { name: "heightMode"; type: "int" }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/ApplicationHeader 2.0"
+        exports: ["org.kde.kirigami/ApplicationHeader 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentItem"
+        Property { name: "headerStyle"; type: "int" }
+        Property { name: "backButtonEnabled"; type: "bool" }
+        Property { name: "pageDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "minimumHeight"; type: "int" }
+        Property { name: "preferredHeight"; type: "int" }
+        Property { name: "maximumHeight"; type: "int" }
+        Property { name: "position"; type: "int" }
+        Property { name: "pageRow"; type: "PageRow_QMLTYPE_23"; isPointer: true }
+        Property { name: "page"; type: "Page_QMLTYPE_32"; isPointer: true }
+        Property { name: "paintedHeight"; type: "int"; isReadonly: true }
+        Property { name: "leftPadding"; type: "int" }
+        Property { name: "topPadding"; type: "int" }
+        Property { name: "rightPadding"; type: "int" }
+        Property { name: "bottomPadding"; type: "int" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "__appWindow"; type: "QObject"; isPointer: true }
+        Property { name: "background"; type: "QQuickItem"; isPointer: true }
+        Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/ApplicationItem 2.1"
+        exports: ["org.kde.kirigami/ApplicationItem 2.1"]
+        exportMetaObjectRevisions: [1]
+        isComposite: true
+        defaultProperty: "__data"
+        Property { name: "pageStack"; type: "PageRow_QMLTYPE_23"; isReadonly: true; isPointer: true }
+        Property { name: "activeFocusItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "font"; type: "QFont" }
+        Property { name: "palette"; type: "QVariant" }
+        Property { name: "locale"; type: "QQmlLocale"; isPointer: true }
+        Property { name: "menuBar"; type: "QQuickItem"; isPointer: true }
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "footer"; type: "QQuickItem"; isPointer: true }
+        Property { name: "controlsVisible"; type: "bool" }
+        Property { name: "globalDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "wideScreen"; type: "bool" }
+        Property { name: "contextDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "reachableMode"; type: "bool" }
+        Property { name: "reachableModeEnabled"; type: "bool" }
+        Property { name: "contentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "color"; type: "QColor" }
+        Property { name: "background"; type: "QQuickItem"; isPointer: true }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "__data"; type: "QObject"; isList: true; isReadonly: true }
+        Method {
+            name: "showPassiveNotification"
+            type: "QVariant"
+            Parameter { name: "message"; type: "QVariant" }
+            Parameter { name: "timeout"; type: "QVariant" }
+            Parameter { name: "actionText"; type: "QVariant" }
+            Parameter { name: "callBack"; type: "QVariant" }
+        }
+        Method { name: "hidePassiveNotification"; type: "QVariant" }
+        Method { name: "applicationWindow"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickApplicationWindow"
+        name: "org.kde.kirigami/ApplicationWindow 2.0"
+        exports: ["org.kde.kirigami/ApplicationWindow 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentData"
+        Property { name: "pageStack"; type: "PageRow_QMLTYPE_23"; isReadonly: true; isPointer: true }
+        Property { name: "controlsVisible"; type: "bool" }
+        Property { name: "globalDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "wideScreen"; type: "bool" }
+        Property { name: "contextDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "reachableMode"; type: "bool" }
+        Property { name: "reachableModeEnabled"; type: "bool" }
+        Property { name: "quitAction"; type: "Action_QMLTYPE_13"; isReadonly: true; isPointer: true }
+        Method {
+            name: "showPassiveNotification"
+            type: "QVariant"
+            Parameter { name: "message"; type: "QVariant" }
+            Parameter { name: "timeout"; type: "QVariant" }
+            Parameter { name: "actionText"; type: "QVariant" }
+            Parameter { name: "callBack"; type: "QVariant" }
+        }
+        Method { name: "hidePassiveNotification"; type: "QVariant" }
+        Method { name: "applicationWindow"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/Avatar 2.13"
+        exports: ["org.kde.kirigami/Avatar 2.13"]
+        exportMetaObjectRevisions: [13]
+        isComposite: true
+        defaultProperty: "data"
+        Enum {
+            name: "ImageMode"
+            values: {
+                "AlwaysShowImage": 0,
+                "AdaptiveImageOrInitals": 1,
+                "AlwaysShowInitials": 2
+            }
+        }
+        Enum {
+            name: "InitialsMode"
+            values: {
+                "UseInitials": 0,
+                "UseIcon": 1
+            }
+        }
+        Property { name: "name"; type: "string" }
+        Property { name: "initialsMode"; type: "int" }
+        Property { name: "imageMode"; type: "int" }
+        Property { name: "color"; type: "QVariant" }
+        Property { name: "actions"; type: "AvatarGroup"; isPointer: true }
+        Property { name: "border"; type: "BorderPropertiesGroup_QMLTYPE_190"; isPointer: true }
+        Property { name: "source"; type: "QUrl" }
+        Property { name: "cache"; type: "bool" }
+        Property { name: "sourceSize"; type: "QSize" }
+        Property { name: "smooth"; type: "bool" }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/BasicListItem 2.0"
+        exports: ["org.kde.kirigami/BasicListItem 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "_basicDefault"
+        Property { name: "leading"; type: "QQuickItem"; isPointer: true }
+        Property { name: "leadingPadding"; type: "double" }
+        Property { name: "trailing"; type: "QQuickItem"; isPointer: true }
+        Property { name: "trailingPadding"; type: "double" }
+        Property { name: "bold"; type: "bool" }
+        Property { name: "icon"; type: "QVariant" }
+        Property { name: "reserveSpaceForSubtitle"; type: "bool" }
+        Property { name: "fadeContent"; type: "bool" }
+        Property { name: "label"; type: "string" }
+        Property { name: "subtitle"; type: "string" }
+        Property { name: "iconSize"; type: "int" }
+        Property { name: "iconColor"; type: "QColor" }
+        Property { name: "reserveSpaceForIcon"; type: "bool" }
+        Property { name: "reserveSpaceForLabel"; type: "bool" }
+        Property { name: "textSpacing"; type: "double" }
+        Property { name: "_basicDefault"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "supportsMouseEvents"; type: "bool" }
+        Property { name: "alternatingBackground"; type: "bool" }
+        Property { name: "sectionDelegate"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "action"; type: "QQuickAction"; isPointer: true }
+        Property { name: "containsMouse"; type: "bool"; isReadonly: true }
+        Property { name: "_default"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/Card 2.4"
+        exports: ["org.kde.kirigami/Card 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "actions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "hiddenActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "banner"; type: "BannerImage_QMLTYPE_200"; isReadonly: true; isPointer: true }
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "headerOrientation"; type: "int" }
+        Property { name: "footer"; type: "QQuickItem"; isPointer: true }
+        Property { name: "showClickFeedback"; type: "bool" }
+    }
+    Component {
+        prototype: "QQuickGridView"
+        name: "org.kde.kirigami/CardsGridView 2.4"
+        exports: ["org.kde.kirigami/CardsGridView 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "delegate"
+        Property { name: "columns"; type: "int"; isReadonly: true }
+        Property { name: "maximumColumns"; type: "int" }
+        Property { name: "maximumColumnWidth"; type: "int" }
+        Property { name: "minimumColumnWidth"; type: "int" }
+        Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "_delegateComponent"; type: "QQmlComponent"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickGridLayout"
+        name: "org.kde.kirigami/CardsLayout 2.4"
+        exports: ["org.kde.kirigami/CardsLayout 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "maximumColumns"; type: "int" }
+        Property { name: "maximumColumnWidth"; type: "int" }
+        Property { name: "minimumColumnWidth"; type: "int" }
+    }
+    Component {
+        prototype: "QQuickListView"
+        name: "org.kde.kirigami/CardsListView 2.4"
+        exports: ["org.kde.kirigami/CardsListView 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "_delegateComponent"; type: "QQmlComponent"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/CheckableListItem 2.14"
+        exports: ["org.kde.kirigami/CheckableListItem 2.14"]
+        exportMetaObjectRevisions: [14]
+        isComposite: true
+        defaultProperty: "_basicDefault"
+        Property { name: "leading"; type: "QQuickItem"; isPointer: true }
+        Property { name: "leadingPadding"; type: "double" }
+        Property { name: "trailing"; type: "QQuickItem"; isPointer: true }
+        Property { name: "trailingPadding"; type: "double" }
+        Property { name: "bold"; type: "bool" }
+        Property { name: "icon"; type: "QVariant" }
+        Property { name: "reserveSpaceForSubtitle"; type: "bool" }
+        Property { name: "fadeContent"; type: "bool" }
+        Property { name: "label"; type: "string" }
+        Property { name: "subtitle"; type: "string" }
+        Property { name: "iconSize"; type: "int" }
+        Property { name: "iconColor"; type: "QColor" }
+        Property { name: "reserveSpaceForIcon"; type: "bool" }
+        Property { name: "reserveSpaceForLabel"; type: "bool" }
+        Property { name: "textSpacing"; type: "double" }
+        Property { name: "_basicDefault"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "supportsMouseEvents"; type: "bool" }
+        Property { name: "alternatingBackground"; type: "bool" }
+        Property { name: "sectionDelegate"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "action"; type: "QQuickAction"; isPointer: true }
+        Property { name: "containsMouse"; type: "bool"; isReadonly: true }
+        Property { name: "_default"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickDrawer"
+        name: "org.kde.kirigami/ContextDrawer 2.0"
+        exports: ["org.kde.kirigami/ContextDrawer 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentData"
+        Property { name: "title"; type: "string" }
+        Property { name: "actions"; type: "QVariant" }
+        Property { name: "page"; type: "Page_QMLTYPE_32"; isPointer: true }
+        Property { name: "header"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "footer"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "drawerOpen"; type: "bool" }
+        Property { name: "enabled"; type: "bool" }
+        Property { name: "peeking"; type: "bool" }
+        Property { name: "animating"; type: "bool"; isReadonly: true }
+        Property { name: "collapsible"; type: "bool" }
+        Property { name: "collapsed"; type: "bool" }
+        Property { name: "collapsedSize"; type: "int" }
+        Property {
+            name: "handleOpenIcon"
+            type: "IconPropertiesGroup_QMLTYPE_94"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "handleClosedIcon"; type: "IconPropertiesGroup_QMLTYPE_94"; isPointer: true }
+        Property { name: "handleOpenToolTip"; type: "string" }
+        Property { name: "handleClosedToolTip"; type: "string" }
+        Property { name: "handleVisible"; type: "bool" }
+        Property { name: "handle"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "__internal"; type: "QObject"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickColumnLayout"
+        name: "org.kde.kirigami/FlexColumn 2.14"
+        exports: ["org.kde.kirigami/FlexColumn 2.14"]
+        exportMetaObjectRevisions: [14]
+        isComposite: true
+        defaultProperty: "columnChildren"
+        Enum {
+            name: "CrossAxis"
+            values: {
+                "Left": 0,
+                "Center": 1,
+                "Right": 2
+            }
+        }
+        Property { name: "padding"; type: "double" }
+        Property { name: "maximumWidth"; type: "double" }
+        Property { name: "alignment"; type: "int" }
+        Property { name: "columnChildren"; type: "QQuickItem"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/FormLayout 2.3"
+        exports: ["org.kde.kirigami/FormLayout 2.3"]
+        exportMetaObjectRevisions: [3]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "wideMode"; type: "bool" }
+        Property { name: "twinFormLayouts"; type: "QQuickItem"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickDrawer"
+        name: "org.kde.kirigami/GlobalDrawer 2.0"
+        exports: ["org.kde.kirigami/GlobalDrawer 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "content"
+        Property { name: "actions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "bannerVisible"; type: "bool" }
+        Property { name: "showContentWhenCollapsed"; type: "bool" }
+        Property { name: "showTopContentWhenCollapsed"; type: "bool" }
+        Property { name: "showHeaderWhenCollapsed"; type: "bool" }
+        Property { name: "resetMenuOnTriggered"; type: "bool" }
+        Property { name: "currentSubMenu"; type: "Action_QMLTYPE_13"; isReadonly: true; isPointer: true }
+        Property { name: "isMenu"; type: "bool" }
+        Property { name: "collapseButtonVisible"; type: "bool" }
+        Property { name: "title"; type: "string" }
+        Property { name: "titleIcon"; type: "QVariant" }
+        Property { name: "bannerImageSource"; type: "QUrl" }
+        Property { name: "content"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "topContent"; type: "QObject"; isList: true; isReadonly: true }
+        Signal { name: "bannerClicked" }
+        Method { name: "resetMenu"; type: "QVariant" }
+        Property { name: "drawerOpen"; type: "bool" }
+        Property { name: "enabled"; type: "bool" }
+        Property { name: "peeking"; type: "bool" }
+        Property { name: "animating"; type: "bool"; isReadonly: true }
+        Property { name: "collapsible"; type: "bool" }
+        Property { name: "collapsed"; type: "bool" }
+        Property { name: "collapsedSize"; type: "int" }
+        Property {
+            name: "handleOpenIcon"
+            type: "IconPropertiesGroup_QMLTYPE_94"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "handleClosedIcon"; type: "IconPropertiesGroup_QMLTYPE_94"; isPointer: true }
+        Property { name: "handleOpenToolTip"; type: "string" }
+        Property { name: "handleClosedToolTip"; type: "string" }
+        Property { name: "handleVisible"; type: "bool" }
+        Property { name: "handle"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "__internal"; type: "QObject"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickLabel"
+        name: "org.kde.kirigami/Heading 2.0"
+        exports: ["org.kde.kirigami/Heading 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "data"
+        Enum {
+            name: "Type"
+            values: {
+                "Normal": 0,
+                "Primary": 1,
+                "Secondary": 2
+            }
+        }
+        Property { name: "level"; type: "int" }
+        Property { name: "step"; type: "int" }
+        Property { name: "type"; type: "int" }
+        Method {
+            name: "headerPointSize"
+            type: "QVariant"
+            Parameter { name: "l"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/Hero 2.15"
+        exports: ["org.kde.kirigami/Hero 2.15"]
+        exportMetaObjectRevisions: [15]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "source"; type: "QQuickItem"; isPointer: true }
+        Property { name: "destination"; type: "QQuickItem"; isPointer: true }
+        Property { name: "restore"; type: "bool" }
+        Property { name: "mask"; type: "QObject"; isReadonly: true; isPointer: true }
+        Property { name: "easing"; type: "QObject"; isReadonly: true; isPointer: true }
+        Property { name: "duration"; type: "int" }
+        Method { name: "open"; type: "QVariant" }
+        Method { name: "close"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/InlineMessage 2.4"
+        exports: ["org.kde.kirigami/InlineMessage 2.4"]
+        exportMetaObjectRevisions: [4]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "type"; type: "int" }
+        Property { name: "icon"; type: "IconPropertiesGroup_QMLTYPE_94"; isPointer: true }
+        Property { name: "text"; type: "string" }
+        Property { name: "showCloseButton"; type: "bool" }
+        Property { name: "actions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "animating"; type: "bool"; isReadonly: true }
+        Property { name: "_animating"; type: "bool" }
+        Signal {
+            name: "linkHovered"
+            Parameter { name: "link"; type: "string" }
+        }
+        Signal {
+            name: "linkActivated"
+            Parameter { name: "link"; type: "string" }
+        }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/ItemViewHeader 2.1"
+        exports: ["org.kde.kirigami/ItemViewHeader 2.1"]
+        exportMetaObjectRevisions: [1]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "title"; type: "string" }
+        Property { name: "color"; type: "QColor" }
+        Property { name: "backgroundImage"; type: "QQuickImage"; isReadonly: true; isPointer: true }
+        Property { name: "minimumHeight"; type: "int" }
+        Property { name: "maximumHeight"; type: "int" }
+        Property { name: "view"; type: "QQuickListView"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickLabel"
+        name: "org.kde.kirigami/Label 2.0"
+        exports: ["org.kde.kirigami/Label 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "data"
+    }
+    Component {
+        prototype: "QQuickLabel"
+        name: "org.kde.kirigami/LinkButton 2.6"
+        exports: ["org.kde.kirigami/LinkButton 2.6"]
+        exportMetaObjectRevisions: [6]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "action"; type: "Action_QMLTYPE_13"; isPointer: true }
+        Property { name: "acceptedButtons"; type: "int" }
+        Property { name: "mouseArea"; type: "QQuickMouseArea"; isReadonly: true; isPointer: true }
+        Signal {
+            name: "pressed"
+            Parameter { name: "mouse"; type: "QObject"; isPointer: true }
+        }
+        Signal {
+            name: "clicked"
+            Parameter { name: "mouse"; type: "QObject"; isPointer: true }
+        }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/ListItemDragHandle 2.5"
+        exports: ["org.kde.kirigami/ListItemDragHandle 2.5"]
+        exportMetaObjectRevisions: [5]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "listItem"; type: "QQuickItem"; isPointer: true }
+        Property { name: "listView"; type: "QQuickListView"; isPointer: true }
+        Signal {
+            name: "moveRequested"
+            Parameter { name: "oldIndex"; type: "int" }
+            Parameter { name: "newIndex"; type: "int" }
+        }
+        Signal { name: "dropped" }
+    }
+    Component {
+        prototype: "QQuickItemDelegate"
+        name: "org.kde.kirigami/ListSectionHeader 2.10"
+        exports: ["org.kde.kirigami/ListSectionHeader 2.10"]
+        exportMetaObjectRevisions: [10]
+        isComposite: true
+        defaultProperty: "_contents"
+        Property { name: "label"; type: "string" }
+        Property { name: "_contents"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "supportsMouseEvents"; type: "bool" }
+        Property { name: "alternatingBackground"; type: "bool" }
+        Property { name: "sectionDelegate"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "action"; type: "QQuickAction"; isPointer: true }
+        Property { name: "containsMouse"; type: "bool"; isReadonly: true }
+        Property { name: "_default"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickDrawer"
+        name: "org.kde.kirigami/OverlayDrawer 2.0"
+        exports: ["org.kde.kirigami/OverlayDrawer 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentData"
+        Property { name: "drawerOpen"; type: "bool" }
+        Property { name: "enabled"; type: "bool" }
+        Property { name: "peeking"; type: "bool" }
+        Property { name: "animating"; type: "bool"; isReadonly: true }
+        Property { name: "collapsible"; type: "bool" }
+        Property { name: "collapsed"; type: "bool" }
+        Property { name: "collapsedSize"; type: "int" }
+        Property {
+            name: "handleOpenIcon"
+            type: "IconPropertiesGroup_QMLTYPE_94"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "handleClosedIcon"; type: "IconPropertiesGroup_QMLTYPE_94"; isPointer: true }
+        Property { name: "handleOpenToolTip"; type: "string" }
+        Property { name: "handleClosedToolTip"; type: "string" }
+        Property { name: "handleVisible"; type: "bool" }
+        Property { name: "handle"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "__internal"; type: "QObject"; isPointer: true }
+    }
+    Component {
+        prototype: "QObject"
+        name: "org.kde.kirigami/OverlaySheet 2.0"
+        exports: ["org.kde.kirigami/OverlaySheet 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentItem"
+        Property { name: "contentItem"; type: "QQuickItem"; isPointer: true }
+        Property { name: "sheetOpen"; type: "bool" }
+        Property { name: "leftPadding"; type: "int" }
+        Property { name: "topPadding"; type: "int" }
+        Property { name: "rightPadding"; type: "int" }
+        Property { name: "bottomPadding"; type: "int" }
+        Property { name: "leftInset"; type: "double" }
+        Property { name: "topInset"; type: "double" }
+        Property { name: "rightInset"; type: "double" }
+        Property { name: "bottomInset"; type: "double" }
+        Property { name: "header"; type: "QQuickItem"; isPointer: true }
+        Property { name: "footer"; type: "QQuickItem"; isPointer: true }
+        Property { name: "background"; type: "QQuickItem"; isPointer: true }
+        Property { name: "showCloseButton"; type: "bool" }
+        Property { name: "parent"; type: "QQuickItem"; isPointer: true }
+        Property { name: "rootItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Method { name: "open"; type: "QVariant" }
+        Method { name: "close"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickPage"
+        name: "org.kde.kirigami/Page 2.0"
+        exports: ["org.kde.kirigami/Page 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "contentData"
+        Property { name: "flickable"; type: "QQuickFlickable"; isPointer: true }
+        Property { name: "isCurrentPage"; type: "bool"; isReadonly: true }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "needsAttention"; type: "bool" }
+        Property { name: "progress"; type: "QVariant" }
+        Property { name: "titleDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "globalToolBarItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "globalToolBarStyle"; type: "int" }
+        Property { name: "contextualActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "mainAction"; type: "QObject"; isPointer: true }
+        Property { name: "leftAction"; type: "QObject"; isPointer: true }
+        Property { name: "rightAction"; type: "QObject"; isPointer: true }
+        Property {
+            name: "actions"
+            type: "PageActionPropertyGroup_QMLTYPE_30"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Signal { name: "contextualActionsAboutToShow" }
+        Signal {
+            name: "backRequested"
+            Parameter { name: "event"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickAction"
+        name: "org.kde.kirigami/PagePoolAction 2.11"
+        exports: ["org.kde.kirigami/PagePoolAction 2.11"]
+        exportMetaObjectRevisions: [11]
+        isComposite: true
+        defaultProperty: "children"
+        Property { name: "page"; type: "string" }
+        Property { name: "pagePool"; type: "PagePool"; isPointer: true }
+        Property { name: "pageStack"; type: "QQuickItem"; isPointer: true }
+        Property { name: "basePage"; type: "Page_QMLTYPE_31"; isPointer: true }
+        Property { name: "initialProperties"; type: "QVariant" }
+        Property { name: "useLayers"; type: "bool" }
+        Property { name: "_private"; type: "QObject"; isPointer: true }
+        Method { name: "pageItem"; type: "QVariant" }
+        Method { name: "layerContainsPage"; type: "QVariant" }
+        Method { name: "stackContainsPage"; type: "QVariant" }
+        Enum {
+            name: "DisplayHint"
+            values: {
+                "NoPreference": 0,
+                "IconOnly": 1,
+                "KeepVisible": 2,
+                "AlwaysHide": 4,
+                "HideChildIndicator": 8
+            }
+        }
+        Property { name: "visible"; type: "bool" }
+        Property { name: "tooltip"; type: "string" }
+        Property { name: "separator"; type: "bool" }
+        Property { name: "expandible"; type: "bool" }
+        Property { name: "parent"; type: "Action_QMLTYPE_12"; isPointer: true }
+        Property { name: "displayHint"; type: "int" }
+        Property { name: "displayComponent"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "__children"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "visibleChildren"; type: "QVariant"; isReadonly: true }
+        Property { name: "iconName"; type: "string" }
+        Property { name: "iconSource"; type: "QUrl" }
+        Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
+        Method {
+            name: "displayHintSet"
+            type: "QVariant"
+            Parameter { name: "hint"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickControl"
+        name: "org.kde.kirigami/PageRow 2.0"
+        exports: ["org.kde.kirigami/PageRow 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "lastItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "initialPage"; type: "QVariant" }
+        Property { name: "defaultColumnWidth"; type: "int" }
+        Property { name: "wideMode"; type: "bool"; isReadonly: true }
+        Property { name: "depth"; type: "int"; isReadonly: true }
+        Property { name: "currentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "currentIndex"; type: "int" }
+        Property { name: "columnView"; type: "ColumnView"; isReadonly: true; isPointer: true }
+        Property { name: "items"; type: "QQuickItem"; isList: true; isReadonly: true }
+        Property { name: "visibleItems"; type: "QList<QObject*>"; isReadonly: true }
+        Property { name: "firstVisibleItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "lastVisibleItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "interactive"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property {
+            name: "globalToolBar"
+            type: "PageRowGlobalToolBarStyleGroup_QMLTYPE_21"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "layers"; type: "StackView_QMLTYPE_22"; isReadonly: true; isPointer: true }
+        Signal {
+            name: "pageInserted"
+            Parameter { name: "position"; type: "int" }
+            Parameter { name: "page"; type: "QQuickItem"; isPointer: true }
+        }
+        Signal {
+            name: "pagePushed"
+            Parameter { name: "page"; type: "QQuickItem"; isPointer: true }
+        }
+        Signal {
+            name: "pageRemoved"
+            Parameter { name: "page"; type: "QQuickItem"; isPointer: true }
+        }
+        Method {
+            name: "push"
+            type: "QVariant"
+            Parameter { name: "page"; type: "QVariant" }
+            Parameter { name: "properties"; type: "QVariant" }
+        }
+        Method {
+            name: "insertPage"
+            type: "QVariant"
+            Parameter { name: "position"; type: "QVariant" }
+            Parameter { name: "page"; type: "QVariant" }
+            Parameter { name: "properties"; type: "QVariant" }
+        }
+        Method {
+            name: "movePage"
+            type: "QVariant"
+            Parameter { name: "fromPos"; type: "QVariant" }
+            Parameter { name: "toPos"; type: "QVariant" }
+        }
+        Method {
+            name: "removePage"
+            type: "QVariant"
+            Parameter { name: "page"; type: "QVariant" }
+        }
+        Method {
+            name: "pop"
+            type: "QVariant"
+            Parameter { name: "page"; type: "QVariant" }
+        }
+        Method {
+            name: "replace"
+            type: "QVariant"
+            Parameter { name: "page"; type: "QVariant" }
+            Parameter { name: "properties"; type: "QVariant" }
+        }
+        Method { name: "clear"; type: "QVariant" }
+        Method {
+            name: "get"
+            type: "QVariant"
+            Parameter { name: "idx"; type: "QVariant" }
+        }
+        Method { name: "flickBack"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickTextField"
+        name: "org.kde.kirigami/PasswordField 2.8"
+        exports: ["org.kde.kirigami/PasswordField 2.8"]
+        exportMetaObjectRevisions: [8]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "showPassword"; type: "bool" }
+        Property { name: "focusSequence"; type: "string" }
+        Property { name: "leftActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "rightActions"; type: "QObject"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickColumnLayout"
+        name: "org.kde.kirigami/PlaceholderMessage 2.12"
+        exports: ["org.kde.kirigami/PlaceholderMessage 2.12"]
+        exportMetaObjectRevisions: [12]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "text"; type: "string" }
+        Property { name: "explanation"; type: "string" }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "helpfulAction"; type: "QQuickAction"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickApplicationWindow"
+        name: "org.kde.kirigami/RouterWindow 2.12"
+        exports: ["org.kde.kirigami/RouterWindow 2.12"]
+        exportMetaObjectRevisions: [12]
+        isComposite: true
+        defaultProperty: "routes"
+        Property { name: "routes"; type: "PageRoute"; isList: true; isReadonly: true }
+        Property { name: "initialRoute"; type: "QJSValue" }
+        Property { name: "router"; type: "PageRouter"; isReadonly: true; isPointer: true }
+        Property { name: "pageStack"; type: "PageRow_QMLTYPE_23"; isReadonly: true; isPointer: true }
+        Property { name: "controlsVisible"; type: "bool" }
+        Property { name: "globalDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "wideScreen"; type: "bool" }
+        Property { name: "contextDrawer"; type: "OverlayDrawer_QMLTYPE_113"; isPointer: true }
+        Property { name: "reachableMode"; type: "bool" }
+        Property { name: "reachableModeEnabled"; type: "bool" }
+        Property { name: "quitAction"; type: "Action_QMLTYPE_13"; isReadonly: true; isPointer: true }
+        Method {
+            name: "showPassiveNotification"
+            type: "QVariant"
+            Parameter { name: "message"; type: "QVariant" }
+            Parameter { name: "timeout"; type: "QVariant" }
+            Parameter { name: "actionText"; type: "QVariant" }
+            Parameter { name: "callBack"; type: "QVariant" }
+        }
+        Method { name: "hidePassiveNotification"; type: "QVariant" }
+        Method { name: "applicationWindow"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickPage"
+        name: "org.kde.kirigami/ScrollablePage 2.0"
+        exports: ["org.kde.kirigami/ScrollablePage 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "mainItem"
+        Property { name: "mainItem"; type: "QObject"; isPointer: true }
+        Property { name: "keyboardNavigationEnabled"; type: "bool" }
+        Property { name: "refreshing"; type: "bool" }
+        Property { name: "supportsRefreshing"; type: "bool" }
+        Property { name: "flickable"; type: "QQuickFlickable"; isPointer: true }
+        Property { name: "verticalScrollBarPolicy"; type: "int" }
+        Property { name: "horizontalScrollBarPolicy"; type: "int" }
+        Property { name: "isCurrentPage"; type: "bool"; isReadonly: true }
+        Property { name: "icon"; type: "ActionIconGroup_QMLTYPE_29"; isPointer: true }
+        Property { name: "needsAttention"; type: "bool" }
+        Property { name: "progress"; type: "QVariant" }
+        Property { name: "titleDelegate"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "globalToolBarItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Property { name: "globalToolBarStyle"; type: "int" }
+        Property { name: "contextualActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "mainAction"; type: "QObject"; isPointer: true }
+        Property { name: "leftAction"; type: "QObject"; isPointer: true }
+        Property { name: "rightAction"; type: "QObject"; isPointer: true }
+        Property {
+            name: "actions"
+            type: "PageActionPropertyGroup_QMLTYPE_30"
+            isReadonly: true
+            isPointer: true
+        }
+        Property { name: "overlay"; type: "QQuickItem"; isReadonly: true; isPointer: true }
+        Signal { name: "contextualActionsAboutToShow" }
+        Signal {
+            name: "backRequested"
+            Parameter { name: "event"; type: "QVariant" }
+        }
+    }
+    Component {
+        prototype: "QQuickTextField"
+        name: "org.kde.kirigami/SearchField 2.8"
+        exports: ["org.kde.kirigami/SearchField 2.8"]
+        exportMetaObjectRevisions: [8]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "autoAccept"; type: "bool" }
+        Property { name: "delaySearch"; type: "bool" }
+        Property { name: "focusSequence"; type: "string" }
+        Property { name: "leftActions"; type: "QObject"; isList: true; isReadonly: true }
+        Property { name: "rightActions"; type: "QObject"; isList: true; isReadonly: true }
+    }
+    Component {
+        prototype: "QQuickTextArea"
+        name: "org.kde.kirigami/SelectableLabel 2.20"
+        exports: ["org.kde.kirigami/SelectableLabel 2.20"]
+        exportMetaObjectRevisions: [20]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "cursorShape"; type: "Qt::CursorShape" }
+    }
+    Component {
+        prototype: "QQuickRectangle"
+        name: "org.kde.kirigami/Separator 2.0"
+        exports: ["org.kde.kirigami/Separator 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "data"
+        Enum {
+            name: "Weight"
+            values: {
+                "Light": 0,
+                "Normal": 1
+            }
+        }
+        Property { name: "weight"; type: "int" }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/ShadowedImage 2.12"
+        exports: ["org.kde.kirigami/ShadowedImage 2.12"]
+        exportMetaObjectRevisions: [12]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "color"; type: "QColor" }
+        Property { name: "radius"; type: "double" }
+        Property { name: "shadow"; type: "ShadowGroup"; isReadonly: true; isPointer: true }
+        Property { name: "border"; type: "BorderGroup"; isReadonly: true; isPointer: true }
+        Property { name: "corners"; type: "CornersGroup"; isReadonly: true; isPointer: true }
+        Property { name: "source"; type: "QUrl" }
+        Property { name: "asynchronous"; type: "bool" }
+        Property { name: "fillMode"; type: "int" }
+        Property { name: "sourceSize"; type: "QSize" }
+    }
+    Component {
+        prototype: "QQuickSwipeDelegate"
+        name: "org.kde.kirigami/SwipeListItem 2.0"
+        exports: ["org.kde.kirigami/SwipeListItem 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        defaultProperty: "_default"
+        Property { name: "alternatingBackground"; type: "bool" }
+        Property { name: "sectionDelegate"; type: "bool" }
+        Property { name: "separatorVisible"; type: "bool" }
+        Property { name: "actionsVisible"; type: "bool"; isReadonly: true }
+        Property { name: "actions"; type: "Action_QMLTYPE_12"; isList: true; isReadonly: true }
+        Property { name: "textColor"; type: "QColor" }
+        Property { name: "backgroundColor"; type: "QColor" }
+        Property { name: "alternateBackgroundColor"; type: "QColor" }
+        Property { name: "activeTextColor"; type: "QColor" }
+        Property { name: "activeBackgroundColor"; type: "QColor" }
+        Property { name: "alwaysVisibleActions"; type: "bool" }
+        Property { name: "supportsMouseEvents"; type: "bool" }
+        Property { name: "containsMouse"; type: "bool"; isReadonly: true }
+        Property { name: "_default"; type: "QQuickItem"; isPointer: true }
+    }
+    Component {
+        prototype: "QQuickItem"
+        name: "org.kde.kirigami/SwipeNavigator 2.13"
+        exports: ["org.kde.kirigami/SwipeNavigator 2.13"]
+        exportMetaObjectRevisions: [13]
+        isComposite: true
+        defaultProperty: "pages"
+        Property { name: "pages"; type: "Page_QMLTYPE_32"; isList: true; isReadonly: true }
+        Property { name: "big"; type: "bool" }
+        Property { name: "header"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "footer"; type: "QQmlComponent"; isPointer: true }
+        Property { name: "initialIndex"; type: "int" }
+        Property { name: "layers"; type: "StackView_QMLTYPE_22"; isReadonly: true; isPointer: true }
+        Property { name: "currentIndex"; type: "int" }
+    }
+    Component {
+        prototype: "QObject"
+        name: "org.kde.kirigami/Units 2.0"
+        exports: ["org.kde.kirigami/Units 2.0"]
+        exportMetaObjectRevisions: [0]
+        isComposite: true
+        isCreatable: false
+        isSingleton: true
+        Property { name: "gridUnit"; type: "int" }
+        Property { name: "iconSizes"; type: "QObject"; isPointer: true }
+        Property { name: "smallSpacing"; type: "int" }
+        Property { name: "largeSpacing"; type: "int" }
+        Property { name: "devicePixelRatio"; type: "double" }
+        Property { name: "veryLongDuration"; type: "int" }
+        Property { name: "longDuration"; type: "int" }
+        Property { name: "shortDuration"; type: "int" }
+        Property { name: "veryShortDuration"; type: "int" }
+        Property { name: "toolTipDelay"; type: "int" }
+        Property { name: "humanMoment"; type: "int" }
+        Property { name: "wheelScrollLines"; type: "int"; isReadonly: true }
+        Property { name: "fontMetrics"; type: "QVariant" }
+    }
+    Component {
+        prototype: "QQuickLabel"
+        name: "org.kde.kirigami/UrlButton 2.6"
+        exports: ["org.kde.kirigami/UrlButton 2.6"]
+        exportMetaObjectRevisions: [6]
+        isComposite: true
+        defaultProperty: "data"
+        Property { name: "url"; type: "string" }
+        Property { name: "action"; type: "Action_QMLTYPE_13"; isPointer: true }
+        Property { name: "acceptedButtons"; type: "int" }
+        Property { name: "mouseArea"; type: "QQuickMouseArea"; isReadonly: true; isPointer: true }
+        Signal {
+            name: "pressed"
+            Parameter { name: "mouse"; type: "QObject"; isPointer: true }
+        }
+        Signal {
+            name: "clicked"
+            Parameter { name: "mouse"; type: "QObject"; isPointer: true }
+        }
+    }
+}
diff --git a/src/scenegraph/managedtexturenode.cpp b/src/scenegraph/managedtexturenode.cpp
new file mode 100644 (file)
index 0000000..5c021b1
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "managedtexturenode.h"
+
+ManagedTextureNode::ManagedTextureNode()
+{
+}
+
+void ManagedTextureNode::setTexture(std::shared_ptr<QSGTexture> texture)
+{
+    m_texture = texture;
+    QSGSimpleTextureNode::setTexture(texture.get());
+}
+
+ImageTexturesCache::ImageTexturesCache()
+    : d(new ImageTexturesCachePrivate)
+{
+}
+
+ImageTexturesCache::~ImageTexturesCache()
+{
+}
+
+std::shared_ptr<QSGTexture> ImageTexturesCache::loadTexture(QQuickWindow *window, const QImage &image, QQuickWindow::CreateTextureOptions options)
+{
+    qint64 id = image.cacheKey();
+    std::shared_ptr<QSGTexture> texture = d->cache.value(id).value(window).lock();
+
+    if (!texture) {
+        auto cleanAndDelete = [this, window, id](QSGTexture *texture) {
+            QHash<QWindow *, std::weak_ptr<QSGTexture>> &textures = (d->cache)[id];
+            textures.remove(window);
+            if (textures.isEmpty()) {
+                d->cache.remove(id);
+            }
+            delete texture;
+        };
+        texture = std::shared_ptr<QSGTexture>(window->createTextureFromImage(image, options), cleanAndDelete);
+        (d->cache)[id][window] = texture;
+    }
+
+    // if we have a cache in an atlas but our request cannot use an atlassed texture
+    // create a new texture and use that
+    // don't use removedFromAtlas() as that requires keeping a reference to the non atlased version
+    if (!(options & QQuickWindow::TextureCanUseAtlas) && texture->isAtlasTexture()) {
+        texture = std::shared_ptr<QSGTexture>(window->createTextureFromImage(image, options));
+    }
+
+    return texture;
+}
+
+std::shared_ptr<QSGTexture> ImageTexturesCache::loadTexture(QQuickWindow *window, const QImage &image)
+{
+    return loadTexture(window, image, {});
+}
diff --git a/src/scenegraph/managedtexturenode.h b/src/scenegraph/managedtexturenode.h
new file mode 100644 (file)
index 0000000..b65a498
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+#include <QImage>
+#include <QQuickWindow>
+#include <QSGSimpleTextureNode>
+#include <QSGTexture>
+#include <memory>
+
+class ManagedTextureNode : public QSGSimpleTextureNode
+{
+    Q_DISABLE_COPY(ManagedTextureNode)
+public:
+    ManagedTextureNode();
+
+    void setTexture(std::shared_ptr<QSGTexture> texture);
+
+private:
+    std::shared_ptr<QSGTexture> m_texture;
+};
+
+typedef QHash<qint64, QHash<QWindow *, std::weak_ptr<QSGTexture>>> TexturesCache;
+
+struct ImageTexturesCachePrivate {
+    TexturesCache cache;
+};
+
+class ImageTexturesCache
+{
+public:
+    ImageTexturesCache();
+    ~ImageTexturesCache();
+
+    /**
+     * @returns the texture for a given @p window and @p image.
+     *
+     * If an @p image id is the same as one already provided before, we won't create
+     * a new texture and return a shared pointer to the existing texture.
+     */
+    std::shared_ptr<QSGTexture> loadTexture(QQuickWindow *window, const QImage &image, QQuickWindow::CreateTextureOptions options);
+
+    std::shared_ptr<QSGTexture> loadTexture(QQuickWindow *window, const QImage &image);
+
+private:
+    std::unique_ptr<ImageTexturesCachePrivate> d;
+};
diff --git a/src/scenegraph/paintedrectangleitem.cpp b/src/scenegraph/paintedrectangleitem.cpp
new file mode 100644 (file)
index 0000000..63e6df6
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "paintedrectangleitem.h"
+
+#include <QPainter>
+#include <cmath>
+
+PaintedRectangleItem::PaintedRectangleItem(QQuickItem *parent)
+    : QQuickPaintedItem(parent)
+{
+}
+
+void PaintedRectangleItem::setColor(const QColor &color)
+{
+    m_color = color;
+    update();
+}
+
+void PaintedRectangleItem::setRadius(qreal radius)
+{
+    m_radius = radius;
+    update();
+}
+
+void PaintedRectangleItem::setBorderColor(const QColor &color)
+{
+    m_borderColor = color;
+    update();
+}
+
+void PaintedRectangleItem::setBorderWidth(qreal width)
+{
+    m_borderWidth = width;
+    update();
+}
+
+void PaintedRectangleItem::paint(QPainter *painter)
+{
+    painter->setRenderHint(QPainter::Antialiasing, true);
+    painter->setPen(Qt::transparent);
+
+    auto radius = std::min(m_radius, std::min(width(), height()) / 2);
+    auto borderWidth = std::floor(m_borderWidth);
+
+    if (borderWidth > 0.0) {
+        painter->setBrush(m_borderColor);
+        painter->drawRoundedRect(0, 0, width(), height(), radius, radius);
+    }
+
+    painter->setBrush(m_color);
+    painter->drawRoundedRect(borderWidth, borderWidth, width() - borderWidth * 2, height() - borderWidth * 2, radius, radius);
+}
diff --git a/src/scenegraph/paintedrectangleitem.h b/src/scenegraph/paintedrectangleitem.h
new file mode 100644 (file)
index 0000000..8036682
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef PAINTEDRECTANGLEITEM_H
+#define PAINTEDRECTANGLEITEM_H
+
+#include <QQuickPaintedItem>
+
+/**
+ * A rectangle with a border and rounded corners, rendered through QPainter.
+ *
+ * This is a helper used by ShadowedRectangle as fallback for when software
+ * rendering is used, which means our shaders cannot be used.
+ *
+ * Since we cannot actually use QSGPaintedNode, we need to do some trickery
+ * using QQuickPaintedItem as a child of ShadowedRectangle.
+ *
+ * \warning This item is **not** intended as a general purpose item.
+ */
+class PaintedRectangleItem : public QQuickPaintedItem
+{
+    Q_OBJECT
+public:
+    explicit PaintedRectangleItem(QQuickItem *parent = nullptr);
+
+    void setColor(const QColor &color);
+    void setRadius(qreal radius);
+    void setBorderColor(const QColor &color);
+    void setBorderWidth(qreal width);
+
+    void paint(QPainter *painter) override;
+
+private:
+    QColor m_color;
+    qreal m_radius = 0.0;
+    QColor m_borderColor;
+    qreal m_borderWidth = 0.0;
+};
+
+#endif // PAINTEDRECTANGLEITEM_H
diff --git a/src/scenegraph/shaders/header_desktop.glsl b/src/scenegraph/shaders/header_desktop.glsl
new file mode 100644 (file)
index 0000000..53b0dd5
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// This file contains common directives needed for the shaders to work.
+// It is included as the very first bit in the shader.
+// Important: If a specific GLSL version is needed, it should be set in this
+// file.
+
+// This file is intended for desktop OpenGL version 2.1 or greater.
+
+#version 120
+
+#ifndef lowp
+    #define lowp
+#endif
+
+#ifndef mediump
+    #define mediump
+#endif
+
+#ifndef highp
+    #define highp mediump
+#endif
diff --git a/src/scenegraph/shaders/header_desktop_core.glsl b/src/scenegraph/shaders/header_desktop_core.glsl
new file mode 100644 (file)
index 0000000..fbec34d
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// This file contains common directives needed for the shaders to work.
+// It is included as the very first bit in the shader.
+// Important: If a specific GLSL version is needed, it should be set in this
+// file.
+
+// This file is intended for desktop OpenGL version 4.5 or greater.
+
+#version 450
+
+#define CORE_PROFILE
diff --git a/src/scenegraph/shaders/header_es.glsl b/src/scenegraph/shaders/header_es.glsl
new file mode 100644 (file)
index 0000000..6a58d4c
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// This file contains common directives needed for the shaders to work.
+// It is included as the very first bit in the shader.
+// Important: If a specific GLSL version is needed, it should be set in this
+// file.
+
+// This file is intended for OpenGLES version 2.0 or greater.
+
+#version 100
+#extension GL_OES_standard_derivatives : enable
+
diff --git a/src/scenegraph/shaders/sdf.glsl b/src/scenegraph/shaders/sdf.glsl
new file mode 100644 (file)
index 0000000..69402c5
--- /dev/null
@@ -0,0 +1,240 @@
+// SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+// SPDX-FileCopyrightText: 2017 Inigo Quilez
+//
+// SPDX-License-Identifier: MIT
+//
+// This file is based on
+// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+
+//if not GLES
+// include "desktop_header.glsl"
+//else
+// include "es_header.glsl"
+
+// A maximum point count to be used for sdf_polygon input arrays.
+// Unfortunately even function inputs require a fixed size at declaration time
+// for arrays, unless we were to use OpenGL 4.5.
+// Since the polygon is most likely to be defined in a uniform, this should be
+// at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2).
+#define SDF_POLYGON_MAX_POINT_COUNT 400
+
+/*********************************
+    Shapes
+*********************************/
+
+// Distance field for a circle.
+//
+// \param point A point on the distance field.
+// \param radius The radius of the circle.
+//
+// \return The signed distance from point to the circle. If negative, point is
+//         inside the circle.
+lowp float sdf_circle(in lowp vec2 point, in lowp float radius)
+{
+    return length(point) - radius;
+}
+
+// Distance field for a triangle.
+//
+// \param point A point on the distance field.
+// \param p0 The first vertex of the triangle.
+// \param p0 The second vertex of the triangle.
+// \param p0 The third vertex of the triangle.
+//
+// \note The ordering of the three vertices does not matter.
+//
+// \return The signed distance from point to triangle. If negative, point is
+//         inside the triangle.
+lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2)
+{
+    lowp vec2 e0 = p1 - p0;
+    lowp vec2 e1 = p2 - p1;
+    lowp vec2 e2 = p0 - p2;
+
+    lowp vec2 v0 = point - p0;
+    lowp vec2 v1 = point - p1;
+    lowp vec2 v2 = point - p2;
+
+    lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 );
+    lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 );
+    lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 );
+
+    lowp float s = sign( e0.x*e2.y - e0.y*e2.x );
+    lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
+                          vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
+                          vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
+
+    return -sqrt(d.x)*sign(d.y);
+}
+
+// Distance field for a rectangle.
+//
+// \param point A point on the distance field.
+// \param rect A vec2 with the size of the rectangle.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect)
+{
+    lowp vec2 d = abs(point) - rect;
+    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Distance field for a rectangle with rounded corners.
+//
+// \param point The point to calculate the distance of.
+// \param rect The rectangle to calculate the distance of.
+// \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius)
+{
+    radius.xy = (point.x > 0.0) ? radius.xy : radius.zw;
+    radius.x = (point.y > 0.0) ? radius.x : radius.y;
+    lowp vec2 d = abs(point) - rect + radius.x;
+    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x;
+}
+
+/*********************
+    Operators
+*********************/
+
+// Convert a distance field to an annular (hollow) distance field.
+//
+// \param sdf The result of an sdf shape to convert.
+// \param thickness The thickness of the resulting shape.
+//
+// \return The value of sdf modified to an annular shape.
+lowp float sdf_annular(in lowp float sdf, in lowp float thickness)
+{
+    return abs(sdf) - thickness;
+}
+
+// Union two sdf shapes together.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and
+//         sdf2.
+lowp float sdf_union(in lowp float sdf1, in lowp float sdf2)
+{
+    return min(sdf1, sdf2);
+}
+
+// Subtract two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return sdf1 with sdf2 subtracted from it.
+lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, -sdf2);
+}
+
+// Intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The intersection between sdf1 and sdf2, that is, the area where both
+//         sdf1 and sdf2 provide the same distance value.
+lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, sdf2);
+}
+
+// Smoothly intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+// \param smoothing The amount of smoothing to apply.
+//
+// \return A smoothed version of the intersect operation.
+lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing)
+{
+    lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0);
+    return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h);
+}
+
+// Round an sdf shape.
+//
+// \param sdf The sdf shape to round.
+// \param amount The amount of rounding to apply.
+//
+// \return The rounded shape of sdf.
+//         Note that rounding happens by basically selecting an isoline of sdf,
+//         therefore, the resulting shape may be larger than the input shape.
+lowp float sdf_round(in lowp float sdf, in lowp float amount)
+{
+    return sdf - amount;
+}
+
+// Convert an sdf shape to an outline of its shape.
+//
+// \param sdf The sdf shape to turn into an outline.
+//
+// \return The outline of sdf.
+lowp float sdf_outline(in lowp float sdf)
+{
+    return abs(sdf);
+}
+
+/********************
+    Convenience
+********************/
+
+// A constant to represent a "null" value of an sdf.
+//
+// Since 0 is a point exactly on the outline of an sdf shape, and negative
+// values are inside the shape, this uses a very large positive constant to
+// indicate a value that is really far away from the actual sdf shape.
+const lowp float sdf_null = 99999.0;
+
+// A constant for a default level of smoothing when rendering an sdf.
+//
+// This
+const lowp float sdf_default_smoothing = 0.625;
+
+// Render an sdf shape alpha-blended onto an existing color.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// blending amount and a smoothing amount.
+//
+// \param alpha The alpha to use for blending.
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing)
+{
+   lowp float g = fwidth(sdf);
+   return mix(sourceColor, sdfColor, alpha * (1.0 - smoothstep(-smoothing * g, smoothing * g, sdf)));
+}
+
+// Render an sdf shape.
+//
+// This will render the sdf shape on top of whatever source color is input,
+// making sure to apply smoothing if desired.
+//
+// \param sdf The sdf shape to render.
+// \param sourceColor The source color to render on top of.
+// \param sdfColor The color to use for rendering the sdf shape.
+//
+// \return sourceColor with the sdf shape rendered on top.
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing);
+}
+
+// Render an sdf shape.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// smoothing amount.
+//
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing);
+}
diff --git a/src/scenegraph/shaders/sdf_lowpower.glsl b/src/scenegraph/shaders/sdf_lowpower.glsl
new file mode 100644 (file)
index 0000000..8cdc364
--- /dev/null
@@ -0,0 +1,240 @@
+// SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+// SPDX-FileCopyrightText: 2017 Inigo Quilez
+//
+// SPDX-License-Identifier: MIT
+//
+// This file is based on
+// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+
+//if not GLES
+// include "desktop_header.glsl"
+//else
+// include "es_header.glsl"
+
+// A maximum point count to be used for sdf_polygon input arrays.
+// Unfortunately even function inputs require a fixed size at declaration time
+// for arrays, unless we were to use OpenGL 4.5.
+// Since the polygon is most likely to be defined in a uniform, this should be
+// at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2).
+#define SDF_POLYGON_MAX_POINT_COUNT 400
+
+/*********************************
+    Shapes
+*********************************/
+
+// Distance field for a circle.
+//
+// \param point A point on the distance field.
+// \param radius The radius of the circle.
+//
+// \return The signed distance from point to the circle. If negative, point is
+//         inside the circle.
+lowp float sdf_circle(in lowp vec2 point, in lowp float radius)
+{
+    return length(point) - radius;
+}
+
+// Distance field for a triangle.
+//
+// \param point A point on the distance field.
+// \param p0 The first vertex of the triangle.
+// \param p0 The second vertex of the triangle.
+// \param p0 The third vertex of the triangle.
+//
+// \note The ordering of the three vertices does not matter.
+//
+// \return The signed distance from point to triangle. If negative, point is
+//         inside the triangle.
+lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2)
+{
+    lowp vec2 e0 = p1 - p0;
+    lowp vec2 e1 = p2 - p1;
+    lowp vec2 e2 = p0 - p2;
+
+    lowp vec2 v0 = point - p0;
+    lowp vec2 v1 = point - p1;
+    lowp vec2 v2 = point - p2;
+
+    lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 );
+    lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 );
+    lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 );
+
+    lowp float s = sign( e0.x*e2.y - e0.y*e2.x );
+    lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
+                          vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
+                          vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
+
+    return -sqrt(d.x)*sign(d.y);
+}
+
+// Distance field for a rectangle.
+//
+// \param point A point on the distance field.
+// \param rect A vec2 with the size of the rectangle.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect)
+{
+    lowp vec2 d = abs(point) - rect;
+    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Distance field for a rectangle with rounded corners.
+//
+// \param point The point to calculate the distance of.
+// \param rect The rectangle to calculate the distance of.
+// \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius)
+{
+    radius.xy = (point.x > 0.0) ? radius.xy : radius.zw;
+    radius.x = (point.y > 0.0) ? radius.x : radius.y;
+    lowp vec2 d = abs(point) - rect + radius.x;
+    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x;
+}
+
+/*********************
+    Operators
+*********************/
+
+// Convert a distance field to an annular (hollow) distance field.
+//
+// \param sdf The result of an sdf shape to convert.
+// \param thickness The thickness of the resulting shape.
+//
+// \return The value of sdf modified to an annular shape.
+lowp float sdf_annular(in lowp float sdf, in lowp float thickness)
+{
+    return abs(sdf) - thickness;
+}
+
+// Union two sdf shapes together.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and
+//         sdf2.
+lowp float sdf_union(in lowp float sdf1, in lowp float sdf2)
+{
+    return min(sdf1, sdf2);
+}
+
+// Subtract two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return sdf1 with sdf2 subtracted from it.
+lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, -sdf2);
+}
+
+// Intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The intersection between sdf1 and sdf2, that is, the area where both
+//         sdf1 and sdf2 provide the same distance value.
+lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, sdf2);
+}
+
+// Smoothly intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+// \param smoothing The amount of smoothing to apply.
+//
+// \return A smoothed version of the intersect operation.
+lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing)
+{
+    lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0);
+    return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h);
+}
+
+// Round an sdf shape.
+//
+// \param sdf The sdf shape to round.
+// \param amount The amount of rounding to apply.
+//
+// \return The rounded shape of sdf.
+//         Note that rounding happens by basically selecting an isoline of sdf,
+//         therefore, the resulting shape may be larger than the input shape.
+lowp float sdf_round(in lowp float sdf, in lowp float amount)
+{
+    return sdf - amount;
+}
+
+// Convert an sdf shape to an outline of its shape.
+//
+// \param sdf The sdf shape to turn into an outline.
+//
+// \return The outline of sdf.
+lowp float sdf_outline(in lowp float sdf)
+{
+    return abs(sdf);
+}
+
+/********************
+    Convenience
+********************/
+
+// A constant to represent a "null" value of an sdf.
+//
+// Since 0 is a point exactly on the outline of an sdf shape, and negative
+// values are inside the shape, this uses a very large positive constant to
+// indicate a value that is really far away from the actual sdf shape.
+const lowp float sdf_null = 99999.0;
+
+// A constant for a default level of smoothing when rendering an sdf.
+//
+// This
+const lowp float sdf_default_smoothing = 0.625;
+
+// Render an sdf shape alpha-blended onto an existing color.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// blending amount and a smoothing amount.
+//
+// \param alpha The alpha to use for blending.
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing)
+{
+    lowp float g = smoothing * fwidth(sdf);
+    return mix(sourceColor, sdfColor, alpha * (1.0 - clamp(sdf / g, 0.0, 1.0)));
+}
+
+// Render an sdf shape.
+//
+// This will render the sdf shape on top of whatever source color is input,
+// making sure to apply smoothing if desired.
+//
+// \param sdf The sdf shape to render.
+// \param sourceColor The source color to render on top of.
+// \param sdfColor The color to use for rendering the sdf shape.
+//
+// \return sourceColor with the sdf shape rendered on top.
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing);
+}
+
+// Render an sdf shape.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// smoothing amount.
+//
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing);
+}
diff --git a/src/scenegraph/shaders/shaders.qrc b/src/scenegraph/shaders/shaders.qrc
new file mode 100644 (file)
index 0000000..d2ad90f
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+    <qresource prefix="/org/kde/kirigami/shaders">
+        <file>header_es.glsl</file>
+        <file>header_desktop.glsl</file>
+        <file>header_desktop_core.glsl</file>
+        <file>sdf.glsl</file>
+        <file>sdf_lowpower.glsl</file>
+        <file alias="sdf_core.glsl">sdf.glsl</file>
+        <file>shadowedrectangle.vert</file>
+        <file alias="shadowedrectangle_core.vert">shadowedrectangle.vert</file>
+        <file>shadowedrectangle.frag</file>
+        <file>shadowedrectangle_lowpower.frag</file>
+        <file alias="shadowedrectangle_core.frag">shadowedrectangle.frag</file>
+        <file>shadowedborderrectangle.frag</file>
+        <file>shadowedborderrectangle_lowpower.frag</file>
+        <file alias="shadowedborderrectangle_core.frag">shadowedborderrectangle.frag</file>
+        <file>shadowedtexture.frag</file>
+        <file>shadowedtexture_lowpower.frag</file>
+        <file alias="shadowedtexture_core.frag">shadowedtexture.frag</file>
+        <file>shadowedbordertexture.frag</file>
+        <file>shadowedbordertexture_lowpower.frag</file>
+        <file alias="shadowedbordertexture_core.frag">shadowedbordertexture.frag</file>
+    </qresource>
+</RCC>
+
diff --git a/src/scenegraph/shaders/shadowedborderrectangle.frag b/src/scenegraph/shaders/shadowedborderrectangle.frag
new file mode 100644 (file)
index 0000000..5a68375
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform lowp float borderWidth;
+uniform lowp vec4 borderColor;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#endif
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = radius + size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow));
+
+    // Scale corrected corner radius
+    lowp vec4 corner_radius = radius * inverse_scale;
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius);
+
+    col = sdf_render(outer_rect, col, borderColor);
+
+    // The inner rectangle distance field is the outer reduced by twice the border size.
+    lowp float inner_rect = outer_rect + (borderWidth * inverse_scale) * 2.0;
+
+    // Finally, render the inner rectangle.
+    col = sdf_render(inner_rect, col, color);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedborderrectangle_lowpower.frag b/src/scenegraph/shaders/shadowedborderrectangle_lowpower.frag
new file mode 100644 (file)
index 0000000..5868e97
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This is a version of shadowedborderrectangle.frag for extremely low powered
+// hardware (PinePhone). It does not draw a shadow and also eliminates alpha
+// blending.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform lowp float borderWidth;
+uniform lowp vec4 borderColor;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#define texture texture2D
+#endif
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, aspect, radius);
+
+    col = sdf_render(outer_rect, col, borderColor);
+
+    // The inner distance field is the outer reduced by border width.
+    lowp float inner_rect = outer_rect + borderWidth * 2.0;
+
+    // Render it.
+    col = sdf_render(inner_rect, col, color);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedbordertexture.frag b/src/scenegraph/shaders/shadowedbordertexture.frag
new file mode 100644 (file)
index 0000000..7ea011d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform lowp float borderWidth;
+uniform lowp vec4 borderColor;
+uniform sampler2D textureSource;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#define texture texture2D
+#endif
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = radius + size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow));
+
+    // Scale corrected corner radius
+    lowp vec4 corner_radius = radius * inverse_scale;
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius);
+
+    col = sdf_render(outer_rect, col, borderColor);
+
+    // The inner rectangle distance field is the outer reduced by twice the border width.
+    lowp float inner_rect = outer_rect + (borderWidth * inverse_scale) * 2.0;
+
+    // Render the inner rectangle.
+    col = sdf_render(inner_rect, col, color);
+
+    // Sample the texture, then blend it on top of the background color.
+    lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale);
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedbordertexture_lowpower.frag b/src/scenegraph/shaders/shadowedbordertexture_lowpower.frag
new file mode 100644 (file)
index 0000000..a2f5fc9
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform lowp float borderWidth;
+uniform lowp vec4 borderColor;
+uniform sampler2D textureSource;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#define texture texture2D
+#endif
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the outer rectangle distance field.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, aspect, radius);
+
+    // Render it
+    col = sdf_render(outer_rect, col, borderColor);
+
+    // Inner rectangle distance field equals outer reduced by twice the border width
+    lowp float inner_rect = outer_rect + borderWidth * 2.0;
+
+    // Render it so we have a background for the image.
+    col = sdf_render(inner_rect, col, color);
+
+    // Sample the texture, then render it, blending with the background color.
+    lowp vec2 texture_uv = ((uv / aspect) + 1.0) / 2.0;
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedrectangle.frag b/src/scenegraph/shaders/shadowedrectangle.frag
new file mode 100644 (file)
index 0000000..cbbd73d
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#endif
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = radius + size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow));
+
+    // Calculate the main rectangle distance field and render it.
+    lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, radius * inverse_scale);
+
+    col = sdf_render(rect, col, color);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedrectangle.vert b/src/scenegraph/shaders/shadowedrectangle.vert
new file mode 100644 (file)
index 0000000..f27244c
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+uniform highp mat4 matrix;
+uniform lowp vec2 aspect;
+
+#ifdef CORE_PROFILE
+in highp vec4 in_vertex;
+in mediump vec2 in_uv;
+out mediump vec2 uv;
+#else
+attribute highp vec4 in_vertex;
+attribute mediump vec2 in_uv;
+varying mediump vec2 uv;
+#endif
+
+void main() {
+    uv = (-1.0 + 2.0 * in_uv) * aspect;
+    gl_Position = matrix * in_vertex;
+}
diff --git a/src/scenegraph/shaders/shadowedrectangle_lowpower.frag b/src/scenegraph/shaders/shadowedrectangle_lowpower.frag
new file mode 100644 (file)
index 0000000..92d777d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This is a version of shadowedrectangle.frag meant for very low power hardware
+// (PinePhone). It does not render a shadow and does not do alpha blending.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#endif
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the main rectangle distance field.
+    lowp float rect = sdf_rounded_rectangle(uv, aspect, radius);
+
+    // Render it.
+    col = sdf_render(rect, col, color);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedtexture.frag b/src/scenegraph/shaders/shadowedtexture.frag
new file mode 100644 (file)
index 0000000..0bb0112
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a texture on top of a rectangle with rounded corners and
+// a shadow below it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform sampler2D textureSource;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#define texture texture2D
+#endif
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = radius + size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow));
+
+    // Calculate the main rectangle distance field and render it.
+    lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, radius * inverse_scale);
+
+    col = sdf_render(rect, col, color);
+
+    // Sample the texture, then blend it on top of the background color.
+    lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale);
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders/shadowedtexture_lowpower.frag b/src/scenegraph/shaders/shadowedtexture_lowpower.frag
new file mode 100644 (file)
index 0000000..d77c121
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a texture on top of a rectangle with rounded corners and
+// a shadow below it.
+
+uniform lowp float opacity;
+uniform lowp float size;
+uniform lowp vec4 radius;
+uniform lowp vec4 color;
+uniform lowp vec4 shadowColor;
+uniform lowp vec2 offset;
+uniform lowp vec2 aspect;
+uniform sampler2D textureSource;
+
+#ifdef CORE_PROFILE
+in lowp vec2 uv;
+out lowp vec4 out_color;
+#else
+varying lowp vec2 uv;
+#define out_color gl_FragColor
+#define texture texture2D
+#endif
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the main rectangle distance field.
+    lowp float rect = sdf_rounded_rectangle(uv, aspect, radius);
+
+    // Render it, so we have a background for the image.
+    col = sdf_render(rect, col, color);
+
+    // Sample the texture, then render it, blending it with the background.
+    lowp vec2 texture_uv = ((uv / aspect) + 1.0) / 2.0;
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * opacity;
+}
diff --git a/src/scenegraph/shaders6/CMakeLists.txt b/src/scenegraph/shaders6/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8128965
--- /dev/null
@@ -0,0 +1,21 @@
+# SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
+# SPDX-License-Identifier: BSD-2-Clause
+
+if (QT_MAJOR_VERSION EQUAL "6")
+    qt6_add_shaders(KirigamiPlugin "shaders6"
+        BATCHABLE
+        PRECOMPILE
+        OPTIMIZED
+        PREFIX "/org/kde/kirigami/shaders/"
+        FILES
+            shadowedrectangle.vert
+            shadowedrectangle.frag
+            shadowedrectangle_lowpower.frag
+            shadowedborderrectangle.frag
+            shadowedborderrectangle_lowpower.frag
+            shadowedtexture.frag
+            shadowedtexture_lowpower.frag
+            shadowedbordertexture.frag
+            shadowedbordertexture_lowpower.frag
+    )
+endif()
diff --git a/src/scenegraph/shaders6/sdf.glsl b/src/scenegraph/shaders6/sdf.glsl
new file mode 100644 (file)
index 0000000..69402c5
--- /dev/null
@@ -0,0 +1,240 @@
+// SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+// SPDX-FileCopyrightText: 2017 Inigo Quilez
+//
+// SPDX-License-Identifier: MIT
+//
+// This file is based on
+// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+
+//if not GLES
+// include "desktop_header.glsl"
+//else
+// include "es_header.glsl"
+
+// A maximum point count to be used for sdf_polygon input arrays.
+// Unfortunately even function inputs require a fixed size at declaration time
+// for arrays, unless we were to use OpenGL 4.5.
+// Since the polygon is most likely to be defined in a uniform, this should be
+// at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2).
+#define SDF_POLYGON_MAX_POINT_COUNT 400
+
+/*********************************
+    Shapes
+*********************************/
+
+// Distance field for a circle.
+//
+// \param point A point on the distance field.
+// \param radius The radius of the circle.
+//
+// \return The signed distance from point to the circle. If negative, point is
+//         inside the circle.
+lowp float sdf_circle(in lowp vec2 point, in lowp float radius)
+{
+    return length(point) - radius;
+}
+
+// Distance field for a triangle.
+//
+// \param point A point on the distance field.
+// \param p0 The first vertex of the triangle.
+// \param p0 The second vertex of the triangle.
+// \param p0 The third vertex of the triangle.
+//
+// \note The ordering of the three vertices does not matter.
+//
+// \return The signed distance from point to triangle. If negative, point is
+//         inside the triangle.
+lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2)
+{
+    lowp vec2 e0 = p1 - p0;
+    lowp vec2 e1 = p2 - p1;
+    lowp vec2 e2 = p0 - p2;
+
+    lowp vec2 v0 = point - p0;
+    lowp vec2 v1 = point - p1;
+    lowp vec2 v2 = point - p2;
+
+    lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 );
+    lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 );
+    lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 );
+
+    lowp float s = sign( e0.x*e2.y - e0.y*e2.x );
+    lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
+                          vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
+                          vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
+
+    return -sqrt(d.x)*sign(d.y);
+}
+
+// Distance field for a rectangle.
+//
+// \param point A point on the distance field.
+// \param rect A vec2 with the size of the rectangle.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect)
+{
+    lowp vec2 d = abs(point) - rect;
+    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Distance field for a rectangle with rounded corners.
+//
+// \param point The point to calculate the distance of.
+// \param rect The rectangle to calculate the distance of.
+// \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius)
+{
+    radius.xy = (point.x > 0.0) ? radius.xy : radius.zw;
+    radius.x = (point.y > 0.0) ? radius.x : radius.y;
+    lowp vec2 d = abs(point) - rect + radius.x;
+    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x;
+}
+
+/*********************
+    Operators
+*********************/
+
+// Convert a distance field to an annular (hollow) distance field.
+//
+// \param sdf The result of an sdf shape to convert.
+// \param thickness The thickness of the resulting shape.
+//
+// \return The value of sdf modified to an annular shape.
+lowp float sdf_annular(in lowp float sdf, in lowp float thickness)
+{
+    return abs(sdf) - thickness;
+}
+
+// Union two sdf shapes together.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and
+//         sdf2.
+lowp float sdf_union(in lowp float sdf1, in lowp float sdf2)
+{
+    return min(sdf1, sdf2);
+}
+
+// Subtract two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return sdf1 with sdf2 subtracted from it.
+lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, -sdf2);
+}
+
+// Intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The intersection between sdf1 and sdf2, that is, the area where both
+//         sdf1 and sdf2 provide the same distance value.
+lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, sdf2);
+}
+
+// Smoothly intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+// \param smoothing The amount of smoothing to apply.
+//
+// \return A smoothed version of the intersect operation.
+lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing)
+{
+    lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0);
+    return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h);
+}
+
+// Round an sdf shape.
+//
+// \param sdf The sdf shape to round.
+// \param amount The amount of rounding to apply.
+//
+// \return The rounded shape of sdf.
+//         Note that rounding happens by basically selecting an isoline of sdf,
+//         therefore, the resulting shape may be larger than the input shape.
+lowp float sdf_round(in lowp float sdf, in lowp float amount)
+{
+    return sdf - amount;
+}
+
+// Convert an sdf shape to an outline of its shape.
+//
+// \param sdf The sdf shape to turn into an outline.
+//
+// \return The outline of sdf.
+lowp float sdf_outline(in lowp float sdf)
+{
+    return abs(sdf);
+}
+
+/********************
+    Convenience
+********************/
+
+// A constant to represent a "null" value of an sdf.
+//
+// Since 0 is a point exactly on the outline of an sdf shape, and negative
+// values are inside the shape, this uses a very large positive constant to
+// indicate a value that is really far away from the actual sdf shape.
+const lowp float sdf_null = 99999.0;
+
+// A constant for a default level of smoothing when rendering an sdf.
+//
+// This
+const lowp float sdf_default_smoothing = 0.625;
+
+// Render an sdf shape alpha-blended onto an existing color.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// blending amount and a smoothing amount.
+//
+// \param alpha The alpha to use for blending.
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing)
+{
+   lowp float g = fwidth(sdf);
+   return mix(sourceColor, sdfColor, alpha * (1.0 - smoothstep(-smoothing * g, smoothing * g, sdf)));
+}
+
+// Render an sdf shape.
+//
+// This will render the sdf shape on top of whatever source color is input,
+// making sure to apply smoothing if desired.
+//
+// \param sdf The sdf shape to render.
+// \param sourceColor The source color to render on top of.
+// \param sdfColor The color to use for rendering the sdf shape.
+//
+// \return sourceColor with the sdf shape rendered on top.
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing);
+}
+
+// Render an sdf shape.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// smoothing amount.
+//
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing);
+}
diff --git a/src/scenegraph/shaders6/sdf_lowpower.glsl b/src/scenegraph/shaders6/sdf_lowpower.glsl
new file mode 100644 (file)
index 0000000..8cdc364
--- /dev/null
@@ -0,0 +1,240 @@
+// SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+// SPDX-FileCopyrightText: 2017 Inigo Quilez
+//
+// SPDX-License-Identifier: MIT
+//
+// This file is based on
+// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+
+//if not GLES
+// include "desktop_header.glsl"
+//else
+// include "es_header.glsl"
+
+// A maximum point count to be used for sdf_polygon input arrays.
+// Unfortunately even function inputs require a fixed size at declaration time
+// for arrays, unless we were to use OpenGL 4.5.
+// Since the polygon is most likely to be defined in a uniform, this should be
+// at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2).
+#define SDF_POLYGON_MAX_POINT_COUNT 400
+
+/*********************************
+    Shapes
+*********************************/
+
+// Distance field for a circle.
+//
+// \param point A point on the distance field.
+// \param radius The radius of the circle.
+//
+// \return The signed distance from point to the circle. If negative, point is
+//         inside the circle.
+lowp float sdf_circle(in lowp vec2 point, in lowp float radius)
+{
+    return length(point) - radius;
+}
+
+// Distance field for a triangle.
+//
+// \param point A point on the distance field.
+// \param p0 The first vertex of the triangle.
+// \param p0 The second vertex of the triangle.
+// \param p0 The third vertex of the triangle.
+//
+// \note The ordering of the three vertices does not matter.
+//
+// \return The signed distance from point to triangle. If negative, point is
+//         inside the triangle.
+lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2)
+{
+    lowp vec2 e0 = p1 - p0;
+    lowp vec2 e1 = p2 - p1;
+    lowp vec2 e2 = p0 - p2;
+
+    lowp vec2 v0 = point - p0;
+    lowp vec2 v1 = point - p1;
+    lowp vec2 v2 = point - p2;
+
+    lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 );
+    lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 );
+    lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 );
+
+    lowp float s = sign( e0.x*e2.y - e0.y*e2.x );
+    lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
+                          vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
+                          vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
+
+    return -sqrt(d.x)*sign(d.y);
+}
+
+// Distance field for a rectangle.
+//
+// \param point A point on the distance field.
+// \param rect A vec2 with the size of the rectangle.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect)
+{
+    lowp vec2 d = abs(point) - rect;
+    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Distance field for a rectangle with rounded corners.
+//
+// \param point The point to calculate the distance of.
+// \param rect The rectangle to calculate the distance of.
+// \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left.
+//
+// \return The signed distance from point to rectangle. If negative, point is
+//         inside the rectangle.
+lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius)
+{
+    radius.xy = (point.x > 0.0) ? radius.xy : radius.zw;
+    radius.x = (point.y > 0.0) ? radius.x : radius.y;
+    lowp vec2 d = abs(point) - rect + radius.x;
+    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x;
+}
+
+/*********************
+    Operators
+*********************/
+
+// Convert a distance field to an annular (hollow) distance field.
+//
+// \param sdf The result of an sdf shape to convert.
+// \param thickness The thickness of the resulting shape.
+//
+// \return The value of sdf modified to an annular shape.
+lowp float sdf_annular(in lowp float sdf, in lowp float thickness)
+{
+    return abs(sdf) - thickness;
+}
+
+// Union two sdf shapes together.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and
+//         sdf2.
+lowp float sdf_union(in lowp float sdf1, in lowp float sdf2)
+{
+    return min(sdf1, sdf2);
+}
+
+// Subtract two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return sdf1 with sdf2 subtracted from it.
+lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, -sdf2);
+}
+
+// Intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+//
+// \return The intersection between sdf1 and sdf2, that is, the area where both
+//         sdf1 and sdf2 provide the same distance value.
+lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2)
+{
+    return max(sdf1, sdf2);
+}
+
+// Smoothly intersect two sdf shapes.
+//
+// \param sdf1 The first sdf shape.
+// \param sdf2 The second sdf shape.
+// \param smoothing The amount of smoothing to apply.
+//
+// \return A smoothed version of the intersect operation.
+lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing)
+{
+    lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0);
+    return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h);
+}
+
+// Round an sdf shape.
+//
+// \param sdf The sdf shape to round.
+// \param amount The amount of rounding to apply.
+//
+// \return The rounded shape of sdf.
+//         Note that rounding happens by basically selecting an isoline of sdf,
+//         therefore, the resulting shape may be larger than the input shape.
+lowp float sdf_round(in lowp float sdf, in lowp float amount)
+{
+    return sdf - amount;
+}
+
+// Convert an sdf shape to an outline of its shape.
+//
+// \param sdf The sdf shape to turn into an outline.
+//
+// \return The outline of sdf.
+lowp float sdf_outline(in lowp float sdf)
+{
+    return abs(sdf);
+}
+
+/********************
+    Convenience
+********************/
+
+// A constant to represent a "null" value of an sdf.
+//
+// Since 0 is a point exactly on the outline of an sdf shape, and negative
+// values are inside the shape, this uses a very large positive constant to
+// indicate a value that is really far away from the actual sdf shape.
+const lowp float sdf_null = 99999.0;
+
+// A constant for a default level of smoothing when rendering an sdf.
+//
+// This
+const lowp float sdf_default_smoothing = 0.625;
+
+// Render an sdf shape alpha-blended onto an existing color.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// blending amount and a smoothing amount.
+//
+// \param alpha The alpha to use for blending.
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing)
+{
+    lowp float g = smoothing * fwidth(sdf);
+    return mix(sourceColor, sdfColor, alpha * (1.0 - clamp(sdf / g, 0.0, 1.0)));
+}
+
+// Render an sdf shape.
+//
+// This will render the sdf shape on top of whatever source color is input,
+// making sure to apply smoothing if desired.
+//
+// \param sdf The sdf shape to render.
+// \param sourceColor The source color to render on top of.
+// \param sdfColor The color to use for rendering the sdf shape.
+//
+// \return sourceColor with the sdf shape rendered on top.
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing);
+}
+
+// Render an sdf shape.
+//
+// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a
+// smoothing amount.
+//
+// \param smoothing The amount of smoothing to apply to the sdf.
+//
+lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing)
+{
+    return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing);
+}
diff --git a/src/scenegraph/shaders6/shadowedborderrectangle.frag b/src/scenegraph/shaders6/shadowedborderrectangle.frag
new file mode 100644 (file)
index 0000000..5bb51be
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+#include "uniforms.glsl"
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow));
+
+    // Scale corrected corner radius
+    lowp vec4 corner_radius = ubuf.radius * inverse_scale;
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, corner_radius);
+
+    col = sdf_render(outer_rect, col, ubuf.borderColor);
+
+    // The inner rectangle distance field is the outer reduced by twice the border size.
+    lowp float inner_rect = outer_rect + (ubuf.borderWidth * inverse_scale) * 2.0;
+
+    // Finally, render the inner rectangle.
+    col = sdf_render(inner_rect, col, ubuf.color);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedborderrectangle_lowpower.frag b/src/scenegraph/shaders6/shadowedborderrectangle_lowpower.frag
new file mode 100644 (file)
index 0000000..abf1c15
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf_lowpower.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This is a version of shadowedborderrectangle.frag for extremely low powered
+// hardware (PinePhone). It does not draw a shadow and also eliminates alpha
+// blending.
+
+#include "uniforms.glsl"
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius);
+
+    col = sdf_render(outer_rect, col, ubuf.borderColor);
+
+    // The inner distance field is the outer reduced by border width.
+    lowp float inner_rect = outer_rect + ubuf.borderWidth * 2.0;
+
+    // Render it.
+    col = sdf_render(inner_rect, col, ubuf.color);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedbordertexture.frag b/src/scenegraph/shaders6/shadowedbordertexture.frag
new file mode 100644 (file)
index 0000000..da1a19c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+#include "uniforms.glsl"
+layout(binding = 1) uniform sampler2D textureSource;
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow));
+
+    // Scale corrected corner radius
+    lowp vec4 corner_radius = ubuf.radius * inverse_scale;
+
+    // Calculate the outer rectangle distance field and render it.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, corner_radius);
+
+    col = sdf_render(outer_rect, col, ubuf.borderColor);
+
+    // The inner rectangle distance field is the outer reduced by twice the border width.
+    lowp float inner_rect = outer_rect + (ubuf.borderWidth * inverse_scale) * 2.0;
+
+    // Render the inner rectangle.
+    col = sdf_render(inner_rect, col, ubuf.color);
+
+    // Sample the texture, then blend it on top of the background color.
+    lowp vec2 texture_uv = ((uv / ubuf.aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale);
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedbordertexture_lowpower.frag b/src/scenegraph/shaders6/shadowedbordertexture_lowpower.frag
new file mode 100644 (file)
index 0000000..ca79d7b
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf_lowpower.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+// In addition it renders a border around it.
+
+#include "uniforms.glsl"
+layout(binding = 1) uniform sampler2D textureSource;
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the outer rectangle distance field.
+    lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius);
+
+    // Render it
+    col = sdf_render(outer_rect, col, ubuf.borderColor);
+
+    // Inner rectangle distance field equals outer reduced by twice the border width
+    lowp float inner_rect = outer_rect + ubuf.borderWidth * 2.0;
+
+    // Render it so we have a background for the image.
+    col = sdf_render(inner_rect, col, ubuf.color);
+
+    // Sample the texture, then render it, blending with the background color.
+    lowp vec2 texture_uv = ((uv / ubuf.aspect) + 1.0) / 2.0;
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedrectangle.frag b/src/scenegraph/shaders6/shadowedrectangle.frag
new file mode 100644 (file)
index 0000000..6705d45
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a rectangle with rounded corners and a shadow below it.
+
+#include "uniforms.glsl"
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow));
+
+    // Calculate the main rectangle distance field and render it.
+    lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, ubuf.radius * inverse_scale);
+
+    col = sdf_render(rect, col, ubuf.color);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedrectangle.vert b/src/scenegraph/shaders6/shadowedrectangle.vert
new file mode 100644 (file)
index 0000000..312ed1b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "uniforms.glsl"
+
+layout(location = 0) in highp vec4 in_vertex;
+layout(location = 1) in mediump vec2 in_uv;
+
+layout(location = 0) out mediump vec2 uv;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main() {
+    uv = (-1.0 + 2.0 * in_uv) * ubuf.aspect;
+    gl_Position = ubuf.matrix * in_vertex;
+}
diff --git a/src/scenegraph/shaders6/shadowedrectangle_lowpower.frag b/src/scenegraph/shaders6/shadowedrectangle_lowpower.frag
new file mode 100644 (file)
index 0000000..8a88691
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+// See sdf.glsl for the SDF related functions.
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf_lowpower.glsl"
+
+// This is a version of shadowedrectangle.frag meant for very low power hardware
+// (PinePhone). It does not render a shadow and does not do alpha blending.
+
+#include "uniforms.glsl"
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the main rectangle distance field.
+    lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius);
+
+    // Render it.
+    col = sdf_render(rect, col, ubuf.color);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedtexture.frag b/src/scenegraph/shaders6/shadowedtexture.frag
new file mode 100644 (file)
index 0000000..508e3f9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a texture on top of a rectangle with rounded corners and
+// a shadow below it.
+
+#include "uniforms.glsl"
+layout(binding = 1) uniform sampler2D textureSource;
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+const lowp float minimum_shadow_radius = 0.05;
+
+void main()
+{
+    // Scaling factor that is the inverse of the amount of scaling applied to the geometry.
+    lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0);
+
+    // Correction factor to round the corners of a larger shadow.
+    // We want to account for size in regards to shadow radius, so that a larger shadow is
+    // more rounded, but only if we are not already rounding the corners due to corner radius.
+    lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius));
+    lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor;
+
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the shadow's distance field.
+    lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale);
+    // Render it, interpolating the color over the distance.
+    col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow));
+
+    // Calculate the main rectangle distance field and render it.
+    lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, ubuf.radius * inverse_scale);
+
+    col = sdf_render(rect, col, ubuf.color);
+
+    // Sample the texture, then blend it on top of the background color.
+    lowp vec2 texture_uv = ((uv / ubuf.aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale);
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/shadowedtexture_lowpower.frag b/src/scenegraph/shaders6/shadowedtexture_lowpower.frag
new file mode 100644 (file)
index 0000000..e2b95c4
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#version 440
+
+#extension GL_GOOGLE_include_directive: enable
+#include "sdf_lowpower.glsl"
+// See sdf.glsl for the SDF related functions.
+
+// This shader renders a texture on top of a rectangle with rounded corners and
+// a shadow below it.
+
+#include "uniforms.glsl"
+layout(binding = 1) uniform sampler2D textureSource;
+
+layout(location = 0) in lowp vec2 uv;
+layout(location = 0) out lowp vec4 out_color;
+
+void main()
+{
+    lowp vec4 col = vec4(0.0);
+
+    // Calculate the main rectangle distance field.
+    lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius);
+
+    // Render it, so we have a background for the image.
+    col = sdf_render(rect, col, ubuf.color);
+
+    // Sample the texture, then render it, blending it with the background.
+    lowp vec2 texture_uv = ((uv / ubuf.aspect) + 1.0) / 2.0;
+    lowp vec4 texture_color = texture(textureSource, texture_uv);
+    col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing);
+
+    out_color = col * ubuf.opacity;
+}
diff --git a/src/scenegraph/shaders6/uniforms.glsl b/src/scenegraph/shaders6/uniforms.glsl
new file mode 100644 (file)
index 0000000..0df5fb6
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+layout(std140, binding = 0) uniform buf {
+    highp mat4 matrix; // offset 0
+    lowp vec2 aspect; // offset 64
+
+    lowp float opacity; // offset 72
+    lowp float size; // offset 76
+    lowp vec4 radius; // offset 80
+    lowp vec4 color; // offset 96
+    lowp vec4 shadowColor; // offset 112
+    lowp vec2 offset; // offset 128
+
+    lowp float borderWidth; // offset 136
+    lowp vec4 borderColor; // offset 144
+} ubuf; // size 160
diff --git a/src/scenegraph/shadowedborderrectanglematerial.cpp b/src/scenegraph/shadowedborderrectanglematerial.cpp
new file mode 100644 (file)
index 0000000..f55e753
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedborderrectanglematerial.h"
+
+#include <QOpenGLContext>
+
+QSGMaterialType ShadowedBorderRectangleMaterial::staticType;
+
+ShadowedBorderRectangleMaterial::ShadowedBorderRectangleMaterial()
+{
+    setFlag(QSGMaterial::Blending, true);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QSGMaterialShader *ShadowedBorderRectangleMaterial::createShader() const
+#else
+QSGMaterialShader *ShadowedBorderRectangleMaterial::createShader(QSGRendererInterface::RenderMode) const
+#endif
+{
+    return new ShadowedBorderRectangleShader{shaderType};
+}
+
+QSGMaterialType *ShadowedBorderRectangleMaterial::type() const
+{
+    return &staticType;
+}
+
+int ShadowedBorderRectangleMaterial::compare(const QSGMaterial *other) const
+{
+    auto material = static_cast<const ShadowedBorderRectangleMaterial *>(other);
+
+    auto result = ShadowedRectangleMaterial::compare(other);
+    /* clang-format off */
+    if (result == 0
+        && material->borderColor == borderColor
+        && qFuzzyCompare(material->borderWidth, borderWidth)) { /* clang-format on */
+        return 0;
+    }
+
+    return QSGMaterial::compare(other);
+}
+
+ShadowedBorderRectangleShader::ShadowedBorderRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType)
+    : ShadowedRectangleShader(shaderType)
+{
+    setShader(shaderType, QStringLiteral("shadowedborderrectangle"));
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ShadowedBorderRectangleShader::initialize()
+{
+    ShadowedRectangleShader::initialize();
+    m_borderWidthLocation = program()->uniformLocation("borderWidth");
+    m_borderColorLocation = program()->uniformLocation("borderColor");
+}
+
+void ShadowedBorderRectangleShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    ShadowedRectangleShader::updateState(state, newMaterial, oldMaterial);
+
+    auto p = program();
+
+    if (!oldMaterial || newMaterial->compare(oldMaterial) != 0 || state.isCachedMaterialDataDirty()) {
+        auto material = static_cast<ShadowedBorderRectangleMaterial *>(newMaterial);
+        p->setUniformValue(m_borderWidthLocation, material->borderWidth);
+        p->setUniformValue(m_borderColorLocation, material->borderColor);
+    }
+}
+#else
+bool ShadowedBorderRectangleShader::updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    bool changed = ShadowedRectangleShader::updateUniformData(state, newMaterial, oldMaterial);
+    QByteArray *buf = state.uniformData();
+    Q_ASSERT(buf->size() >= 160);
+
+    if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) {
+        const auto material = static_cast<ShadowedBorderRectangleMaterial *>(newMaterial);
+        memcpy(buf->data() + 136, &material->borderWidth, 8);
+        float c[4];
+        material->borderColor.getRgbF(&c[0], &c[1], &c[2], &c[3]);
+        memcpy(buf->data() + 144, c, 16);
+        changed = true;
+    }
+
+    return changed;
+}
+#endif
diff --git a/src/scenegraph/shadowedborderrectanglematerial.h b/src/scenegraph/shadowedborderrectanglematerial.h
new file mode 100644 (file)
index 0000000..e931a06
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "shadowedrectanglematerial.h"
+
+/**
+ * A material rendering a rectangle with a shadow and a border.
+ *
+ * This material uses a distance field shader to render a rectangle with a
+ * shadow below it, optionally with rounded corners and a border.
+ */
+class ShadowedBorderRectangleMaterial : public ShadowedRectangleMaterial
+{
+public:
+    ShadowedBorderRectangleMaterial();
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QSGMaterialShader *createShader() const override;
+#else
+    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override;
+#endif
+    QSGMaterialType *type() const override;
+    int compare(const QSGMaterial *other) const override;
+
+    float borderWidth = 0.0;
+    QColor borderColor = Qt::black;
+
+    static QSGMaterialType staticType;
+};
+
+class ShadowedBorderRectangleShader : public ShadowedRectangleShader
+{
+public:
+    ShadowedBorderRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void initialize() override;
+    void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+
+private:
+    int m_borderWidthLocation = -1;
+    int m_borderColorLocation = -1;
+#else
+    bool updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#endif
+};
diff --git a/src/scenegraph/shadowedbordertexturematerial.cpp b/src/scenegraph/shadowedbordertexturematerial.cpp
new file mode 100644 (file)
index 0000000..d546a9f
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedbordertexturematerial.h"
+
+#include <QOpenGLContext>
+
+QSGMaterialType ShadowedBorderTextureMaterial::staticType;
+
+ShadowedBorderTextureMaterial::ShadowedBorderTextureMaterial()
+    : ShadowedBorderRectangleMaterial()
+{
+    setFlag(QSGMaterial::Blending, true);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QSGMaterialShader *ShadowedBorderTextureMaterial::createShader() const
+#else
+QSGMaterialShader *ShadowedBorderTextureMaterial::createShader(QSGRendererInterface::RenderMode) const
+#endif
+{
+    return new ShadowedBorderTextureShader{shaderType};
+}
+
+QSGMaterialType *ShadowedBorderTextureMaterial::type() const
+{
+    return &staticType;
+}
+
+int ShadowedBorderTextureMaterial::compare(const QSGMaterial *other) const
+{
+    auto material = static_cast<const ShadowedBorderTextureMaterial *>(other);
+
+    auto result = ShadowedBorderRectangleMaterial::compare(other);
+    if (result == 0) {
+        if (material->textureSource == textureSource) {
+            return 0;
+        } else {
+            return (material->textureSource < textureSource) ? 1 : -1;
+        }
+    }
+
+    return QSGMaterial::compare(other);
+}
+
+ShadowedBorderTextureShader::ShadowedBorderTextureShader(ShadowedRectangleMaterial::ShaderType shaderType)
+    : ShadowedBorderRectangleShader(shaderType)
+{
+    setShader(shaderType, QStringLiteral("shadowedbordertexture"));
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ShadowedBorderTextureShader::initialize()
+{
+    ShadowedBorderRectangleShader::initialize();
+    program()->setUniformValue("textureSource", 0);
+}
+
+void ShadowedBorderTextureShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    ShadowedBorderRectangleShader::updateState(state, newMaterial, oldMaterial);
+
+    auto texture = static_cast<ShadowedBorderTextureMaterial *>(newMaterial)->textureSource;
+    if (texture) {
+        texture->bind();
+    }
+}
+#else
+void ShadowedBorderTextureShader::updateSampledImage(QSGMaterialShader::RenderState &state,
+                                                     int binding,
+                                                     QSGTexture **texture,
+                                                     QSGMaterial *newMaterial,
+                                                     QSGMaterial *oldMaterial)
+{
+    Q_UNUSED(state);
+    Q_UNUSED(oldMaterial);
+    if (binding == 1) {
+        *texture = static_cast<ShadowedBorderTextureMaterial *>(newMaterial)->textureSource;
+    }
+}
+#endif
diff --git a/src/scenegraph/shadowedbordertexturematerial.h b/src/scenegraph/shadowedbordertexturematerial.h
new file mode 100644 (file)
index 0000000..9bfd825
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QSGTexture>
+
+#include "shadowedborderrectanglematerial.h"
+
+class ShadowedBorderTextureMaterial : public ShadowedBorderRectangleMaterial
+{
+public:
+    ShadowedBorderTextureMaterial();
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QSGMaterialShader *createShader() const override;
+#else
+    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override;
+#endif
+    QSGMaterialType *type() const override;
+    int compare(const QSGMaterial *other) const override;
+
+    QSGTexture *textureSource = nullptr;
+
+    static QSGMaterialType staticType;
+};
+
+class ShadowedBorderTextureShader : public ShadowedBorderRectangleShader
+{
+public:
+    ShadowedBorderTextureShader(ShadowedRectangleMaterial::ShaderType shaderType);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void initialize() override;
+    void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#else
+    void
+    updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#endif
+};
diff --git a/src/scenegraph/shadowedrectanglematerial.cpp b/src/scenegraph/shadowedrectanglematerial.cpp
new file mode 100644 (file)
index 0000000..3d20645
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedrectanglematerial.h"
+
+#include <QOpenGLContext>
+
+QSGMaterialType ShadowedRectangleMaterial::staticType;
+
+ShadowedRectangleMaterial::ShadowedRectangleMaterial()
+{
+    setFlag(QSGMaterial::Blending, true);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QSGMaterialShader *ShadowedRectangleMaterial::createShader() const
+#else
+QSGMaterialShader *ShadowedRectangleMaterial::createShader(QSGRendererInterface::RenderMode) const
+#endif
+{
+    return new ShadowedRectangleShader{shaderType};
+}
+
+QSGMaterialType *ShadowedRectangleMaterial::type() const
+{
+    return &staticType;
+}
+
+int ShadowedRectangleMaterial::compare(const QSGMaterial *other) const
+{
+    auto material = static_cast<const ShadowedRectangleMaterial *>(other);
+    /* clang-format off */
+    if (material->color == color
+        && material->shadowColor == shadowColor
+        && material->offset == offset
+        && material->aspect == aspect
+        && qFuzzyCompare(material->size, size)
+        && qFuzzyCompare(material->radius, radius)) { /* clang-format on */
+        return 0;
+    }
+
+    return QSGMaterial::compare(other);
+}
+
+ShadowedRectangleShader::ShadowedRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType)
+{
+    setShader(shaderType, QStringLiteral("shadowedrectangle"));
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+const char *const *ShadowedRectangleShader::attributeNames() const
+{
+    static char const *const names[] = {"in_vertex", "in_uv", nullptr};
+    return names;
+}
+
+void ShadowedRectangleShader::initialize()
+{
+    QSGMaterialShader::initialize();
+    m_matrixLocation = program()->uniformLocation("matrix");
+    m_aspectLocation = program()->uniformLocation("aspect");
+    m_opacityLocation = program()->uniformLocation("opacity");
+    m_sizeLocation = program()->uniformLocation("size");
+    m_radiusLocation = program()->uniformLocation("radius");
+    m_colorLocation = program()->uniformLocation("color");
+    m_shadowColorLocation = program()->uniformLocation("shadowColor");
+    m_offsetLocation = program()->uniformLocation("offset");
+}
+
+void ShadowedRectangleShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    auto p = program();
+
+    if (state.isMatrixDirty()) {
+        p->setUniformValue(m_matrixLocation, state.combinedMatrix());
+    }
+
+    if (state.isOpacityDirty()) {
+        p->setUniformValue(m_opacityLocation, state.opacity());
+    }
+
+    if (!oldMaterial || newMaterial->compare(oldMaterial) != 0 || state.isCachedMaterialDataDirty()) {
+        auto material = static_cast<ShadowedRectangleMaterial *>(newMaterial);
+        p->setUniformValue(m_aspectLocation, material->aspect);
+        p->setUniformValue(m_sizeLocation, material->size);
+        p->setUniformValue(m_radiusLocation, material->radius);
+        p->setUniformValue(m_colorLocation, material->color);
+        p->setUniformValue(m_shadowColorLocation, material->shadowColor);
+        p->setUniformValue(m_offsetLocation, material->offset);
+    }
+}
+#else
+bool ShadowedRectangleShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    bool changed = false;
+    QByteArray *buf = state.uniformData();
+    Q_ASSERT(buf->size() >= 160);
+
+    if (state.isMatrixDirty()) {
+        const QMatrix4x4 m = state.combinedMatrix();
+        memcpy(buf->data(), m.constData(), 64);
+        changed = true;
+    }
+
+    if (state.isOpacityDirty()) {
+        const float opacity = state.opacity();
+        memcpy(buf->data() + 72, &opacity, 4);
+        changed = true;
+    }
+
+    if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) {
+        const auto material = static_cast<ShadowedRectangleMaterial *>(newMaterial);
+        memcpy(buf->data() + 64, &material->aspect, 8);
+        memcpy(buf->data() + 76, &material->size, 4);
+        memcpy(buf->data() + 80, &material->radius, 16);
+        float c[4];
+        material->color.getRgbF(&c[0], &c[1], &c[2], &c[3]);
+        memcpy(buf->data() + 96, c, 16);
+        material->shadowColor.getRgbF(&c[0], &c[1], &c[2], &c[3]);
+        memcpy(buf->data() + 112, c, 16);
+        memcpy(buf->data() + 128, &material->offset, 8);
+        changed = true;
+    }
+
+    return changed;
+}
+#endif
+
+void ShadowedRectangleShader::setShader(ShadowedRectangleMaterial::ShaderType shaderType, const QString &shader)
+{
+    const auto shaderRoot = QStringLiteral(":/org/kde/kirigami/shaders/");
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    auto header = QOpenGLContext::currentContext()->isOpenGLES() ? QStringLiteral("header_es.glsl") : QStringLiteral("header_desktop.glsl");
+
+    setShaderSourceFiles(QOpenGLShader::Vertex, {shaderRoot + header, shaderRoot + QStringLiteral("shadowedrectangle.vert")});
+
+    QString shaderFile = shader + QStringLiteral(".frag");
+    auto sdfFile = QStringLiteral("sdf.glsl");
+    if (shaderType == ShadowedRectangleMaterial::ShaderType::LowPower) {
+        shaderFile = shader + QStringLiteral("_lowpower.frag");
+        sdfFile = QStringLiteral("sdf_lowpower.glsl");
+    }
+
+    setShaderSourceFiles(QOpenGLShader::Fragment, {shaderRoot + header, shaderRoot + sdfFile, shaderRoot + shaderFile});
+#else
+    setShaderFileName(QSGMaterialShader::VertexStage, shaderRoot + QStringLiteral("shadowedrectangle.vert.qsb"));
+
+    auto shaderFile = shader;
+    if (shaderType == ShadowedRectangleMaterial::ShaderType::LowPower) {
+        shaderFile += QStringLiteral("_lowpower");
+    }
+    setShaderFileName(QSGMaterialShader::FragmentStage, shaderRoot + shaderFile + QStringLiteral(".frag.qsb"));
+#endif
+}
diff --git a/src/scenegraph/shadowedrectanglematerial.h b/src/scenegraph/shadowedrectanglematerial.h
new file mode 100644 (file)
index 0000000..b0ab522
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QColor>
+#include <QSGMaterial>
+#include <QSGMaterialShader>
+
+/**
+ * A material rendering a rectangle with a shadow.
+ *
+ * This material uses a distance field shader to render a rectangle with a
+ * shadow below it, optionally with rounded corners.
+ */
+class ShadowedRectangleMaterial : public QSGMaterial
+{
+public:
+    enum class ShaderType {
+        Standard,
+        LowPower,
+    };
+
+    ShadowedRectangleMaterial();
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QSGMaterialShader *createShader() const override;
+#else
+    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override;
+#endif
+    QSGMaterialType *type() const override;
+    int compare(const QSGMaterial *other) const override;
+
+    QVector2D aspect = QVector2D{1.0, 1.0};
+    float size = 0.0;
+    QVector4D radius = QVector4D{0.0, 0.0, 0.0, 0.0};
+    QColor color = Qt::white;
+    QColor shadowColor = Qt::black;
+    QVector2D offset;
+    ShaderType shaderType = ShaderType::Standard;
+
+    static QSGMaterialType staticType;
+};
+
+class ShadowedRectangleShader : public QSGMaterialShader
+{
+public:
+    ShadowedRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    char const *const *attributeNames() const override;
+
+    void initialize() override;
+    void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#else
+    bool updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#endif
+
+protected:
+    void setShader(ShadowedRectangleMaterial::ShaderType shaderType, const QString &shader);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+private:
+    int m_matrixLocation = -1;
+    int m_opacityLocation = -1;
+    int m_aspectLocation = -1;
+    int m_sizeLocation = -1;
+    int m_radiusLocation = -1;
+    int m_colorLocation = -1;
+    int m_shadowColorLocation = -1;
+    int m_offsetLocation = -1;
+#endif
+};
diff --git a/src/scenegraph/shadowedrectanglenode.cpp b/src/scenegraph/shadowedrectanglenode.cpp
new file mode 100644 (file)
index 0000000..ee1e58e
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedrectanglenode.h"
+#include "shadowedborderrectanglematerial.h"
+
+QColor premultiply(const QColor &color)
+{
+    return QColor::fromRgbF(color.redF() * color.alphaF(), //
+                            color.greenF() * color.alphaF(),
+                            color.blueF() * color.alphaF(),
+                            color.alphaF());
+}
+
+ShadowedRectangleNode::ShadowedRectangleNode()
+{
+    m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4};
+    setGeometry(m_geometry);
+
+    setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
+}
+
+void ShadowedRectangleNode::setBorderEnabled(bool enabled)
+{
+    // We can achieve more performant shaders by splitting the two into separate
+    // shaders. This requires separating the materials as well. So when
+    // borderWidth is increased to something where the border should be visible,
+    // switch to the with-border material. Otherwise use the no-border version.
+
+    if (enabled) {
+        if (!m_material || m_material->type() == borderlessMaterialType()) {
+            auto newMaterial = createBorderMaterial();
+            newMaterial->shaderType = m_shaderType;
+            setMaterial(newMaterial);
+            m_material = newMaterial;
+            m_rect = QRectF{};
+            markDirty(QSGNode::DirtyMaterial);
+        }
+    } else {
+        if (!m_material || m_material->type() == borderMaterialType()) {
+            auto newMaterial = createBorderlessMaterial();
+            newMaterial->shaderType = m_shaderType;
+            setMaterial(newMaterial);
+            m_material = newMaterial;
+            m_rect = QRectF{};
+            markDirty(QSGNode::DirtyMaterial);
+        }
+    }
+}
+
+void ShadowedRectangleNode::setRect(const QRectF &rect)
+{
+    if (rect == m_rect) {
+        return;
+    }
+
+    m_rect = rect;
+
+    QVector2D newAspect{1.0, 1.0};
+    if (m_rect.width() >= m_rect.height()) {
+        newAspect.setX(m_rect.width() / m_rect.height());
+    } else {
+        newAspect.setY(m_rect.height() / m_rect.width());
+    }
+
+    if (m_material->aspect != newAspect) {
+        m_material->aspect = newAspect;
+        markDirty(QSGNode::DirtyMaterial);
+        m_aspect = newAspect;
+    }
+}
+
+void ShadowedRectangleNode::setSize(qreal size)
+{
+    auto minDimension = std::min(m_rect.width(), m_rect.height());
+    float uniformSize = (size / minDimension) * 2.0;
+
+    if (!qFuzzyCompare(m_material->size, uniformSize)) {
+        m_material->size = uniformSize;
+        markDirty(QSGNode::DirtyMaterial);
+        m_size = size;
+    }
+}
+
+void ShadowedRectangleNode::setRadius(const QVector4D &radius)
+{
+    float minDimension = std::min(m_rect.width(), m_rect.height());
+    auto uniformRadius = QVector4D{std::min(radius.x() * 2.0f / minDimension, 1.0f),
+                                   std::min(radius.y() * 2.0f / minDimension, 1.0f),
+                                   std::min(radius.z() * 2.0f / minDimension, 1.0f),
+                                   std::min(radius.w() * 2.0f / minDimension, 1.0f)};
+
+    if (m_material->radius != uniformRadius) {
+        m_material->radius = uniformRadius;
+        markDirty(QSGNode::DirtyMaterial);
+        m_radius = radius;
+    }
+}
+
+void ShadowedRectangleNode::setColor(const QColor &color)
+{
+    auto premultiplied = premultiply(color);
+    if (m_material->color != premultiplied) {
+        m_material->color = premultiplied;
+        markDirty(QSGNode::DirtyMaterial);
+    }
+}
+
+void ShadowedRectangleNode::setShadowColor(const QColor &color)
+{
+    auto premultiplied = premultiply(color);
+    if (m_material->shadowColor != premultiplied) {
+        m_material->shadowColor = premultiplied;
+        markDirty(QSGNode::DirtyMaterial);
+    }
+}
+
+void ShadowedRectangleNode::setOffset(const QVector2D &offset)
+{
+    auto minDimension = std::min(m_rect.width(), m_rect.height());
+    auto uniformOffset = offset / minDimension;
+
+    if (m_material->offset != uniformOffset) {
+        m_material->offset = uniformOffset;
+        markDirty(QSGNode::DirtyMaterial);
+        m_offset = offset;
+    }
+}
+
+void ShadowedRectangleNode::setBorderWidth(qreal width)
+{
+    if (m_material->type() != borderMaterialType()) {
+        return;
+    }
+
+    auto minDimension = std::min(m_rect.width(), m_rect.height());
+    float uniformBorderWidth = width / minDimension;
+
+    auto borderMaterial = static_cast<ShadowedBorderRectangleMaterial *>(m_material);
+    if (!qFuzzyCompare(borderMaterial->borderWidth, uniformBorderWidth)) {
+        borderMaterial->borderWidth = uniformBorderWidth;
+        markDirty(QSGNode::DirtyMaterial);
+        m_borderWidth = width;
+    }
+}
+
+void ShadowedRectangleNode::setBorderColor(const QColor &color)
+{
+    if (m_material->type() != borderMaterialType()) {
+        return;
+    }
+
+    auto borderMaterial = static_cast<ShadowedBorderRectangleMaterial *>(m_material);
+    auto premultiplied = premultiply(color);
+    if (borderMaterial->borderColor != premultiplied) {
+        borderMaterial->borderColor = premultiplied;
+        markDirty(QSGNode::DirtyMaterial);
+    }
+}
+
+void ShadowedRectangleNode::setShaderType(ShadowedRectangleMaterial::ShaderType type)
+{
+    m_shaderType = type;
+}
+
+void ShadowedRectangleNode::updateGeometry()
+{
+    auto rect = m_rect;
+    if (m_shaderType == ShadowedRectangleMaterial::ShaderType::Standard) {
+        rect = rect.adjusted(-m_size * m_aspect.x(), //
+                             -m_size * m_aspect.y(),
+                             m_size * m_aspect.x(),
+                             m_size * m_aspect.y());
+
+        auto offsetLength = m_offset.length();
+        rect = rect.adjusted(-offsetLength * m_aspect.x(), //
+                             -offsetLength * m_aspect.y(),
+                             offsetLength * m_aspect.x(),
+                             offsetLength * m_aspect.y());
+    }
+
+    QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0.0, 0.0, 1.0, 1.0});
+    markDirty(QSGNode::DirtyGeometry);
+}
+
+ShadowedRectangleMaterial *ShadowedRectangleNode::createBorderlessMaterial()
+{
+    return new ShadowedRectangleMaterial{};
+}
+
+ShadowedBorderRectangleMaterial *ShadowedRectangleNode::createBorderMaterial()
+{
+    return new ShadowedBorderRectangleMaterial{};
+}
+
+QSGMaterialType *ShadowedRectangleNode::borderlessMaterialType()
+{
+    return &ShadowedRectangleMaterial::staticType;
+}
+
+QSGMaterialType *ShadowedRectangleNode::borderMaterialType()
+{
+    return &ShadowedBorderRectangleMaterial::staticType;
+}
diff --git a/src/scenegraph/shadowedrectanglenode.h b/src/scenegraph/shadowedrectanglenode.h
new file mode 100644 (file)
index 0000000..755f56f
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QColor>
+#include <QSGGeometryNode>
+#include <QVector2D>
+#include <QVector4D>
+
+#include "shadowedrectanglematerial.h"
+
+struct QSGMaterialType;
+class ShadowedBorderRectangleMaterial;
+
+/**
+ * Scene graph node for a shadowed rectangle.
+ *
+ * This node will set up the geometry and materials for a shadowed rectangle,
+ * optionally with rounded corners.
+ *
+ * \note You must call updateGeometry() after setting properties of this node,
+ * otherwise the node's state will not correctly reflect all the properties.
+ *
+ * \sa ShadowedRectangle
+ */
+class ShadowedRectangleNode : public QSGGeometryNode
+{
+public:
+    ShadowedRectangleNode();
+
+    /**
+     * Set whether to draw a border.
+     *
+     * Note that this will switch between a material with or without border.
+     * This means this needs to be called before any other setters.
+     */
+    void setBorderEnabled(bool enabled);
+
+    void setRect(const QRectF &rect);
+    void setSize(qreal size);
+    void setRadius(const QVector4D &radius);
+    void setColor(const QColor &color);
+    void setShadowColor(const QColor &color);
+    void setOffset(const QVector2D &offset);
+    void setBorderWidth(qreal width);
+    void setBorderColor(const QColor &color);
+    void setShaderType(ShadowedRectangleMaterial::ShaderType type);
+
+    /**
+     * Update the geometry for this node.
+     *
+     * This is done as an explicit step to avoid the geometry being recreated
+     * multiple times while updating properties.
+     */
+    void updateGeometry();
+
+protected:
+    virtual ShadowedRectangleMaterial *createBorderlessMaterial();
+    virtual ShadowedBorderRectangleMaterial *createBorderMaterial();
+    virtual QSGMaterialType *borderMaterialType();
+    virtual QSGMaterialType *borderlessMaterialType();
+
+    QSGGeometry *m_geometry;
+    ShadowedRectangleMaterial *m_material = nullptr;
+    ShadowedRectangleMaterial::ShaderType m_shaderType = ShadowedRectangleMaterial::ShaderType::Standard;
+
+private:
+    QRectF m_rect;
+    qreal m_size = 0.0;
+    QVector4D m_radius = QVector4D{0.0, 0.0, 0.0, 0.0};
+    QVector2D m_offset = QVector2D{0.0, 0.0};
+    QVector2D m_aspect = QVector2D{1.0, 1.0};
+    qreal m_borderWidth = 0.0;
+    QColor m_borderColor;
+};
diff --git a/src/scenegraph/shadowedtexturematerial.cpp b/src/scenegraph/shadowedtexturematerial.cpp
new file mode 100644 (file)
index 0000000..1c58565
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedtexturematerial.h"
+
+#include <QOpenGLContext>
+
+QSGMaterialType ShadowedTextureMaterial::staticType;
+
+ShadowedTextureMaterial::ShadowedTextureMaterial()
+    : ShadowedRectangleMaterial()
+{
+    setFlag(QSGMaterial::Blending, true);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QSGMaterialShader *ShadowedTextureMaterial::createShader() const
+#else
+QSGMaterialShader *ShadowedTextureMaterial::createShader(QSGRendererInterface::RenderMode) const
+#endif
+{
+    return new ShadowedTextureShader{shaderType};
+}
+
+QSGMaterialType *ShadowedTextureMaterial::type() const
+{
+    return &staticType;
+}
+
+int ShadowedTextureMaterial::compare(const QSGMaterial *other) const
+{
+    auto material = static_cast<const ShadowedTextureMaterial *>(other);
+
+    auto result = ShadowedRectangleMaterial::compare(other);
+    if (result == 0) {
+        if (material->textureSource == textureSource) {
+            return 0;
+        } else {
+            return (material->textureSource < textureSource) ? 1 : -1;
+        }
+    }
+
+    return QSGMaterial::compare(other);
+}
+
+ShadowedTextureShader::ShadowedTextureShader(ShadowedRectangleMaterial::ShaderType shaderType)
+    : ShadowedRectangleShader(shaderType)
+{
+    setShader(shaderType, QStringLiteral("shadowedtexture"));
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ShadowedTextureShader::initialize()
+{
+    ShadowedRectangleShader::initialize();
+    program()->setUniformValue("textureSource", 0);
+}
+
+void ShadowedTextureShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+    ShadowedRectangleShader::updateState(state, newMaterial, oldMaterial);
+
+    auto texture = static_cast<ShadowedTextureMaterial *>(newMaterial)->textureSource;
+    if (texture) {
+        texture->bind();
+    }
+}
+#else
+void ShadowedTextureShader::updateSampledImage(QSGMaterialShader::RenderState &state,
+                                               int binding,
+                                               QSGTexture **texture,
+                                               QSGMaterial *newMaterial,
+                                               QSGMaterial *oldMaterial)
+{
+    Q_UNUSED(state);
+    Q_UNUSED(oldMaterial);
+    if (binding == 1) {
+        *texture = static_cast<ShadowedTextureMaterial *>(newMaterial)->textureSource;
+    }
+}
+#endif
diff --git a/src/scenegraph/shadowedtexturematerial.h b/src/scenegraph/shadowedtexturematerial.h
new file mode 100644 (file)
index 0000000..d7edb02
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QSGTexture>
+
+#include "shadowedrectanglematerial.h"
+
+/**
+ * A material rendering a rectangle with a shadow.
+ *
+ * This material uses a distance field shader to render a rectangle with a
+ * shadow below it, optionally with rounded corners.
+ */
+class ShadowedTextureMaterial : public ShadowedRectangleMaterial
+{
+public:
+    ShadowedTextureMaterial();
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QSGMaterialShader *createShader() const override;
+#else
+    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override;
+#endif
+    QSGMaterialType *type() const override;
+    int compare(const QSGMaterial *other) const override;
+
+    QSGTexture *textureSource = nullptr;
+
+    static QSGMaterialType staticType;
+};
+
+class ShadowedTextureShader : public ShadowedRectangleShader
+{
+public:
+    ShadowedTextureShader(ShadowedRectangleMaterial::ShaderType shaderType);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void initialize() override;
+    void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#else
+    void
+    updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+#endif
+};
diff --git a/src/scenegraph/shadowedtexturenode.cpp b/src/scenegraph/shadowedtexturenode.cpp
new file mode 100644 (file)
index 0000000..0b22a4d
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedtexturenode.h"
+
+#include "shadowedbordertexturematerial.h"
+
+template<typename T>
+inline void preprocessTexture(QSGMaterial *material, QSGTextureProvider *provider)
+{
+    auto m = static_cast<T *>(material);
+    // Since we handle texture coordinates differently in the shader, we
+    // need to remove the texture from the atlas for now.
+    if (provider->texture()->isAtlasTexture()) {
+        // Blegh, I have no idea why "removedFromAtlas" doesn't just return
+        // the texture when it's not an atlas.
+        m->textureSource = provider->texture()->removedFromAtlas();
+    } else {
+        m->textureSource = provider->texture();
+    }
+    if (QSGDynamicTexture *dynamic_texture = qobject_cast<QSGDynamicTexture *>(m->textureSource)) {
+        dynamic_texture->updateTexture();
+    }
+}
+
+ShadowedTextureNode::ShadowedTextureNode()
+    : ShadowedRectangleNode()
+{
+    setFlag(QSGNode::UsePreprocess);
+}
+
+ShadowedTextureNode::~ShadowedTextureNode()
+{
+    QObject::disconnect(m_textureChangeConnectionHandle);
+}
+
+void ShadowedTextureNode::setTextureSource(QSGTextureProvider *source)
+{
+    if (m_textureSource == source) {
+        return;
+    }
+
+    if (m_textureSource) {
+        m_textureSource->disconnect();
+    }
+
+    m_textureSource = source;
+    m_textureChangeConnectionHandle = QObject::connect(m_textureSource.data(), &QSGTextureProvider::textureChanged, [this] {
+        markDirty(QSGNode::DirtyMaterial);
+    });
+    markDirty(QSGNode::DirtyMaterial);
+}
+
+void ShadowedTextureNode::preprocess()
+{
+    if (m_textureSource && m_material && m_textureSource->texture()) {
+        if (m_material->type() == borderlessMaterialType()) {
+            preprocessTexture<ShadowedTextureMaterial>(m_material, m_textureSource);
+        } else {
+            preprocessTexture<ShadowedBorderTextureMaterial>(m_material, m_textureSource);
+        }
+    }
+}
+
+ShadowedRectangleMaterial *ShadowedTextureNode::createBorderlessMaterial()
+{
+    return new ShadowedTextureMaterial{};
+}
+
+ShadowedBorderRectangleMaterial *ShadowedTextureNode::createBorderMaterial()
+{
+    return new ShadowedBorderTextureMaterial{};
+}
+
+QSGMaterialType *ShadowedTextureNode::borderlessMaterialType()
+{
+    return &ShadowedTextureMaterial::staticType;
+}
+
+QSGMaterialType *ShadowedTextureNode::borderMaterialType()
+{
+    return &ShadowedBorderTextureMaterial::staticType;
+}
diff --git a/src/scenegraph/shadowedtexturenode.h b/src/scenegraph/shadowedtexturenode.h
new file mode 100644 (file)
index 0000000..d0b0efc
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QPointer>
+#include <QSGTextureProvider>
+
+#include "shadowedrectanglenode.h"
+#include "shadowedtexturematerial.h"
+
+/**
+ * Scene graph node for a shadowed texture source.
+ *
+ * This node will set up the geometry and materials for a shadowed rectangle,
+ * optionally with rounded corners, using a supplied texture source as the color
+ * for the rectangle.
+ *
+ * \note You must call updateGeometry() after setting properties of this node,
+ * otherwise the node's state will not correctly reflect all the properties.
+ *
+ * \sa ShadowedTexture
+ */
+class ShadowedTextureNode : public ShadowedRectangleNode
+{
+public:
+    ShadowedTextureNode();
+    ~ShadowedTextureNode();
+
+    void setTextureSource(QSGTextureProvider *source);
+    void preprocess() override;
+
+private:
+    ShadowedRectangleMaterial *createBorderlessMaterial() override;
+    ShadowedBorderRectangleMaterial *createBorderMaterial() override;
+    QSGMaterialType *borderlessMaterialType() override;
+    QSGMaterialType *borderMaterialType() override;
+
+    QPointer<QSGTextureProvider> m_textureSource;
+    QMetaObject::Connection m_textureChangeConnectionHandle;
+};
diff --git a/src/scenepositionattached.cpp b/src/scenepositionattached.cpp
new file mode 100644 (file)
index 0000000..5576a99
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "scenepositionattached.h"
+#include <QDebug>
+#include <QQuickItem>
+
+ScenePositionAttached::ScenePositionAttached(QObject *parent)
+    : QObject(parent)
+{
+    m_item = qobject_cast<QQuickItem *>(parent);
+    connectAncestors(m_item);
+}
+
+ScenePositionAttached::~ScenePositionAttached()
+{
+}
+
+int ScenePositionAttached::x() const
+{
+    qreal x = 0;
+    QQuickItem *item = m_item;
+
+    while (item) {
+        x += item->x();
+        item = item->parentItem();
+    }
+
+    return x;
+}
+
+int ScenePositionAttached::y() const
+{
+    qreal y = 0;
+    QQuickItem *item = m_item;
+
+    while (item) {
+        y += item->y();
+        item = item->parentItem();
+    }
+
+    return y;
+}
+
+void ScenePositionAttached::connectAncestors(QQuickItem *item)
+{
+    if (!item) {
+        return;
+    }
+
+    QQuickItem *ancestor = item;
+    while (ancestor) {
+        m_ancestors << ancestor;
+
+        connect(ancestor, &QQuickItem::xChanged, this, &ScenePositionAttached::xChanged);
+        connect(ancestor, &QQuickItem::yChanged, this, &ScenePositionAttached::yChanged);
+        connect(ancestor, &QQuickItem::parentChanged, this, [this, ancestor]() {
+            do {
+                disconnect(ancestor, nullptr, this, nullptr);
+                m_ancestors.pop_back();
+            } while (!m_ancestors.isEmpty() && m_ancestors.last() != ancestor);
+
+            connectAncestors(ancestor);
+            Q_EMIT xChanged();
+            Q_EMIT yChanged();
+        });
+
+        ancestor = ancestor->parentItem();
+    }
+}
+
+ScenePositionAttached *ScenePositionAttached::qmlAttachedProperties(QObject *object)
+{
+    return new ScenePositionAttached(object);
+}
+
+#include "moc_scenepositionattached.cpp"
diff --git a/src/scenepositionattached.h b/src/scenepositionattached.h
new file mode 100644 (file)
index 0000000..88a164d
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#ifndef SCENEPOSITIONATTACHED_H
+#define SCENEPOSITIONATTACHED_H
+
+#include <QObject>
+#include <QtQml>
+
+class QQuickItem;
+
+/**
+ * @brief This attached property contains items window-local x & y coordinates.
+ *
+ * Note that an Item's X and Y coordinates are relative to its parent, meaning
+ * that when a list of Items has the same parent, the top-most Item will have
+ * coordinates 0x0.
+ *
+ * Use this attached property to get the X and Y coordinates of an Item that are
+ * relative to their QML scene instead of their parent. This is useful when
+ * implementing custom views and animations.
+ *
+ * Typically the 0x0 coordinate of the QML scene starts at the top left corner
+ * of the window, below the titlebar.
+ *
+ * Example usage:
+ * @include scenepositionattached.qml
+ *
+ * @since org.kde.kirigami 2.3
+ */
+class ScenePositionAttached : public QObject
+{
+    Q_OBJECT
+    /**
+     * The global scene X position
+     */
+    Q_PROPERTY(int x READ x NOTIFY xChanged)
+
+    /**
+     * The global scene Y position
+     */
+    Q_PROPERTY(int y READ y NOTIFY yChanged)
+
+public:
+    explicit ScenePositionAttached(QObject *parent = nullptr);
+    ~ScenePositionAttached() override;
+
+    int x() const;
+    int y() const;
+
+    // QML attached property
+    static ScenePositionAttached *qmlAttachedProperties(QObject *object);
+
+Q_SIGNALS:
+    void xChanged();
+    void yChanged();
+
+private:
+    void connectAncestors(QQuickItem *item);
+
+    QQuickItem *m_item = nullptr;
+    QList<QQuickItem *> m_ancestors;
+};
+
+QML_DECLARE_TYPEINFO(ScenePositionAttached, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // SCENEPOSITIONATTACHED_H
diff --git a/src/settings.cpp b/src/settings.cpp
new file mode 100644 (file)
index 0000000..0aeae38
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "settings.h"
+
+#include <QDebug>
+#include <QFile>
+#include <QGuiApplication>
+#include <QIcon>
+#include <QMouseEvent>
+#include <QSettings>
+#include <QStandardPaths>
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include <QTouchDevice>
+#else
+#include <QInputDevice>
+#endif
+#include <QWindow>
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformmenu.h>
+#include <QtGui/qpa/qplatformtheme.h>
+
+#include "libkirigami/tabletmodewatcher.h"
+
+#ifndef KIRIGAMI_BUILD_TYPE_STATIC
+#include "../kirigami_version.h"
+#endif
+
+class SettingsSingleton
+{
+public:
+    Settings self;
+};
+
+Q_GLOBAL_STATIC(SettingsSingleton, privateSettingsSelf)
+
+Settings::Settings(QObject *parent)
+    : QObject(parent)
+    , m_hasTouchScreen(false)
+    , m_hasTransientTouchInput(false)
+{
+    m_tabletModeAvailable = Kirigami::TabletModeWatcher::self()->isTabletModeAvailable();
+    connect(Kirigami::TabletModeWatcher::self(), &Kirigami::TabletModeWatcher::tabletModeAvailableChanged, this, [this](bool tabletModeAvailable) {
+        setTabletModeAvailable(tabletModeAvailable);
+    });
+
+    m_tabletMode = Kirigami::TabletModeWatcher::self()->isTabletMode();
+    connect(Kirigami::TabletModeWatcher::self(), &Kirigami::TabletModeWatcher::tabletModeChanged, this, [this](bool tabletMode) {
+        setTabletMode(tabletMode);
+    });
+
+#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(UBUNTU_TOUCH)
+    m_mobile = true;
+    m_hasTouchScreen = true;
+#else
+    // Mostly for debug purposes and for platforms which are always mobile,
+    // such as Plasma Mobile
+    if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE")) {
+        m_mobile = QByteArrayList{"1", "true"}.contains(qgetenv("QT_QUICK_CONTROLS_MOBILE"));
+    } else {
+        m_mobile = false;
+    }
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    const auto touchDevices = QTouchDevice::devices();
+    const auto touchDeviceType = QTouchDevice::TouchScreen;
+#else
+    const auto touchDevices = QInputDevice::devices();
+    const auto touchDeviceType = QInputDevice::DeviceType::TouchScreen;
+#endif
+    for (const auto &device : touchDevices) {
+        if (device->type() == touchDeviceType) {
+            m_hasTouchScreen = true;
+            break;
+        }
+    }
+    if (m_hasTouchScreen) {
+        connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow *win) {
+            if (win) {
+                win->installEventFilter(this);
+            }
+        });
+    }
+#endif
+
+    auto bar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar();
+    m_hasPlatformMenuBar = bar != nullptr;
+    if (bar != nullptr) {
+        bar->deleteLater();
+    }
+
+    const QString configPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kdeglobals"));
+    if (QFile::exists(configPath)) {
+        QSettings globals(configPath, QSettings::IniFormat);
+        globals.beginGroup(QStringLiteral("KDE"));
+        m_scrollLines = qMax(1, globals.value(QStringLiteral("WheelScrollLines"), 3).toInt());
+    } else {
+        m_scrollLines = 3;
+    }
+}
+
+Settings::~Settings()
+{
+}
+
+Settings *Settings::self()
+{
+    return &privateSettingsSelf()->self;
+}
+
+bool Settings::eventFilter(QObject *watched, QEvent *event)
+{
+    Q_UNUSED(watched)
+    switch (event->type()) {
+    case QEvent::TouchBegin:
+        setTransientTouchInput(true);
+        break;
+    case QEvent::MouseButtonPress:
+    case QEvent::MouseMove: {
+        QMouseEvent *me = static_cast<QMouseEvent *>(event);
+        if (me->source() == Qt::MouseEventNotSynthesized) {
+            setTransientTouchInput(false);
+        }
+        break;
+    }
+    case QEvent::Wheel:
+        setTransientTouchInput(false);
+    default:
+        break;
+    }
+
+    return false;
+}
+
+void Settings::setTabletModeAvailable(bool mobileAvailable)
+{
+    if (mobileAvailable == m_tabletModeAvailable) {
+        return;
+    }
+
+    m_tabletModeAvailable = mobileAvailable;
+    Q_EMIT tabletModeAvailableChanged();
+}
+
+bool Settings::isTabletModeAvailable() const
+{
+    return m_tabletModeAvailable;
+}
+
+void Settings::setIsMobile(bool mobile)
+{
+    if (mobile == m_mobile) {
+        return;
+    }
+
+    m_mobile = mobile;
+    Q_EMIT isMobileChanged();
+}
+
+bool Settings::isMobile() const
+{
+    return m_mobile;
+}
+
+void Settings::setTabletMode(bool tablet)
+{
+    if (tablet == m_tabletMode) {
+        return;
+    }
+
+    m_tabletMode = tablet;
+    Q_EMIT tabletModeChanged();
+}
+
+bool Settings::tabletMode() const
+{
+    return m_tabletMode;
+}
+
+void Settings::setTransientTouchInput(bool touch)
+{
+    if (touch == m_hasTransientTouchInput) {
+        return;
+    }
+
+    m_hasTransientTouchInput = touch;
+    if (!m_tabletMode) {
+        Q_EMIT hasTransientTouchInputChanged();
+    }
+}
+
+bool Settings::hasTransientTouchInput() const
+{
+    return m_hasTransientTouchInput || m_tabletMode;
+}
+
+QString Settings::style() const
+{
+    return m_style;
+}
+
+void Settings::setStyle(const QString &style)
+{
+    m_style = style;
+}
+
+int Settings::mouseWheelScrollLines() const
+{
+    return m_scrollLines;
+}
+
+QStringList Settings::information() const
+{
+    return {
+#ifndef KIRIGAMI_BUILD_TYPE_STATIC
+        tr("KDE Frameworks %1").arg(QStringLiteral(KIRIGAMI2_VERSION_STRING)),
+#endif
+        tr("The %1 windowing system").arg(QGuiApplication::platformName()),
+        tr("Qt %2 (built against %3)").arg(QString::fromLocal8Bit(qVersion()), QStringLiteral(QT_VERSION_STR))};
+}
+
+QVariant Settings::applicationWindowIcon() const
+{
+    const QIcon &windowIcon = qApp->windowIcon();
+    if (windowIcon.isNull()) {
+        return QVariant();
+    }
+    return windowIcon;
+}
+
+bool Settings::hasPlatformMenuBar() const
+{
+    return m_hasPlatformMenuBar;
+}
diff --git a/src/settings.h b/src/settings.h
new file mode 100644 (file)
index 0000000..d994828
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <QObject>
+#include <QVariant>
+
+/**
+ * This class contains global kirigami settings about the current device setup
+ * It is exposed to QML as the singleton "Settings"
+ */
+class Settings : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property specifies whether the system can dynamically enter and exit tablet mode
+     * (or the device is actually a tablet).
+     *
+     * This is the case for foldable convertibles and transformable laptops that support
+     * keyboard detachment.
+     */
+    Q_PROPERTY(bool tabletModeAvailable READ isTabletModeAvailable NOTIFY tabletModeAvailableChanged)
+
+    /**
+     * @brief This property specifies whether the application is running on a small
+     * mobile device such as a mobile phone.
+     *
+     * This is used when we want to do specific adaptations to the UI for small
+     * screen form factors, such as having bigger touch areas.
+     */
+    Q_PROPERTY(bool isMobile READ isMobile NOTIFY isMobileChanged)
+
+    /**
+     * @brief This property specifies whether the application is running on a device
+     * that is behaving like a tablet.
+     *
+     * @note This doesn't mean exactly a tablet form factor, but
+     * that the preferred input mode for the device is the touch screen
+     * and that pointer and keyboard are either secondary or not available.
+     */
+    Q_PROPERTY(bool tabletMode READ tabletMode NOTIFY tabletModeChanged)
+
+    /**
+     * @brief This property specifies whether the system has a platform menu bar;
+     * e.g. a user is on macOS or has a global menu on KDE Plasma.
+     *
+     * @warning Android has a platform menu bar; which may not be what you expected.
+     */
+    Q_PROPERTY(bool hasPlatformMenuBar READ hasPlatformMenuBar CONSTANT)
+
+    /**
+     * @brief This property specifies whether the user is currently interacting
+     * with the app with the touch screen.
+     */
+    Q_PROPERTY(bool hasTransientTouchInput READ hasTransientTouchInput NOTIFY hasTransientTouchInputChanged)
+
+    /**
+     * @brief This property holds the name of the QtQuick Controls style the application is using,
+     * for instance org.kde.desktop, Plasma, Material, Universal etc
+     */
+    Q_PROPERTY(QString style READ style CONSTANT)
+
+    // TODO: make this adapt without file watchers?
+    /**
+     * @brief This property holds the number of lines of text the mouse wheel should scroll.
+     */
+    Q_PROPERTY(int mouseWheelScrollLines READ mouseWheelScrollLines CONSTANT)
+
+    /**
+     * @brief This property holds the runtime information about the libraries in use.
+     *
+     * @since KDE Frameworks 5.52
+     * @since org.kde.kirigami 2.6
+     */
+    Q_PROPERTY(QStringList information READ information CONSTANT)
+
+    /**
+     * @brief This property holds the name of the application window icon.
+     * @see QGuiApplication::windowIcon
+     *
+     * @since KDE Frameworks 5.62
+     * @since org.kde.kirigami 2.10
+     */
+    Q_PROPERTY(QVariant applicationWindowIcon READ applicationWindowIcon CONSTANT)
+
+public:
+    Settings(QObject *parent = nullptr);
+    ~Settings() override;
+
+    void setTabletModeAvailable(bool mobile);
+    bool isTabletModeAvailable() const;
+
+    void setIsMobile(bool mobile);
+    bool isMobile() const;
+
+    void setTabletMode(bool tablet);
+    bool tabletMode() const;
+
+    void setTransientTouchInput(bool touch);
+    bool hasTransientTouchInput() const;
+
+    bool hasPlatformMenuBar() const;
+
+    QString style() const;
+    void setStyle(const QString &style);
+
+    int mouseWheelScrollLines() const;
+
+    QStringList information() const;
+
+    QVariant applicationWindowIcon() const;
+
+    static Settings *self();
+
+protected:
+    bool eventFilter(QObject *watched, QEvent *event) override;
+
+Q_SIGNALS:
+    void tabletModeAvailableChanged();
+    void tabletModeChanged();
+    void isMobileChanged();
+    void hasTransientTouchInputChanged();
+
+private:
+    QString m_style;
+    int m_scrollLines = 0;
+    bool m_tabletModeAvailable : 1;
+    bool m_mobile : 1;
+    bool m_tabletMode : 1;
+    bool m_hasTouchScreen : 1;
+    bool m_hasTransientTouchInput : 1;
+    bool m_hasPlatformMenuBar : 1;
+};
+
+#endif
diff --git a/src/shadowedrectangle.cpp b/src/shadowedrectangle.cpp
new file mode 100644 (file)
index 0000000..648cf85
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedrectangle.h"
+
+#include <QQuickWindow>
+#include <QSGRectangleNode>
+#include <QSGRendererInterface>
+
+#include "scenegraph/paintedrectangleitem.h"
+#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include "scenegraph/shadowedrectanglenode.h"
+#endif
+
+BorderGroup::BorderGroup(QObject *parent)
+    : QObject(parent)
+{
+}
+
+qreal BorderGroup::width() const
+{
+    return m_width;
+}
+
+void BorderGroup::setWidth(qreal newWidth)
+{
+    if (newWidth == m_width) {
+        return;
+    }
+
+    m_width = newWidth;
+    Q_EMIT changed();
+}
+
+QColor BorderGroup::color() const
+{
+    return m_color;
+}
+
+void BorderGroup::setColor(const QColor &newColor)
+{
+    if (newColor == m_color) {
+        return;
+    }
+
+    m_color = newColor;
+    Q_EMIT changed();
+}
+
+ShadowGroup::ShadowGroup(QObject *parent)
+    : QObject(parent)
+{
+}
+
+qreal ShadowGroup::size() const
+{
+    return m_size;
+}
+
+void ShadowGroup::setSize(qreal newSize)
+{
+    if (newSize == m_size) {
+        return;
+    }
+
+    m_size = newSize;
+    Q_EMIT changed();
+}
+
+qreal ShadowGroup::xOffset() const
+{
+    return m_xOffset;
+}
+
+void ShadowGroup::setXOffset(qreal newXOffset)
+{
+    if (newXOffset == m_xOffset) {
+        return;
+    }
+
+    m_xOffset = newXOffset;
+    Q_EMIT changed();
+}
+
+qreal ShadowGroup::yOffset() const
+{
+    return m_yOffset;
+}
+
+void ShadowGroup::setYOffset(qreal newYOffset)
+{
+    if (newYOffset == m_yOffset) {
+        return;
+    }
+
+    m_yOffset = newYOffset;
+    Q_EMIT changed();
+}
+
+QColor ShadowGroup::color() const
+{
+    return m_color;
+}
+
+void ShadowGroup::setColor(const QColor &newColor)
+{
+    if (newColor == m_color) {
+        return;
+    }
+
+    m_color = newColor;
+    Q_EMIT changed();
+}
+
+CornersGroup::CornersGroup(QObject *parent)
+    : QObject(parent)
+{
+}
+
+qreal CornersGroup::topLeft() const
+{
+    return m_topLeft;
+}
+
+void CornersGroup::setTopLeft(qreal newTopLeft)
+{
+    if (newTopLeft == m_topLeft) {
+        return;
+    }
+
+    m_topLeft = newTopLeft;
+    Q_EMIT changed();
+}
+
+qreal CornersGroup::topRight() const
+{
+    return m_topRight;
+}
+
+void CornersGroup::setTopRight(qreal newTopRight)
+{
+    if (newTopRight == m_topRight) {
+        return;
+    }
+
+    m_topRight = newTopRight;
+    Q_EMIT changed();
+}
+
+qreal CornersGroup::bottomLeft() const
+{
+    return m_bottomLeft;
+}
+
+void CornersGroup::setBottomLeft(qreal newBottomLeft)
+{
+    if (newBottomLeft == m_bottomLeft) {
+        return;
+    }
+
+    m_bottomLeft = newBottomLeft;
+    Q_EMIT changed();
+}
+
+qreal CornersGroup::bottomRight() const
+{
+    return m_bottomRight;
+}
+
+void CornersGroup::setBottomRight(qreal newBottomRight)
+{
+    if (newBottomRight == m_bottomRight) {
+        return;
+    }
+
+    m_bottomRight = newBottomRight;
+    Q_EMIT changed();
+}
+
+QVector4D CornersGroup::toVector4D(float all) const
+{
+    return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight,
+                     m_topRight < 0.0 ? all : m_topRight,
+                     m_bottomLeft < 0.0 ? all : m_bottomLeft,
+                     m_topLeft < 0.0 ? all : m_topLeft};
+}
+
+ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem)
+    : QQuickItem(parentItem)
+    , m_border(std::make_unique<BorderGroup>())
+    , m_shadow(std::make_unique<ShadowGroup>())
+    , m_corners(std::make_unique<CornersGroup>())
+{
+    setFlag(QQuickItem::ItemHasContents, true);
+
+    connect(m_border.get(), &BorderGroup::changed, this, &ShadowedRectangle::update);
+    connect(m_shadow.get(), &ShadowGroup::changed, this, &ShadowedRectangle::update);
+    connect(m_corners.get(), &CornersGroup::changed, this, &ShadowedRectangle::update);
+}
+
+ShadowedRectangle::~ShadowedRectangle()
+{
+}
+
+BorderGroup *ShadowedRectangle::border() const
+{
+    return m_border.get();
+}
+
+ShadowGroup *ShadowedRectangle::shadow() const
+{
+    return m_shadow.get();
+}
+
+CornersGroup *ShadowedRectangle::corners() const
+{
+    return m_corners.get();
+}
+
+qreal ShadowedRectangle::radius() const
+{
+    return m_radius;
+}
+
+void ShadowedRectangle::setRadius(qreal newRadius)
+{
+    if (newRadius == m_radius) {
+        return;
+    }
+
+    m_radius = newRadius;
+    if (!isSoftwareRendering()) {
+        update();
+    }
+    Q_EMIT radiusChanged();
+}
+
+QColor ShadowedRectangle::color() const
+{
+    return m_color;
+}
+
+void ShadowedRectangle::setColor(const QColor &newColor)
+{
+    if (newColor == m_color) {
+        return;
+    }
+
+    m_color = newColor;
+    if (!isSoftwareRendering()) {
+        update();
+    }
+    Q_EMIT colorChanged();
+}
+
+ShadowedRectangle::RenderType ShadowedRectangle::renderType() const
+{
+    return m_renderType;
+}
+
+void ShadowedRectangle::setRenderType(RenderType renderType)
+{
+    if (renderType == m_renderType) {
+        return;
+    }
+    m_renderType = renderType;
+    update();
+    Q_EMIT renderTypeChanged();
+}
+
+void ShadowedRectangle::componentComplete()
+{
+    QQuickItem::componentComplete();
+
+    checkSoftwareItem();
+}
+
+bool ShadowedRectangle::isSoftwareRendering() const
+{
+    return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software;
+}
+
+PaintedRectangleItem *ShadowedRectangle::softwareItem() const
+{
+    return m_softwareItem;
+}
+
+void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
+{
+    if (change == QQuickItem::ItemSceneChange && value.window) {
+        checkSoftwareItem();
+        // TODO: only conditionally emit?
+        Q_EMIT softwareRenderingChanged();
+    }
+}
+
+QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
+{
+    Q_UNUSED(data);
+
+#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+    auto shadowNode = static_cast<ShadowedRectangleNode *>(node);
+
+    if (!shadowNode) {
+        shadowNode = new ShadowedRectangleNode{};
+
+        // Cache lowPower state so we only execute the full check once.
+        static bool lowPower = QByteArrayList{"1", "true"}.contains(qgetenv("KIRIGAMI_LOWPOWER_HARDWARE").toLower());
+        if (m_renderType == RenderType::LowQuality || (m_renderType == RenderType::Auto && lowPower)) {
+            shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower);
+        }
+    }
+
+    shadowNode->setBorderEnabled(m_border->isEnabled());
+    shadowNode->setRect(boundingRect());
+    shadowNode->setSize(m_shadow->size());
+    shadowNode->setRadius(m_corners->toVector4D(m_radius));
+    shadowNode->setOffset(QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())});
+    shadowNode->setColor(m_color);
+    shadowNode->setShadowColor(m_shadow->color());
+    shadowNode->setBorderWidth(m_border->width());
+    shadowNode->setBorderColor(m_border->color());
+    shadowNode->updateGeometry();
+    return shadowNode;
+#else
+    Q_UNUSED(node)
+    return nullptr;
+#endif
+}
+
+void ShadowedRectangle::checkSoftwareItem()
+{
+    if (!m_softwareItem && isSoftwareRendering()) {
+        m_softwareItem = new PaintedRectangleItem{this};
+        // The software item is added as a "normal" child item, this means it
+        // will be part of the normal item sort order. Since there is no way to
+        // control the ordering of children, just make sure to have a very low Z
+        // value for the child, to force it to be the lowest item.
+        m_softwareItem->setZ(-99.0);
+
+        auto updateItem = [this]() {
+            auto borderWidth = m_border->width();
+            auto rect = boundingRect();
+            m_softwareItem->setSize(rect.size());
+            m_softwareItem->setColor(m_color);
+            m_softwareItem->setRadius(m_radius);
+            m_softwareItem->setBorderWidth(borderWidth);
+            m_softwareItem->setBorderColor(m_border->color());
+        };
+
+        updateItem();
+
+        connect(this, &ShadowedRectangle::widthChanged, m_softwareItem, updateItem);
+        connect(this, &ShadowedRectangle::heightChanged, m_softwareItem, updateItem);
+        connect(this, &ShadowedRectangle::colorChanged, m_softwareItem, updateItem);
+        connect(this, &ShadowedRectangle::radiusChanged, m_softwareItem, updateItem);
+        connect(m_border.get(), &BorderGroup::changed, m_softwareItem, updateItem);
+        setFlag(QQuickItem::ItemHasContents, false);
+    }
+}
diff --git a/src/shadowedrectangle.h b/src/shadowedrectangle.h
new file mode 100644 (file)
index 0000000..5f12c3b
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QQuickItem>
+#include <memory>
+
+class PaintedRectangleItem;
+
+/**
+ * @brief Grouped property for rectangle border.
+ *
+ * Example usage:
+ * @include shadowedrectangle.qml
+ */
+class BorderGroup : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the border's width in pixels.
+     *
+     * default: ``0``px
+     */
+    Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY changed)
+    /**
+     * @brief This property holds the border's color.
+     *
+     * Full RGBA colors are supported.
+     *
+     * default: ``Qt::black``
+     */
+    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed)
+
+public:
+    explicit BorderGroup(QObject *parent = nullptr);
+
+    qreal width() const;
+    void setWidth(qreal newWidth);
+
+    QColor color() const;
+    void setColor(const QColor &newColor);
+
+    Q_SIGNAL void changed();
+
+    inline bool isEnabled() const
+    {
+        return !qFuzzyIsNull(m_width);
+    }
+
+private:
+    qreal m_width = 0.0;
+    QColor m_color = Qt::black;
+};
+
+/**
+ * @brief Grouped property for the rectangle's shadow.
+ */
+class ShadowGroup : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the shadow's approximate size in pixels.
+     * @note The actual shadow size can be less than this value due to falloff.
+     *
+     * default: ``0``px
+     */
+    Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY changed)
+    /**
+     * @brief This property holds the shadow's offset in pixels on the X axis.
+     *
+     * default: ``0``px
+     */
+    Q_PROPERTY(qreal xOffset READ xOffset WRITE setXOffset NOTIFY changed)
+    /**
+     * @brief This property holds the shadow's offset in pixels on the Y axis.
+     *
+     * default: ``0``px
+     */
+    Q_PROPERTY(qreal yOffset READ yOffset WRITE setYOffset NOTIFY changed)
+    /**
+     * @brief This property holds the shadow's color.
+     *
+     * Full RGBA colors are supported.
+     *
+     * default: ``Qt::black``
+     */
+    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed)
+
+public:
+    explicit ShadowGroup(QObject *parent = nullptr);
+
+    qreal size() const;
+    void setSize(qreal newSize);
+
+    qreal xOffset() const;
+    void setXOffset(qreal newXOffset);
+
+    qreal yOffset() const;
+    void setYOffset(qreal newYOffset);
+
+    QColor color() const;
+    void setColor(const QColor &newShadowColor);
+
+    Q_SIGNAL void changed();
+
+private:
+    qreal m_size = 0.0;
+    qreal m_xOffset = 0.0;
+    qreal m_yOffset = 0.0;
+    QColor m_color = Qt::black;
+};
+
+/**
+ * @brief Grouped property for corner radius.
+ */
+class CornersGroup : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the top-left corner's radius in pixels.
+     *
+     * Setting this to ``-1`` indicates that the value should be ignored.
+     * 
+     * default: ``-1``px
+     */
+    Q_PROPERTY(qreal topLeftRadius READ topLeft WRITE setTopLeft NOTIFY changed)
+
+    /**
+     * @brief This property holds the top-right corner's radius in pixels.
+     *
+     * Setting this to ``-1`` indicates that the value should be ignored.
+     * 
+     * default: ``-1``px
+     */
+    Q_PROPERTY(qreal topRightRadius READ topRight WRITE setTopRight NOTIFY changed)
+
+    /**
+     * @brief This property holds the bottom-left corner's radius in pixels.
+     *
+     * Setting this to ``-1`` indicates that the value should be ignored.
+     * 
+     * default: ``-1``px
+     */
+    Q_PROPERTY(qreal bottomLeftRadius READ bottomLeft WRITE setBottomLeft NOTIFY changed)
+
+    /**
+     * @brief This property holds the bottom-right corner's radius in pixels.
+     *
+     * Setting this to ``-1`` indicates that the value should be ignored.
+     * 
+     * default: ``-1``px
+     */
+    Q_PROPERTY(qreal bottomRightRadius READ bottomRight WRITE setBottomRight NOTIFY changed)
+
+public:
+    explicit CornersGroup(QObject *parent = nullptr);
+
+    qreal topLeft() const;
+    void setTopLeft(qreal newTopLeft);
+
+    qreal topRight() const;
+    void setTopRight(qreal newTopRight);
+
+    qreal bottomLeft() const;
+    void setBottomLeft(qreal newBottomLeft);
+
+    qreal bottomRight() const;
+    void setBottomRight(qreal newBottomRight);
+
+    Q_SIGNAL void changed();
+
+    QVector4D toVector4D(float all) const;
+
+private:
+    float m_topLeft = -1.0;
+    float m_topRight = -1.0;
+    float m_bottomLeft = -1.0;
+    float m_bottomRight = -1.0;
+};
+
+/**
+ * @brief A rectangle with a shadow behind it.
+ *
+ * This item will render a rectangle, with a shadow below it. The rendering is done
+ * using distance fields, which provide greatly improved performance. The shadow is
+ * rendered outside of the item's bounds, so the item's width and height are the
+ * rectangle's width and height.
+ *
+ * @since KDE Frameworks 5.69
+ * @since org.kde.kirigami 2.12
+ */
+class ShadowedRectangle : public QQuickItem
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the radii of the rectangle's corners.
+     *
+     * This is the amount of rounding to apply to all of the rectangle's
+     * corners, in pixels. Each corner can have a different radius.
+     *
+     * default: ``0``
+     *
+     * @see ::corners
+     */
+    Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
+
+    /**
+     * @brief This property holds the rectangle's color.
+     *
+     * Full RGBA colors are supported.
+     *
+     * default: ``Qt::white``
+     */
+    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
+
+    /**
+     * @brief This property holds the border's grouped property.
+     *
+     * Example usage:
+     * @code
+     * Kirigami.ShadowedRectangle {
+     *     border.width: 2
+     *     border.color: Kirigami.Theme.textColor
+     * }
+     * @endcode
+     * @see kirigami::BorderGroup
+     */
+    Q_PROPERTY(BorderGroup *border READ border CONSTANT)
+
+    /**
+     * @brief This property holds the shadow's grouped property.
+     *
+     * Example usage:
+     * @code
+     * Kirigami.ShadowedRectangle {
+     *     shadow.size: 20
+     *     shadow.xOffset: 5
+     *     shadow.yOffset: 5
+     * }
+     * @endcode
+     *
+     * @see kirigami::ShadowGroup
+     */
+    Q_PROPERTY(ShadowGroup *shadow READ shadow CONSTANT)
+
+    /**
+     * @brief This property holds the corners grouped property
+     *
+     * Note that the values from this group override \property radius for the
+     * corner they affect.
+     *
+     * Example usage:
+     * @code
+     * Kirigami.ShadowedRectangle {
+     *     corners.topLeftRadius: 4
+     *     corners.topRightRadius: 5
+     *     corners.bottomLeftRadius: 2
+     *     corners.bottomRightRadius: 10
+     * @endcode
+     *
+     * @see kirigami::CornersGroup
+     */
+    Q_PROPERTY(CornersGroup *corners READ corners CONSTANT)
+
+    /**
+     * @brief This property holds the rectangle's render mode.
+     *
+     * default: ``RenderType.Auto``
+     *
+     * @see ::RenderType
+     */
+    Q_PROPERTY(RenderType renderType READ renderType WRITE setRenderType CONSTANT)
+
+    /**
+     * @brief This property specifies whether software rendering is being used.
+     *
+     * default: ``false``
+     */
+    Q_PROPERTY(bool softwareRendering READ isSoftwareRendering NOTIFY softwareRenderingChanged)
+
+public:
+    ShadowedRectangle(QQuickItem *parent = nullptr);
+    ~ShadowedRectangle() override;
+
+    /**
+     * @brief Available rendering types for ShadowedRectangle.
+     */
+    enum RenderType {
+
+        /**
+         * @brief Automatically determine the optimal rendering type.
+         *
+         * This will use the highest rendering quality possible, falling back to
+         * lower quality if the hardware doesn't support it. It will use software
+         * rendering if the QtQuick scene graph is set to use software rendering.
+         */
+        Auto,
+
+        /**
+         * @brief Use the highest rendering quality possible, even if the hardware might
+         * not be able to handle it normally.
+         */
+        HighQuality,
+
+        /**
+         * @brief Use the lowest rendering quality, even if the hardware could handle
+         * higher quality rendering.
+         *
+         * This might result in certain effects being omitted, like shadows.
+         */
+        LowQuality,
+
+        /**
+         * @brief Always use software rendering for this rectangle.
+         *
+         * Software rendering is intended as a fallback when the QtQuick scene
+         * graph is configured to use software rendering. It will result in
+         * a number of missing features, like shadows and multiple corner radii.
+         */
+        Software
+    };
+    Q_ENUM(RenderType)
+
+    BorderGroup *border() const;
+    ShadowGroup *shadow() const;
+    CornersGroup *corners() const;
+
+    qreal radius() const;
+    void setRadius(qreal newRadius);
+    Q_SIGNAL void radiusChanged();
+
+    QColor color() const;
+    void setColor(const QColor &newColor);
+    Q_SIGNAL void colorChanged();
+
+    RenderType renderType() const;
+    void setRenderType(RenderType renderType);
+    Q_SIGNAL void renderTypeChanged();
+
+    void componentComplete() override;
+
+    bool isSoftwareRendering() const;
+
+Q_SIGNALS:
+    void softwareRenderingChanged();
+
+protected:
+    PaintedRectangleItem *softwareItem() const;
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
+    QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override;
+
+private:
+    void checkSoftwareItem();
+    const std::unique_ptr<BorderGroup> m_border;
+    const std::unique_ptr<ShadowGroup> m_shadow;
+    const std::unique_ptr<CornersGroup> m_corners;
+    qreal m_radius = 0.0;
+    QColor m_color = Qt::white;
+    RenderType m_renderType = RenderType::Auto;
+    PaintedRectangleItem *m_softwareItem = nullptr;
+};
diff --git a/src/shadowedtexture.cpp b/src/shadowedtexture.cpp
new file mode 100644 (file)
index 0000000..0fe611a
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "shadowedtexture.h"
+
+#include <QQuickWindow>
+#include <QSGRectangleNode>
+#include <QSGRendererInterface>
+
+#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include "scenegraph/shadowedtexturenode.h"
+#endif
+
+ShadowedTexture::ShadowedTexture(QQuickItem *parentItem)
+    : ShadowedRectangle(parentItem)
+{
+}
+
+ShadowedTexture::~ShadowedTexture()
+{
+}
+
+QQuickItem *ShadowedTexture::source() const
+{
+    return m_source;
+}
+
+void ShadowedTexture::setSource(QQuickItem *newSource)
+{
+    if (newSource == m_source) {
+        return;
+    }
+
+    m_source = newSource;
+    m_sourceChanged = true;
+    if (m_source && !m_source->parentItem()) {
+        m_source->setParentItem(this);
+    }
+
+    if (!isSoftwareRendering()) {
+        update();
+    }
+    Q_EMIT sourceChanged();
+}
+
+QSGNode *ShadowedTexture::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
+{
+    Q_UNUSED(data)
+#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+
+    auto shadowNode = static_cast<ShadowedRectangleNode *>(node);
+
+    if (!shadowNode || m_sourceChanged) {
+        m_sourceChanged = false;
+        delete shadowNode;
+        if (m_source) {
+            shadowNode = new ShadowedTextureNode{};
+        } else {
+            shadowNode = new ShadowedRectangleNode{};
+        }
+
+        if (qEnvironmentVariableIsSet("KIRIGAMI_LOWPOWER_HARDWARE")) {
+            shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower);
+        }
+    }
+
+    shadowNode->setBorderEnabled(border()->isEnabled());
+    shadowNode->setRect(boundingRect());
+    shadowNode->setSize(shadow()->size());
+    shadowNode->setRadius(corners()->toVector4D(radius()));
+    shadowNode->setOffset(QVector2D{float(shadow()->xOffset()), float(shadow()->yOffset())});
+    shadowNode->setColor(color());
+    shadowNode->setShadowColor(shadow()->color());
+    shadowNode->setBorderWidth(border()->width());
+    shadowNode->setBorderColor(border()->color());
+
+    if (m_source) {
+        static_cast<ShadowedTextureNode *>(shadowNode)->setTextureSource(m_source->textureProvider());
+    }
+
+    shadowNode->updateGeometry();
+    return shadowNode;
+#else
+    Q_UNUSED(node)
+    return nullptr;
+#endif
+}
diff --git a/src/shadowedtexture.h b/src/shadowedtexture.h
new file mode 100644 (file)
index 0000000..0e0743f
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "shadowedrectangle.h"
+
+/**
+ * @brief A rectangle with a shadow, using a QQuickItem as texture.
+ *
+ * This item will render a source item, with a shadow below it. The rendering is done
+ * using distance fields, which provide greatly improved performance. The shadow is
+ * rendered outside of the item's bounds, so the item's width and height are the
+ * rectangle's width and height.
+ *
+ * @since KDE Frameworks 5.69
+ * @since org.kde.kirigami 2.12
+ */
+class ShadowedTexture : public ShadowedRectangle
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the source item that will get rendered with the
+     * shadow.
+     */
+    Q_PROPERTY(QQuickItem *source READ source WRITE setSource NOTIFY sourceChanged)
+
+public:
+    ShadowedTexture(QQuickItem *parent = nullptr);
+    ~ShadowedTexture() override;
+
+    QQuickItem *source() const;
+    void setSource(QQuickItem *newSource);
+    Q_SIGNAL void sourceChanged();
+
+protected:
+    QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override;
+
+private:
+    QQuickItem *m_source = nullptr;
+    bool m_sourceChanged = false;
+};
diff --git a/src/sizegroup.cpp b/src/sizegroup.cpp
new file mode 100644 (file)
index 0000000..d48e5cc
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include <QQmlProperty>
+
+#include "sizegroup.h"
+
+#define pThis (static_cast<SizeGroup *>(prop->object))
+
+void SizeGroup::appendItem(QQmlListProperty<QQuickItem> *prop, QQuickItem *value)
+{
+    pThis->m_items << value;
+    pThis->connectItem(value);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+int SizeGroup::itemCount(QQmlListProperty<QQuickItem> *prop)
+#else
+qsizetype SizeGroup::itemCount(QQmlListProperty<QQuickItem> *prop)
+#endif
+{
+    return pThis->m_items.count();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QQuickItem *SizeGroup::itemAt(QQmlListProperty<QQuickItem> *prop, int index)
+#else
+QQuickItem *SizeGroup::itemAt(QQmlListProperty<QQuickItem> *prop, qsizetype index)
+#endif
+{
+    return pThis->m_items[index];
+}
+
+void SizeGroup::clearItems(QQmlListProperty<QQuickItem> *prop)
+{
+    for (const auto &item : std::as_const(pThis->m_items)) {
+        QObject::disconnect(pThis->m_connections[item].first);
+        QObject::disconnect(pThis->m_connections[item].second);
+    }
+    pThis->m_items.clear();
+}
+
+void SizeGroup::connectItem(QQuickItem *item)
+{
+    auto conn1 = connect(item, &QQuickItem::implicitWidthChanged, this, [this]() {
+        adjustItems(Mode::Width);
+    });
+    auto conn2 = connect(item, &QQuickItem::implicitHeightChanged, this, [this]() {
+        adjustItems(Mode::Height);
+    });
+    m_connections[item] = qMakePair(conn1, conn2);
+    adjustItems(m_mode);
+}
+
+QQmlListProperty<QQuickItem> SizeGroup::items()
+{
+    return QQmlListProperty<QQuickItem>(this, //
+                                        nullptr,
+                                        appendItem,
+                                        itemCount,
+                                        itemAt,
+                                        clearItems);
+}
+
+void SizeGroup::relayout()
+{
+    adjustItems(Mode::Both);
+}
+
+void SizeGroup::componentComplete()
+{
+    adjustItems(Mode::Both);
+}
+
+void SizeGroup::adjustItems(Mode whatChanged)
+{
+    if (m_mode == Mode::Width && whatChanged == Mode::Height) {
+        return;
+    }
+    if (m_mode == Mode::Height && whatChanged == Mode::Width) {
+        return;
+    }
+
+    qreal maxHeight = 0.0;
+    qreal maxWidth = 0.0;
+
+    for (const auto &item : std::as_const(m_items)) {
+        if (item == nullptr) {
+            continue;
+        }
+
+        switch (m_mode) {
+        case Mode::Width:
+            maxWidth = qMax(maxWidth, item->implicitWidth());
+            break;
+        case Mode::Height:
+            maxHeight = qMax(maxHeight, item->implicitHeight());
+            break;
+        case Mode::Both:
+            maxWidth = qMax(maxWidth, item->implicitWidth());
+            maxHeight = qMax(maxHeight, item->implicitHeight());
+            break;
+        case Mode::None:
+            break;
+        }
+    }
+
+    for (const auto &item : std::as_const(m_items)) {
+        if (item == nullptr) {
+            continue;
+        }
+
+        if (!qmlEngine(item) || !qmlContext(item)) {
+            continue;
+        }
+
+        switch (m_mode) {
+        case Mode::Width:
+            QQmlProperty(item, QStringLiteral("Layout.preferredWidth"), qmlContext(item)).write(maxWidth);
+            break;
+        case Mode::Height:
+            QQmlProperty(item, QStringLiteral("Layout.preferredHeight"), qmlContext(item)).write(maxHeight);
+            break;
+        case Mode::Both:
+            QQmlProperty(item, QStringLiteral("Layout.preferredWidth"), qmlContext(item)).write(maxWidth);
+            QQmlProperty(item, QStringLiteral("Layout.preferredHeight"), qmlContext(item)).write(maxHeight);
+            break;
+        case Mode::None:
+            break;
+        }
+    }
+}
diff --git a/src/sizegroup.h b/src/sizegroup.h
new file mode 100644 (file)
index 0000000..ae3cb0f
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QMap>
+#include <QObject>
+#include <QPair>
+#include <QPointer>
+#include <QQmlListProperty>
+#include <QQmlParserStatus>
+#include <QQuickItem>
+
+/**
+ * @brief SizeGroup is a utility object that makes groups of items request the
+ * same size.
+ *
+ * This can be instantiated to automatically manage the height, width, or both
+ * sizes of multiple items based on the item with the highest value. In other
+ * words, if widths are synchronized, all items being managed by a SizeGroup
+ * will have the same preferredWidth as the item with the largest implicitWidth.
+ *
+ * Pass a JavaScript array of ::items to be managed by this object, then set the
+ * ::mode property to define which size to synchronize.
+ *
+ * @note Manually setting a width or height for items managed by a SizeGroup
+ * will override the width or height calculated by the instantiated SizeGroup.
+ *
+ * @note All objects managed by a SizeGroup must belong to a Layout. This
+ * includes Kirigami-specific Layouts such as org::kde::kirigami::FormLayout.
+ *
+ * @include sizegroup.qml
+ */
+class SizeGroup : public QObject, public QQmlParserStatus
+{
+    Q_OBJECT
+    Q_INTERFACES(QQmlParserStatus)
+
+public:
+    enum Mode {
+        /**
+         * @brief SizeGroup does nothing.
+         */
+        None = 0,
+
+        /**
+         * @brief SizeGroup syncs item widths.
+         */
+        Width = 1,
+
+        /**
+         * @brief SizeGroup syncs item heights.
+         */
+        Height = 2,
+
+        /**
+         * @brief SizeGroup syncs both item widths and heights
+         */
+        Both = 3,
+    };
+    Q_ENUM(Mode)
+    Q_DECLARE_FLAGS(Modes, Mode)
+
+private:
+    Mode m_mode = None;
+    QList<QPointer<QQuickItem>> m_items;
+    QMap<QQuickItem *, QPair<QMetaObject::Connection, QMetaObject::Connection>> m_connections;
+
+public:
+    /**
+     * @brief This property sets which dimensions this SizeGroup should sync.
+     */
+    Q_PROPERTY(Mode mode MEMBER m_mode NOTIFY modeChanged)
+    Q_SIGNAL void modeChanged();
+
+    /**
+     * @brief This property holds a list of items this SizeGroup should adjust.
+     */
+    Q_PROPERTY(QQmlListProperty<QQuickItem> items READ items CONSTANT)
+    QQmlListProperty<QQuickItem> items();
+
+    void adjustItems(Mode whatChanged);
+    void connectItem(QQuickItem *item);
+
+    /**
+     * @brief This method forces the SizeGroup to relayout its items.
+     *
+     * Normally this is never needed as the SizeGroup automatically relayouts
+     * items as they're added and their sizes change.
+     */
+    Q_INVOKABLE void relayout();
+
+    void classBegin() override
+    {
+    }
+    void componentComplete() override;
+
+private:
+    static void appendItem(QQmlListProperty<QQuickItem> *prop, QQuickItem *value);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    static int itemCount(QQmlListProperty<QQuickItem> *prop);
+    static QQuickItem *itemAt(QQmlListProperty<QQuickItem> *prop, int index);
+#else
+    static qsizetype itemCount(QQmlListProperty<QQuickItem> *prop);
+    static QQuickItem *itemAt(QQmlListProperty<QQuickItem> *prop, qsizetype index);
+#endif
+    static void clearItems(QQmlListProperty<QQuickItem> *prop);
+};
diff --git a/src/spellcheckinghint.cpp b/src/spellcheckinghint.cpp
new file mode 100644 (file)
index 0000000..843aa59
--- /dev/null
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "spellcheckinghint.h"
+#include <QQuickItem>
+
+SpellCheckingAttached::SpellCheckingAttached(QObject *parent)
+    : QObject(parent)
+{
+}
+
+SpellCheckingAttached::~SpellCheckingAttached()
+{
+}
+
+void SpellCheckingAttached::setEnabled(bool enabled)
+{
+    if (enabled == m_enabled) {
+        return;
+    }
+
+    m_enabled = enabled;
+    Q_EMIT enabledChanged();
+}
+
+bool SpellCheckingAttached::enabled() const
+{
+    return m_enabled;
+}
+
+SpellCheckingAttached *SpellCheckingAttached::qmlAttachedProperties(QObject *object)
+{
+    return new SpellCheckingAttached(object);
+}
diff --git a/src/spellcheckinghint.h b/src/spellcheckinghint.h
new file mode 100644 (file)
index 0000000..c900a05
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <QObject>
+#include <QtQml>
+
+/**
+ * @brief This attached property contains hints for enabling spell checking.
+ *
+ * @warning Kirigami doesn't provide the spell checking, this is just an hint
+ * for the theme. If you want to add spell checking to your custom application
+ * theme checkout \ref Sonnet.
+ *
+ * @code
+ * import org.kde.kirigami 2.18 as Kirigami
+ * TextArea {
+ *    Kirigami.SpellChecking.enabled: true
+ * }
+ * @endcode
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @since org.kde.kirigami 2.18
+ */
+class SpellCheckingAttached : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds whether the spell checking should be enabled on the
+     * TextField/TextArea.
+     *
+     * @note By default, false. This might change in KF6, so if you don't want
+     * spellchecking on your application, explicitly set it to false.
+     *
+     * @since org.kde.kirigami 2.18
+     */
+    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+public:
+    explicit SpellCheckingAttached(QObject *parent = nullptr);
+    ~SpellCheckingAttached() override;
+
+    void setEnabled(bool enabled);
+    bool enabled() const;
+
+    // QML attached property
+    static SpellCheckingAttached *qmlAttachedProperties(QObject *object);
+
+Q_SIGNALS:
+    void enabledChanged();
+
+private:
+    bool m_enabled = false;
+};
+
+QML_DECLARE_TYPEINFO(SpellCheckingAttached, QML_HAS_ATTACHED_PROPERTIES)
diff --git a/src/styles/Material/AbstractListItem.qml b/src/styles/Material/AbstractListItem.qml
new file mode 100644 (file)
index 0000000..be287a7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import org.kde.kirigami 2.4 as Kirigami
+import QtQuick.Controls.Material 2.1 as Mat
+import QtQuick.Controls.Material.impl 2.1 as MatImp
+import "../../private" as P
+import "../../templates" as T
+
+T.AbstractListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {
+
+        MatImp.Ripple {
+            anchors.fill: parent
+            clip: visible
+            visible: listItem.supportsMouseEvents
+            pressed: listItem.pressed
+            anchor: listItem
+            active: listItem.down || listItem.visualFocus
+            color: Qt.rgba(0,0,0,0.2)
+        }
+    }
+    implicitHeight: contentItem.implicitHeight + Kirigami.Units.smallSpacing * 6
+}
diff --git a/src/styles/Material/InlineMessage.qml b/src/styles/Material/InlineMessage.qml
new file mode 100644 (file)
index 0000000..398bc64
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
+ *  SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
+ *  SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtGraphicalEffects 1.0 as GE
+import org.kde.kirigami 2.5 as Kirigami
+import "../../templates" as T
+
+T.InlineMessage {
+    id: root
+
+    background: Rectangle {
+        id: bgBorderRect
+
+        color: {
+            if (root.type == Kirigami.MessageType.Positive) {
+                return Kirigami.Theme.positiveTextColor;
+            } else if (root.type == Kirigami.MessageType.Warning) {
+                return Kirigami.Theme.neutralTextColor;
+            } else if (root.type == Kirigami.MessageType.Error) {
+                return Kirigami.Theme.negativeTextColor;
+            }
+
+            return Kirigami.Theme.activeTextColor;
+        }
+
+        radius: Kirigami.Units.smallSpacing / 2
+
+        Rectangle {
+            id: bgFillRect
+
+            anchors.fill: parent
+            anchors.margins: 1
+
+            color: Kirigami.Theme.backgroundColor
+
+            radius: bgBorderRect.radius * 0.60
+        }
+
+        Rectangle {
+            anchors.fill: bgFillRect
+
+            color: bgBorderRect.color
+
+            opacity: 0.20
+
+            radius: bgFillRect.radius
+        }
+
+        layer.enabled: true
+        layer.effect: GE.DropShadow {
+            horizontalOffset: 0
+            verticalOffset: 1
+            radius: 12
+            samples: 32
+            color: Qt.rgba(0, 0, 0, 0.5)
+        }
+    }
+}
diff --git a/src/styles/Material/Label.qml b/src/styles/Material/Label.qml
new file mode 100644 (file)
index 0000000..ec03894
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ *  SPDX-FileCopyrightText: 2011 by Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.1
+import QtQuick.Controls 2.0 as QQC2
+
+QQC2.Label {
+    verticalAlignment: lineCount > 1 ? Text.AlignTop : Text.AlignVCenter
+
+    activeFocusOnTab: false
+
+    Component.onCompleted: {
+        console.warn("Kirigami.Label is deprecated. Use QQC2.Label instead")
+    }
+}
diff --git a/src/styles/Material/SwipeListItem.qml b/src/styles/Material/SwipeListItem.qml
new file mode 100644 (file)
index 0000000..878d240
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *  SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.4 as Kirigami
+import QtQuick.Controls.Material 2.1 as Mat
+import QtQuick.Controls.Material.impl 2.1 as MatImp
+import "../../private" as P
+import "../../templates" as T
+
+T.SwipeListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {
+        MatImp.Ripple {
+            anchors.fill: parent
+            clip: visible
+            pressed: listItem.pressed
+            anchor: listItem
+            active: listItem.down || listItem.visualFocus
+            color: Qt.rgba(0,0,0,0.2)
+        }
+    }
+    implicitHeight: contentItem.implicitHeight + Kirigami.Units.smallSpacing * 6
+}
diff --git a/src/styles/Material/Theme.qml b/src/styles/Material/Theme.qml
new file mode 100644 (file)
index 0000000..7d93999
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls.Material 2.0
+import org.kde.kirigami 2.16 as Kirigami
+
+/**
+ * \internal
+ */
+Kirigami.BasicThemeDefinition {
+    id: theme
+    //NOTE: this is useless per se, but it forces the Material attached property to be created
+    Material.elevation: 2
+
+    textColor: theme.Material.foreground
+    disabledTextColor: Qt.rgba(theme.textColor.r, theme.textColor.g, theme.textColor.b, 0.6)
+
+    highlightColor: theme.Material.accent
+    //FIXME: something better?
+    highlightedTextColor: theme.Material.background
+    backgroundColor: theme.Material.background
+    alternateBackgroundColor: Qt.darker(theme.Material.background, 1.05)
+
+    hoverColor: theme.Material.highlightedButtonColor
+    focusColor: theme.Material.highlightedButtonColor
+
+    activeTextColor: theme.Material.primary
+    activeBackgroundColor: theme.Material.primary
+    linkColor: "#2980B9"
+    linkBackgroundColor: "#2980B9"
+    visitedLinkColor: "#7F8C8D"
+    visitedLinkBackgroundColor: "#7F8C8D"
+    negativeTextColor: "#DA4453"
+    negativeBackgroundColor: "#DA4453"
+    neutralTextColor: "#F67400"
+    neutralBackgroundColor: "#F67400"
+    positiveTextColor: "#27AE60"
+    positiveBackgroundColor: "#27AE60"
+
+    buttonTextColor: theme.Material.foreground
+    buttonBackgroundColor: theme.Material.buttonColor
+    buttonAlternateBackgroundColor: Qt.darker(theme.Material.buttonColor, 1.05)
+    buttonHoverColor: theme.Material.highlightedButtonColor
+    buttonFocusColor: theme.Material.highlightedButtonColor
+
+    viewTextColor: theme.Material.foreground
+    viewBackgroundColor: theme.Material.dialogColor
+    viewAlternateBackgroundColor: Qt.darker(theme.Material.dialogColor, 1.05)
+    viewHoverColor: theme.Material.listHighlightColor
+    viewFocusColor: theme.Material.listHighlightColor
+
+    selectionTextColor: theme.Material.primaryHighlightedTextColor
+    selectionBackgroundColor: theme.Material.textSelectionColor
+    selectionAlternateBackgroundColor: Qt.darker(theme.Material.textSelectionColor, 1.05)
+    selectionHoverColor: theme.Material.highlightedButtonColor
+    selectionFocusColor: theme.Material.highlightedButtonColor
+
+    tooltipTextColor: fontMetrics.Material.foreground
+    tooltipBackgroundColor: fontMetrics.Material.tooltipColor
+    tooltipAlternateBackgroundColor: Qt.darker(theme.Material.tooltipColor, 1.05)
+    tooltipHoverColor: fontMetrics.Material.highlightedButtonColor
+    tooltipFocusColor: fontMetrics.Material.highlightedButtonColor
+
+    complementaryTextColor: fontMetrics.Material.foreground
+    complementaryBackgroundColor: fontMetrics.Material.background
+    complementaryAlternateBackgroundColor: Qt.lighter(fontMetrics.Material.background, 1.05)
+    complementaryHoverColor: theme.Material.highlightedButtonColor
+    complementaryFocusColor: theme.Material.highlightedButtonColor
+
+    headerTextColor: fontMetrics.Material.primaryTextColor
+    headerBackgroundColor: fontMetrics.Material.primaryColor
+    headerAlternateBackgroundColor: Qt.lighter(fontMetrics.Material.primaryColor, 1.05)
+    headerHoverColor: theme.Material.highlightedButtonColor
+    headerFocusColor: theme.Material.highlightedButtonColor
+
+    defaultFont: fontMetrics.font
+
+    property list<QtObject> children: [
+        TextMetrics {
+            id: fontMetrics
+            //this is to get a source of dark colors
+            Material.theme: Material.Dark
+        }
+    ]
+
+    onSync: object => {
+        //TODO: actually check if it's a dark or light color
+        if (object.Kirigami.Theme.colorSet === Kirigami.Theme.Complementary) {
+            object.Material.theme = Material.Dark
+        } else {
+            object.Material.theme = Material.Light
+        }
+
+        object.Material.foreground = object.Kirigami.Theme.textColor
+        object.Material.background = object.Kirigami.Theme.backgroundColor
+        object.Material.primary = object.Kirigami.Theme.highlightColor
+        object.Material.accent = object.Kirigami.Theme.highlightColor
+    }
+}
diff --git a/src/styles/org.kde.desktop/AbstractApplicationHeader.qml b/src/styles/org.kde.desktop/AbstractApplicationHeader.qml
new file mode 100644 (file)
index 0000000..63044fb
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.5
+import org.kde.kirigami 2.4 as Kirigami
+import "../../templates" as T
+
+T.AbstractApplicationHeader {
+    id: root
+
+    // Always use header bg color for toolbar (if available), even if the page
+    // it's located on uses a different color set
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: Kirigami.Theme.Header
+
+    background: Rectangle {
+        color: Kirigami.Theme.backgroundColor
+        Kirigami.Separator {
+            visible: root.separatorVisible && (!root.page || !root.page.header || !root.page.header.visible || root.page.header.toString().indexOf("ToolBar") === -1)
+            anchors {
+                left: parent.left
+                right: parent.right
+                bottom: root.y <= 0 ? parent.bottom : undefined
+                top: root.y <= 0 ? undefined :  parent.top
+            }
+        }
+    }
+}
diff --git a/src/styles/org.kde.desktop/AbstractListItem.qml b/src/styles/org.kde.desktop/AbstractListItem.qml
new file mode 100644 (file)
index 0000000..46db3f7
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "../../private" as P
+import "../../templates" as T
+
+T.AbstractListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {}
+}
diff --git a/src/styles/org.kde.desktop/SwipeListItem.qml b/src/styles/org.kde.desktop/SwipeListItem.qml
new file mode 100644 (file)
index 0000000..d0b2e86
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <notmart@gmail.com>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import "../../private" as P
+import "../../templates" as T
+
+T.SwipeListItem {
+    id: listItem
+
+    background: P.DefaultListItemBackground {}
+}
diff --git a/src/styles/org.kde.desktop/Theme.qml b/src/styles/org.kde.desktop/Theme.qml
new file mode 100644 (file)
index 0000000..a6e8324
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *  SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.4
+import org.kde.kirigami 2.16 as Kirigami
+
+pragma Singleton
+
+Kirigami.BasicThemeDefinition {
+    id: theme
+
+    textColor: palette.windowText
+    disabledTextColor: disabledPalette.windowText
+
+    highlightColor: palette.highlight
+    highlightedTextColor: palette.highlightedText
+    backgroundColor: palette.window
+    alternateBackgroundColor: Qt.darker(palette.window, 1.05)
+    activeTextColor: palette.highlight
+    activeBackgroundColor: palette.highlight
+    linkColor: "#2980B9"
+    linkBackgroundColor: "#2980B9"
+    visitedLinkColor: "#7F8C8D"
+    visitedLinkBackgroundColor: "#7F8C8D"
+    hoverColor: palette.highlight
+    focusColor: palette.highlight
+    negativeTextColor: "#DA4453"
+    negativeBackgroundColor: "#DA4453"
+    neutralTextColor: "#F67400"
+    neutralBackgroundColor: "#F67400"
+    positiveTextColor: "#27AE60"
+    positiveBackgroundColor: "#27AE60"
+
+    buttonTextColor: palette.buttonText
+    buttonBackgroundColor: palette.button
+    buttonAlternateBackgroundColor: Qt.darker(palette.button, 1.05)
+    buttonHoverColor: palette.highlight
+    buttonFocusColor: palette.highlight
+
+    viewTextColor: palette.text
+    viewBackgroundColor: palette.base
+    viewAlternateBackgroundColor: palette.alternateBase
+    viewHoverColor: palette.highlight
+    viewFocusColor: palette.highlight
+
+    selectionTextColor: palette.highlightedText
+    selectionBackgroundColor: palette.highlight
+    selectionAlternateBackgroundColor: Qt.darker(palette.highlight, 1.05)
+    selectionHoverColor: palette.highlight
+    selectionFocusColor: palette.highlight
+
+    tooltipTextColor: palette.base
+    tooltipBackgroundColor: palette.text
+    tooltipAlternateBackgroundColor: Qt.darker(palette.text, 1.05)
+    tooltipHoverColor: palette.highlight
+    tooltipFocusColor: palette.highlight
+
+    complementaryTextColor: palette.base
+    complementaryBackgroundColor: palette.text
+    complementaryAlternateBackgroundColor: Qt.darker(palette.text, 1.05)
+    complementaryHoverColor: palette.highlight
+    complementaryFocusColor: palette.highlight
+
+    headerTextColor: palette.text
+    headerBackgroundColor: palette.base
+    headerAlternateBackgroundColor: palette.alternateBase
+    headerHoverColor: palette.highlight
+    headerFocusColor: palette.highlight
+
+    property font defaultFont: fontMetrics.font
+
+    property list<QtObject> children: [
+        TextMetrics {
+            id: fontMetrics
+        },
+        SystemPalette {
+            id: palette
+            colorGroup: SystemPalette.Active
+        },
+        SystemPalette {
+            id: disabledPalette
+            colorGroup: SystemPalette.Disabled
+        }
+    ]
+
+    function __propagateColorSet(object, context) {}
+
+    function __propagateTextColor(object, color) {}
+    function __propagateBackgroundColor(object, color) {}
+    function __propagatePrimaryColor(object, color) {}
+    function __propagateAccentColor(object, color) {}
+}
diff --git a/src/toolbarlayout.cpp b/src/toolbarlayout.cpp
new file mode 100644 (file)
index 0000000..f552c1a
--- /dev/null
@@ -0,0 +1,720 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+#include "toolbarlayout.h"
+
+#include <cmath>
+#include <unordered_map>
+
+#include <QDeadlineTimer>
+#include <QQmlComponent>
+#include <QTimer>
+
+#include "enums.h"
+#include "loggingcategory.h"
+#include "toolbarlayoutdelegate.h"
+
+ToolBarLayoutAttached::ToolBarLayoutAttached(QObject *parent)
+    : QObject(parent)
+{
+}
+
+QObject *ToolBarLayoutAttached::action() const
+{
+    return m_action;
+}
+
+void ToolBarLayoutAttached::setAction(QObject *action)
+{
+    m_action = action;
+}
+
+class ToolBarLayout::Private
+{
+public:
+    Private(ToolBarLayout *qq)
+        : q(qq)
+    {
+    }
+
+    void performLayout();
+    QVector<ToolBarLayoutDelegate *> createDelegates();
+    ToolBarLayoutDelegate *createDelegate(QObject *action);
+    qreal layoutStart(qreal layoutWidth);
+    void maybeHideDelegate(int index, qreal &currentWidth, qreal totalWidth);
+
+    ToolBarLayout *q;
+
+    QVector<QObject *> actions;
+    ActionsProperty actionsProperty;
+    QList<QObject *> hiddenActions;
+    QQmlComponent *fullDelegate = nullptr;
+    QQmlComponent *iconDelegate = nullptr;
+    QQmlComponent *moreButton = nullptr;
+    qreal spacing = 0.0;
+    Qt::Alignment alignment = Qt::AlignLeft;
+    qreal visibleWidth = 0.0;
+    Qt::LayoutDirection layoutDirection = Qt::LeftToRight;
+    HeightMode heightMode = ConstrainIfLarger;
+
+    bool completed = false;
+    bool layoutQueued = false;
+    bool actionsChanged = false;
+    std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
+    QVector<ToolBarLayoutDelegate *> sortedDelegates;
+    QQuickItem *moreButtonInstance = nullptr;
+    ToolBarDelegateIncubator *moreButtonIncubator = nullptr;
+    bool shouldShowMoreButton = false;
+    int firstHiddenIndex = -1;
+
+    QVector<QObject *> removedActions;
+    QTimer *removalTimer = nullptr;
+
+    QElapsedTimer performanceTimer;
+
+    static void appendAction(ToolBarLayout::ActionsProperty *list, QObject *action);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    static int actionCount(ToolBarLayout::ActionsProperty *list);
+    static QObject *action(ToolBarLayout::ActionsProperty *list, int index);
+#else
+    static qsizetype actionCount(ToolBarLayout::ActionsProperty *list);
+    static QObject *action(ToolBarLayout::ActionsProperty *list, qsizetype index);
+#endif
+    static void clearActions(ToolBarLayout::ActionsProperty *list);
+};
+
+ToolBarLayout::ToolBarLayout(QQuickItem *parent)
+    : QQuickItem(parent)
+    , d(std::make_unique<Private>(this))
+{
+    d->actionsProperty = ActionsProperty(this, this, Private::appendAction, Private::actionCount, Private::action, Private::clearActions);
+
+    // To prevent multiple assignments to actions from constantly recreating
+    // delegates, we cache the delegates and only remove them once they are no
+    // longer being used. This timer is responsible for triggering that removal.
+    d->removalTimer = new QTimer{this};
+    d->removalTimer->setInterval(1000);
+    d->removalTimer->setSingleShot(true);
+    connect(d->removalTimer, &QTimer::timeout, this, [this]() {
+        for (auto action : std::as_const(d->removedActions)) {
+            if (!d->actions.contains(action)) {
+                d->delegates.erase(action);
+            }
+        }
+        d->removedActions.clear();
+    });
+}
+
+ToolBarLayout::~ToolBarLayout()
+{
+}
+
+ToolBarLayout::ActionsProperty ToolBarLayout::actionsProperty() const
+{
+    return d->actionsProperty;
+}
+
+void ToolBarLayout::addAction(QObject *action)
+{
+    if (action == nullptr) {
+        return;
+    }
+    d->actions.append(action);
+    d->actionsChanged = true;
+
+    connect(action, &QObject::destroyed, this, [this](QObject *action) {
+        auto itr = d->delegates.find(action);
+        if (itr != d->delegates.end()) {
+            d->delegates.erase(itr);
+        }
+
+        d->actions.removeOne(action);
+        d->actionsChanged = true;
+
+        relayout();
+    });
+
+    relayout();
+}
+
+void ToolBarLayout::removeAction(QObject *action)
+{
+    auto itr = d->delegates.find(action);
+    if (itr != d->delegates.end()) {
+        itr->second->hide();
+    }
+
+    d->actions.removeOne(action);
+    d->removedActions.append(action);
+    d->removalTimer->start();
+    d->actionsChanged = true;
+
+    relayout();
+}
+
+void ToolBarLayout::clearActions()
+{
+    for (auto action : std::as_const(d->actions)) {
+        auto itr = d->delegates.find(action);
+        if (itr != d->delegates.end()) {
+            itr->second->hide();
+        }
+    }
+
+    d->removedActions.append(d->actions);
+    d->actions.clear();
+    d->actionsChanged = true;
+
+    relayout();
+}
+
+QList<QObject *> ToolBarLayout::hiddenActions() const
+{
+    return d->hiddenActions;
+}
+
+QQmlComponent *ToolBarLayout::fullDelegate() const
+{
+    return d->fullDelegate;
+}
+
+void ToolBarLayout::setFullDelegate(QQmlComponent *newFullDelegate)
+{
+    if (newFullDelegate == d->fullDelegate) {
+        return;
+    }
+
+    d->fullDelegate = newFullDelegate;
+    d->delegates.clear();
+    relayout();
+    Q_EMIT fullDelegateChanged();
+}
+
+QQmlComponent *ToolBarLayout::iconDelegate() const
+{
+    return d->iconDelegate;
+}
+
+void ToolBarLayout::setIconDelegate(QQmlComponent *newIconDelegate)
+{
+    if (newIconDelegate == d->iconDelegate) {
+        return;
+    }
+
+    d->iconDelegate = newIconDelegate;
+    d->delegates.clear();
+    relayout();
+    Q_EMIT iconDelegateChanged();
+}
+
+QQmlComponent *ToolBarLayout::moreButton() const
+{
+    return d->moreButton;
+}
+
+void ToolBarLayout::setMoreButton(QQmlComponent *newMoreButton)
+{
+    if (newMoreButton == d->moreButton) {
+        return;
+    }
+
+    d->moreButton = newMoreButton;
+    if (d->moreButtonInstance) {
+        d->moreButtonInstance->deleteLater();
+        d->moreButtonInstance = nullptr;
+    }
+    relayout();
+    Q_EMIT moreButtonChanged();
+}
+
+qreal ToolBarLayout::spacing() const
+{
+    return d->spacing;
+}
+
+void ToolBarLayout::setSpacing(qreal newSpacing)
+{
+    if (newSpacing == d->spacing) {
+        return;
+    }
+
+    d->spacing = newSpacing;
+    relayout();
+    Q_EMIT spacingChanged();
+}
+
+Qt::Alignment ToolBarLayout::alignment() const
+{
+    return d->alignment;
+}
+
+void ToolBarLayout::setAlignment(Qt::Alignment newAlignment)
+{
+    if (newAlignment == d->alignment) {
+        return;
+    }
+
+    d->alignment = newAlignment;
+    relayout();
+    Q_EMIT alignmentChanged();
+}
+
+qreal ToolBarLayout::visibleWidth() const
+{
+    return d->visibleWidth;
+}
+
+qreal ToolBarLayout::minimumWidth() const
+{
+    return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
+}
+
+Qt::LayoutDirection ToolBarLayout::layoutDirection() const
+{
+    return d->layoutDirection;
+}
+
+void ToolBarLayout::setLayoutDirection(Qt::LayoutDirection &newLayoutDirection)
+{
+    if (newLayoutDirection == d->layoutDirection) {
+        return;
+    }
+
+    d->layoutDirection = newLayoutDirection;
+    relayout();
+    Q_EMIT layoutDirectionChanged();
+}
+
+ToolBarLayout::HeightMode ToolBarLayout::heightMode() const
+{
+    return d->heightMode;
+}
+
+void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
+{
+    if (newHeightMode == d->heightMode) {
+        return;
+    }
+
+    d->heightMode = newHeightMode;
+    relayout();
+    Q_EMIT heightModeChanged();
+}
+
+void ToolBarLayout::relayout()
+{
+    if (d->completed) {
+        polish();
+    }
+}
+
+void ToolBarLayout::componentComplete()
+{
+    QQuickItem::componentComplete();
+    d->completed = true;
+    relayout();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+void ToolBarLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+#else
+void ToolBarLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+#endif
+{
+    if (newGeometry != oldGeometry) {
+        relayout();
+    }
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QQuickItem::geometryChanged(newGeometry, oldGeometry);
+#else
+    QQuickItem::geometryChange(newGeometry, oldGeometry);
+#endif
+}
+
+void ToolBarLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
+{
+    if (change == ItemVisibleHasChanged || change == ItemSceneChange) {
+        relayout();
+    }
+    QQuickItem::itemChange(change, data);
+}
+
+void ToolBarLayout::updatePolish()
+{
+    d->performLayout();
+}
+
+void ToolBarLayout::Private::performLayout()
+{
+    if (!fullDelegate || !iconDelegate || !moreButton) {
+        qCWarning(KirigamiLog) << "ToolBarLayout: Unable to layout, required properties are not set";
+        return;
+    }
+
+    if (actions.isEmpty()) {
+        q->setImplicitWidth(0);
+        q->setImplicitHeight(0);
+        return;
+    }
+
+    hiddenActions.clear();
+    firstHiddenIndex = -1;
+
+    sortedDelegates = createDelegates();
+
+    bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](const std::pair<QObject *const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
+        return entry.second->isReady();
+    });
+    if (!ready || !moreButtonInstance) {
+        return;
+    }
+
+    qreal maxHeight = 0.0;
+    qreal maxWidth = 0.0;
+
+    // First, calculate the total width and maximum height of all delegates.
+    // This will be used to determine which actions to show, which ones to
+    // collapse to icon-only etc.
+    for (auto entry : std::as_const(sortedDelegates)) {
+        if (!entry->isActionVisible()) {
+            entry->hide();
+            continue;
+        }
+
+        if (entry->isHidden()) {
+            entry->hide();
+            hiddenActions.append(entry->action());
+            continue;
+        }
+
+        if (entry->isIconOnly()) {
+            entry->showIcon();
+        } else {
+            entry->showFull();
+        }
+
+        maxWidth += entry->width() + spacing;
+        maxHeight = std::max(maxHeight, entry->maxHeight());
+    }
+
+    // The last entry also gets spacing but shouldn't, so remove that.
+    maxWidth -= spacing;
+
+    if (q->heightValid() && q->height() > 0.0) {
+        maxHeight = q->height();
+    }
+
+    qreal visibleActionsWidth = 0.0;
+
+    if (maxWidth > q->width() - (hiddenActions.isEmpty() ? 0.0 : moreButtonInstance->width() + spacing)) {
+        // We have more items than fit into the view, so start hiding some.
+
+        qreal layoutWidth = q->width() - (moreButtonInstance->width() + spacing);
+        if (alignment & Qt::AlignHCenter) {
+            // When centering, we need to reserve space on both sides to make sure
+            // things are properly centered, otherwise we will be to the right of
+            // the center.
+            layoutWidth -= (moreButtonInstance->width() + spacing);
+        }
+
+        for (int i = 0; i < sortedDelegates.size(); ++i) {
+            auto delegate = sortedDelegates.at(i);
+
+            maybeHideDelegate(i, visibleActionsWidth, layoutWidth);
+
+            if (delegate->isVisible()) {
+                visibleActionsWidth += delegate->width() + spacing;
+            }
+        }
+        if (!qFuzzyIsNull(visibleActionsWidth)) {
+            // Like above, remove spacing on the last element that incorrectly gets spacing added.
+            visibleActionsWidth -= spacing;
+        }
+    } else {
+        visibleActionsWidth = maxWidth;
+    }
+
+    if (!hiddenActions.isEmpty()) {
+        if (layoutDirection == Qt::LeftToRight) {
+            moreButtonInstance->setX(q->width() - moreButtonInstance->width());
+        } else {
+            moreButtonInstance->setX(0.0);
+        }
+
+        if (heightMode == AlwaysFill) {
+            moreButtonInstance->setHeight(q->height());
+        } else if (heightMode == ConstrainIfLarger) {
+            if (moreButtonInstance->implicitHeight() > maxHeight) {
+                moreButtonInstance->setHeight(maxHeight);
+            } else {
+                moreButtonInstance->setHeight(moreButtonInstance->implicitHeight());
+            }
+        }
+
+        moreButtonInstance->setY(qRound((maxHeight - moreButtonInstance->height()) / 2.0));
+        shouldShowMoreButton = true;
+        moreButtonInstance->setVisible(true);
+    } else {
+        shouldShowMoreButton = false;
+        moreButtonInstance->setVisible(false);
+    }
+
+    if (moreButtonInstance->isVisible() && !q->heightValid()) {
+        maxHeight = std::max(maxHeight, moreButtonInstance->implicitHeight());
+    };
+
+    qreal currentX = layoutStart(visibleActionsWidth);
+    for (auto entry : std::as_const(sortedDelegates)) {
+        if (!entry->isVisible()) {
+            continue;
+        }
+
+        if (heightMode == AlwaysFill) {
+            entry->setHeight(q->height());
+        } else if (heightMode == ConstrainIfLarger) {
+            if (entry->implicitHeight() > maxHeight) {
+                entry->setHeight(maxHeight);
+            } else {
+                entry->setHeight(entry->implicitHeight());
+            }
+        }
+
+        qreal y = qRound((maxHeight - entry->height()) / 2.0);
+
+        if (layoutDirection == Qt::LeftToRight) {
+            entry->setPosition(currentX, y);
+            currentX += entry->width() + spacing;
+        } else {
+            entry->setPosition(currentX - entry->width(), y);
+            currentX -= entry->width() + spacing;
+        }
+
+        entry->show();
+    }
+
+    q->setImplicitSize(maxWidth, maxHeight);
+    Q_EMIT q->hiddenActionsChanged();
+
+    qreal newVisibleWidth = visibleActionsWidth;
+    if (moreButtonInstance->isVisible()) {
+        newVisibleWidth += moreButtonInstance->width() + (newVisibleWidth > 0.0 ? spacing : 0.0);
+    }
+    if (!qFuzzyCompare(newVisibleWidth, visibleWidth)) {
+        visibleWidth = newVisibleWidth;
+        Q_EMIT q->visibleWidthChanged();
+    }
+
+    if (actionsChanged) {
+        // Due to the way QQmlListProperty works, if we emit changed every time
+        // an action is added/removed, we end up emitting way too often. So
+        // instead only do it after everything else is done.
+        Q_EMIT q->actionsChanged();
+        actionsChanged = false;
+    }
+
+    sortedDelegates.clear();
+}
+
+QVector<ToolBarLayoutDelegate *> ToolBarLayout::Private::createDelegates()
+{
+    QVector<ToolBarLayoutDelegate *> result;
+    for (auto action : std::as_const(actions)) {
+        if (delegates.find(action) != delegates.end()) {
+            result.append(delegates.at(action).get());
+        } else if (action) {
+            auto delegate = std::unique_ptr<ToolBarLayoutDelegate>(createDelegate(action));
+            if (delegate) {
+                result.append(delegate.get());
+                delegates.emplace(action, std::move(delegate));
+            }
+        }
+    }
+
+    if (!moreButtonInstance && !moreButtonIncubator) {
+        moreButtonIncubator = new ToolBarDelegateIncubator(moreButton, qmlContext(moreButton));
+        moreButtonIncubator->setStateCallback([this](QQuickItem *item) {
+            item->setParentItem(q);
+        });
+        moreButtonIncubator->setCompletedCallback([this](ToolBarDelegateIncubator *incubator) {
+            moreButtonInstance = qobject_cast<QQuickItem *>(incubator->object());
+            moreButtonInstance->setVisible(false);
+
+            connect(moreButtonInstance, &QQuickItem::visibleChanged, q, [this]() {
+                moreButtonInstance->setVisible(shouldShowMoreButton);
+            });
+            connect(moreButtonInstance, &QQuickItem::widthChanged, q, [this]() {
+                Q_EMIT q->minimumWidthChanged();
+            });
+            q->relayout();
+            Q_EMIT q->minimumWidthChanged();
+
+            QTimer::singleShot(0, q, [this]() {
+                delete moreButtonIncubator;
+                moreButtonIncubator = nullptr;
+            });
+        });
+        moreButtonIncubator->create();
+    }
+
+    return result;
+}
+
+ToolBarLayoutDelegate *ToolBarLayout::Private::createDelegate(QObject *action)
+{
+    QQmlComponent *fullComponent = nullptr;
+    auto displayComponent = action->property("displayComponent");
+    if (displayComponent.isValid()) {
+        fullComponent = displayComponent.value<QQmlComponent *>();
+    }
+
+    if (!fullComponent) {
+        fullComponent = fullDelegate;
+    }
+
+    auto result = new ToolBarLayoutDelegate(q);
+    result->setAction(action);
+    result->createItems(fullComponent, iconDelegate, [this, action](QQuickItem *newItem) {
+        newItem->setParentItem(q);
+        auto attached = static_cast<ToolBarLayoutAttached *>(qmlAttachedPropertiesObject<ToolBarLayout>(newItem, true));
+        attached->setAction(action);
+    });
+
+    return result;
+}
+
+qreal ToolBarLayout::Private::layoutStart(qreal layoutWidth)
+{
+    qreal availableWidth = moreButtonInstance->isVisible() ? q->width() - (moreButtonInstance->width() + spacing) : q->width();
+
+    if (alignment & Qt::AlignLeft) {
+        return layoutDirection == Qt::LeftToRight ? 0.0 : q->width();
+    } else if (alignment & Qt::AlignHCenter) {
+        return (q->width() / 2) + (layoutDirection == Qt::LeftToRight ? -layoutWidth / 2.0 : layoutWidth / 2.0);
+    } else if (alignment & Qt::AlignRight) {
+        qreal offset = availableWidth - layoutWidth;
+        return layoutDirection == Qt::LeftToRight ? offset : q->width() - offset;
+    }
+    return 0.0;
+}
+
+void ToolBarLayout::Private::maybeHideDelegate(int index, qreal &currentWidth, qreal totalWidth)
+{
+    auto delegate = sortedDelegates.at(index);
+
+    if (!delegate->isVisible()) {
+        // If the delegate isn't visible anyway, do nothing.
+        return;
+    }
+
+    if (currentWidth + delegate->width() < totalWidth && (firstHiddenIndex < 0 || index < firstHiddenIndex)) {
+        // If the delegate is fully visible and we have not already hidden
+        // actions, do nothing.
+        return;
+    }
+
+    if (delegate->isKeepVisible()) {
+        // If the action is marked as KeepVisible, we need to try our best to
+        // keep it in view. If the full size delegate does not fit, we try the
+        // icon-only delegate. If that also does not fit, try and find other
+        // actions to hide. Finally, if that also fails, we will hide the
+        // delegate.
+        if (currentWidth + delegate->iconWidth() > totalWidth) {
+            // First, hide any earlier actions that are not marked as KeepVisible.
+            for (auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
+                auto previousDelegate = sortedDelegates.at(currentIndex);
+                if (!previousDelegate->isVisible() || previousDelegate->isKeepVisible()) {
+                    continue;
+                }
+
+                auto width = previousDelegate->width();
+                previousDelegate->hide();
+                hiddenActions.append(previousDelegate->action());
+                currentWidth -= (width + spacing);
+
+                if (currentWidth + delegate->fullWidth() <= totalWidth) {
+                    delegate->showFull();
+                    break;
+                } else if (currentWidth + delegate->iconWidth() <= totalWidth) {
+                    delegate->showIcon();
+                    break;
+                }
+            }
+
+            if (currentWidth + delegate->width() <= totalWidth) {
+                return;
+            }
+
+            // Hiding normal actions did not help enough, so go through actions
+            // with KeepVisible set and try and collapse them to IconOnly.
+            for (auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
+                auto previousDelegate = sortedDelegates.at(currentIndex);
+                if (!previousDelegate->isVisible() || !previousDelegate->isKeepVisible()) {
+                    continue;
+                }
+
+                auto extraSpace = previousDelegate->width() - previousDelegate->iconWidth();
+                previousDelegate->showIcon();
+                currentWidth -= extraSpace;
+
+                if (currentWidth + delegate->fullWidth() <= totalWidth) {
+                    delegate->showFull();
+                    break;
+                } else if (currentWidth + delegate->iconWidth() <= totalWidth) {
+                    delegate->showIcon();
+                    break;
+                }
+            }
+
+            // If that also did not work, then hide this action after all.
+            if (currentWidth + delegate->width() > totalWidth) {
+                delegate->hide();
+                hiddenActions.append(delegate->action());
+            }
+        } else {
+            delegate->showIcon();
+        }
+    } else {
+        // The action is not marked as KeepVisible and it does not fit within
+        // the current layout, so hide it.
+        delegate->hide();
+        hiddenActions.append(delegate->action());
+
+        // If this is the first item to be hidden, mark it so we know we should
+        // also hide the following items.
+        if (firstHiddenIndex < 0) {
+            firstHiddenIndex = index;
+        }
+    }
+}
+
+void ToolBarLayout::Private::appendAction(ToolBarLayout::ActionsProperty *list, QObject *action)
+{
+    auto layout = reinterpret_cast<ToolBarLayout *>(list->data);
+    layout->addAction(action);
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+int ToolBarLayout::Private::actionCount(ToolBarLayout::ActionsProperty *list)
+#else
+qsizetype ToolBarLayout::Private::actionCount(ToolBarLayout::ActionsProperty *list)
+#endif
+{
+    return reinterpret_cast<ToolBarLayout *>(list->data)->d->actions.count();
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QObject *ToolBarLayout::Private::action(ToolBarLayout::ActionsProperty *list, int index)
+#else
+QObject *ToolBarLayout::Private::action(ToolBarLayout::ActionsProperty *list, qsizetype index)
+#endif
+{
+    return reinterpret_cast<ToolBarLayout *>(list->data)->d->actions.at(index);
+}
+
+void ToolBarLayout::Private::clearActions(ToolBarLayout::ActionsProperty *list)
+{
+    reinterpret_cast<ToolBarLayout *>(list->data)->clearActions();
+}
diff --git a/src/toolbarlayout.h b/src/toolbarlayout.h
new file mode 100644 (file)
index 0000000..3ce2671
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+#ifndef TOOLBARLAYOUT_H
+#define TOOLBARLAYOUT_H
+
+#include <QQuickItem>
+#include <memory>
+
+/**
+ * @brief Attached property for ToolBarLayout delegates.
+ */
+class ToolBarLayoutAttached : public QObject
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds the action that this delegate was created for.
+     */
+    Q_PROPERTY(QObject *action READ action CONSTANT)
+public:
+    ToolBarLayoutAttached(QObject *parent = nullptr);
+
+    QObject *action() const;
+    void setAction(QObject *action);
+
+private:
+    QObject *m_action = nullptr;
+};
+
+/**
+ * @brief This is a layout that creates delegates for actions and lays them out in a row.
+ *
+ * This effectively combines QtQuick.Layouts.RowLayout and QtQuick.Repeater in a single item,
+ * with the addition of some extra performance enhancing tweaks. It will create instances
+ * of ::fullDelegate and ::iconDelegate for each action in ::actions. These are
+ * then positioned horizontally. Any action that ends up being placed outside
+ * the width of the item is hidden and will be part of ::hiddenActions which are
+ * shown in a overflow menu.
+ *
+ * The items created as delegates are always created asynchronously, to avoid
+ * creation lag spikes. Each delegate has access to the action it was created
+ * for through the ToolBarLayoutAttached attached property.
+ */
+class ToolBarLayout : public QQuickItem
+{
+    Q_OBJECT
+    /**
+     * @brief This property holds a list of visible actions this layout should create delegates for.
+     */
+    Q_PROPERTY(QQmlListProperty<QObject> actions READ actionsProperty NOTIFY actionsChanged)
+    /**
+     * @brief This property holds a list of hidden actions that do not fit in the current view,
+     * and are thus hidden.
+     */
+    Q_PROPERTY(QList<QObject *> hiddenActions READ hiddenActions NOTIFY hiddenActionsChanged)
+    /**
+     * @brief This property holds a component that is used to create full-size delegates.
+     *
+     * Each delegate has three states, it can be full-size, icon-only or hidden.
+     * By default, the full-size delegate is used.
+     *
+     * The full-size delegate is used when the action has its display hint set to
+     * @link DisplayHint.KeepVisible KeepVisible @endlink. If there is not enough
+     * space, it will either use iconDelegate or the delegate will be shown in the
+     * overflow menu.
+     *
+     * @see org::kde::kirigami::Action::displayComponent
+     */
+    Q_PROPERTY(QQmlComponent *fullDelegate READ fullDelegate WRITE setFullDelegate NOTIFY fullDelegateChanged)
+    /**
+     * @brief This property holds a component that is used to create icon-only delegates.
+     *
+     * This is used when display hint is set to @link DisplayHint.IconOnly IconOnly @endlink,
+     * unless there is not enough space, in which case it will show the delegate in the
+     * overflow menu.
+     *
+     * @see ::fullDelegate
+     */
+    Q_PROPERTY(QQmlComponent *iconDelegate READ iconDelegate WRITE setIconDelegate NOTIFY iconDelegateChanged)
+    /**
+     * @brief This property holds a component that is used to create the "more button" item from.
+     *
+     * The more button is shown when there are actions that do not fit the
+     * current view. It is intended to have functionality to show these hidden
+     * actions, like popup a menu with them showing.
+     */
+    Q_PROPERTY(QQmlComponent *moreButton READ moreButton WRITE setMoreButton NOTIFY moreButtonChanged)
+    /**
+     * @brief This property holds the amount of spacing between individual delegates.
+     */
+    Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
+    /**
+     * @brief This property sets how to align the delegates within this layout.
+     *
+     * When there is more space available than required by the visible delegates,
+     * we need to determine how to place the delegates. This property determines
+     * how to do that.
+     *
+     * @note The ::moreButton, if visible, will always be placed at the end of the layout.
+     * @see Qt::Alignment
+     */
+    Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged)
+    /**
+     * @brief This property holds the combined width of visible delegates in this layout.
+     */
+    Q_PROPERTY(qreal visibleWidth READ visibleWidth NOTIFY visibleWidthChanged)
+    /**
+     * @brief This property holds the minimum width this layout can have.
+     *
+     * This is equal to the width of the ::moreButton.
+     */
+    Q_PROPERTY(qreal minimumWidth READ minimumWidth NOTIFY minimumWidthChanged)
+    /**
+     * @brief This property sets the layout direction of the toolbar layout.
+     *
+     * This is primarily intended to support right-to-left layouts. When set to
+     * LeftToRight, delegates will be layout with the first item on the left and
+     * following items to the right of that. The more button will be placed at
+     * the rightmost position. Alignment flags work normally.
+     *
+     * When set to RightToLeft, delegates will be layout with the first item on
+     * the right and following items to the left of that. The more button will
+     * be placed at the leftmost position. Alignment flags are inverted, so
+     * AlignLeft will align items to the right, and vice-versa.
+     */
+    Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged)
+    /**
+     * @brief This property sets how to handle items that do not match the toolbar's height.
+     *
+     * When toolbar items do not match the height of the toolbar, there are
+     * several ways we can deal with this. This property sets the preferred way.
+     *
+     * default: ``ToolBarLayout.HeightMode.ConstrainIfLarger``
+     *
+     * @see ::HeightMode
+     */
+    Q_PROPERTY(HeightMode heightMode READ heightMode WRITE setHeightMode NOTIFY heightModeChanged)
+
+public:
+    using ActionsProperty = QQmlListProperty<QObject>;
+
+    /**
+     * @brief This enum describes several modes that can be used to deal with items with
+     * height that does not match the toolbar's height.
+     */
+    enum HeightMode {
+        /**
+         * @brief Always center items, allowing them to go outside the bounds of the layout if they are larger.
+         */
+        AlwaysCenter,
+
+        /**
+         * @brief Always match the height of the layout. Larger items will be reduced in height, smaller items will be increased.
+         */
+        AlwaysFill,
+
+        /**
+         * @brief If the item is larger than the toolbar, reduce its height. Otherwise center it in the toolbar.
+         */
+        ConstrainIfLarger,
+    };
+    Q_ENUM(HeightMode)
+
+    ToolBarLayout(QQuickItem *parent = nullptr);
+    ~ToolBarLayout() override;
+
+    ActionsProperty actionsProperty() const;
+    /**
+     * @brief This method adds an action to the list of actions.
+     *
+     * @param action The action to add.
+     */
+    void addAction(QObject *action);
+    /**
+     * This method  removes an action from the list of actions.
+     *
+     * @param action The action to remove.
+     */
+    void removeAction(QObject *action);
+    /**
+     * @brief This method clears the list of actions.
+     */
+    void clearActions();
+    Q_SIGNAL void actionsChanged();
+
+    QList<QObject *> hiddenActions() const;
+    Q_SIGNAL void hiddenActionsChanged();
+
+    QQmlComponent *fullDelegate() const;
+    void setFullDelegate(QQmlComponent *newFullDelegate);
+    Q_SIGNAL void fullDelegateChanged();
+
+    QQmlComponent *iconDelegate() const;
+    void setIconDelegate(QQmlComponent *newIconDelegate);
+    Q_SIGNAL void iconDelegateChanged();
+
+    QQmlComponent *moreButton() const;
+    void setMoreButton(QQmlComponent *newMoreButton);
+    Q_SIGNAL void moreButtonChanged();
+
+    qreal spacing() const;
+    void setSpacing(qreal newSpacing);
+    Q_SIGNAL void spacingChanged();
+
+    Qt::Alignment alignment() const;
+    void setAlignment(Qt::Alignment newAlignment);
+    Q_SIGNAL void alignmentChanged();
+
+    qreal visibleWidth() const;
+    Q_SIGNAL void visibleWidthChanged();
+
+    qreal minimumWidth() const;
+    Q_SIGNAL void minimumWidthChanged();
+
+    Qt::LayoutDirection layoutDirection() const;
+    void setLayoutDirection(Qt::LayoutDirection &newLayoutDirection);
+    Q_SIGNAL void layoutDirectionChanged();
+
+    HeightMode heightMode() const;
+    void setHeightMode(HeightMode newHeightMode);
+    Q_SIGNAL void heightModeChanged();
+
+    /**
+     * @brief This slot queues a relayout of this ToolBarLayout.
+     *
+     * @note The layouting happens during the next scene graph polishing phase.
+     */
+    Q_SLOT void relayout();
+
+    static ToolBarLayoutAttached *qmlAttachedProperties(QObject *object)
+    {
+        return new ToolBarLayoutAttached(object);
+    }
+
+protected:
+    void componentComplete() override;
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#else
+    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+#endif
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) override;
+    void updatePolish() override;
+
+private:
+    class Private;
+    const std::unique_ptr<Private> d;
+};
+
+QML_DECLARE_TYPEINFO(ToolBarLayout, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // TOOLBARLAYOUT_H
diff --git a/src/toolbarlayoutdelegate.cpp b/src/toolbarlayoutdelegate.cpp
new file mode 100644 (file)
index 0000000..b143b82
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+#include "toolbarlayoutdelegate.h"
+
+#include "loggingcategory.h"
+#include "toolbarlayout.h"
+
+ToolBarDelegateIncubator::ToolBarDelegateIncubator(QQmlComponent *component, QQmlContext *context)
+    : QQmlIncubator(QQmlIncubator::Asynchronous)
+    , m_component(component)
+    , m_context(context)
+{
+}
+
+void ToolBarDelegateIncubator::setStateCallback(std::function<void(QQuickItem *)> callback)
+{
+    m_stateCallback = callback;
+}
+
+void ToolBarDelegateIncubator::setCompletedCallback(std::function<void(ToolBarDelegateIncubator *)> callback)
+{
+    m_completedCallback = callback;
+}
+
+void ToolBarDelegateIncubator::create()
+{
+    m_component->create(*this, m_context);
+}
+
+bool ToolBarDelegateIncubator::isFinished()
+{
+    return m_finished;
+}
+
+void ToolBarDelegateIncubator::setInitialState(QObject *object)
+{
+    auto item = qobject_cast<QQuickItem *>(object);
+    if (item) {
+        m_stateCallback(item);
+    }
+}
+
+void ToolBarDelegateIncubator::statusChanged(QQmlIncubator::Status status)
+{
+    if (status == QQmlIncubator::Error) {
+        qCWarning(KirigamiLog) << "Could not create delegate for ToolBarLayout";
+        const auto e = errors();
+        for (const auto &error : e) {
+            qCWarning(KirigamiLog) << error;
+        }
+        m_finished = true;
+    }
+
+    if (status == QQmlIncubator::Ready) {
+        m_completedCallback(this);
+        m_finished = true;
+    }
+}
+
+ToolBarLayoutDelegate::ToolBarLayoutDelegate(ToolBarLayout *parent)
+    : QObject() // Note: delegates are managed by unique_ptr, so don't parent
+    , m_parent(parent)
+{
+}
+
+ToolBarLayoutDelegate::~ToolBarLayoutDelegate()
+{
+    if (m_fullIncubator) {
+        m_fullIncubator->clear();
+        delete m_fullIncubator;
+    }
+    if (m_iconIncubator) {
+        m_iconIncubator->clear();
+        delete m_iconIncubator;
+    }
+    if (m_full) {
+        m_full->disconnect(this);
+        delete m_full;
+    }
+    if (m_icon) {
+        m_icon->disconnect(this);
+        delete m_icon;
+    }
+}
+
+QObject *ToolBarLayoutDelegate::action() const
+{
+    return m_action;
+}
+
+void ToolBarLayoutDelegate::setAction(QObject *action)
+{
+    if (action == m_action) {
+        return;
+    }
+
+    if (m_action) {
+        QObject::disconnect(m_action, SIGNAL(visibleChanged()), this, SLOT(actionVisibleChanged()));
+        QObject::disconnect(m_action, SIGNAL(displayHintChanged()), this, SLOT(displayHintChanged()));
+    }
+
+    m_action = action;
+    if (m_action) {
+        if (m_action->property("visible").isValid()) {
+            QObject::connect(m_action, SIGNAL(visibleChanged()), this, SLOT(actionVisibleChanged()));
+            m_actionVisible = m_action->property("visible").toBool();
+        }
+
+        if (m_action->property("displayHint").isValid()) {
+            QObject::connect(m_action, SIGNAL(displayHintChanged()), this, SLOT(displayHintChanged()));
+            m_displayHint = DisplayHint::DisplayHints{m_action->property("displayHint").toInt()};
+        }
+    }
+}
+
+void ToolBarLayoutDelegate::createItems(QQmlComponent *fullComponent, QQmlComponent *iconComponent, std::function<void(QQuickItem *)> callback)
+{
+    m_fullIncubator = new ToolBarDelegateIncubator(fullComponent, qmlContext(fullComponent));
+    m_fullIncubator->setStateCallback(callback);
+    m_fullIncubator->setCompletedCallback([this](ToolBarDelegateIncubator *incubator) {
+        if (incubator->isError()) {
+            qCWarning(KirigamiLog) << "Could not create delegate for ToolBarLayout";
+            const auto errors = incubator->errors();
+            for (const auto &error : errors) {
+                qCWarning(KirigamiLog) << error;
+            }
+            return;
+        }
+
+        m_full = qobject_cast<QQuickItem *>(incubator->object());
+        m_full->setVisible(false);
+        connect(m_full, &QQuickItem::widthChanged, this, [this]() {
+            m_parent->relayout();
+        });
+        connect(m_full, &QQuickItem::heightChanged, this, [this]() {
+            m_parent->relayout();
+        });
+        connect(m_full, &QQuickItem::visibleChanged, this, &ToolBarLayoutDelegate::ensureItemVisibility);
+
+        if (m_icon) {
+            m_ready = true;
+        }
+
+        m_parent->relayout();
+
+        QMetaObject::invokeMethod(this, &ToolBarLayoutDelegate::cleanupIncubators, Qt::QueuedConnection);
+    });
+    m_iconIncubator = new ToolBarDelegateIncubator(iconComponent, qmlContext(iconComponent));
+    m_iconIncubator->setStateCallback(callback);
+    m_iconIncubator->setCompletedCallback([this](ToolBarDelegateIncubator *incubator) {
+        if (incubator->isError()) {
+            qCWarning(KirigamiLog) << "Could not create delegate for ToolBarLayout";
+            const auto errors = incubator->errors();
+            for (const auto &error : errors) {
+                qCWarning(KirigamiLog) << error;
+            }
+            return;
+        }
+
+        m_icon = qobject_cast<QQuickItem *>(incubator->object());
+        m_icon->setVisible(false);
+        connect(m_icon, &QQuickItem::widthChanged, this, [this]() {
+            m_parent->relayout();
+        });
+        connect(m_icon, &QQuickItem::heightChanged, this, [this]() {
+            m_parent->relayout();
+        });
+        connect(m_icon, &QQuickItem::visibleChanged, this, &ToolBarLayoutDelegate::ensureItemVisibility);
+
+        if (m_full) {
+            m_ready = true;
+        }
+
+        m_parent->relayout();
+
+        QMetaObject::invokeMethod(this, &ToolBarLayoutDelegate::cleanupIncubators, Qt::QueuedConnection);
+    });
+
+    m_fullIncubator->create();
+    m_iconIncubator->create();
+}
+
+bool ToolBarLayoutDelegate::isReady() const
+{
+    return m_ready;
+}
+
+bool ToolBarLayoutDelegate::isActionVisible() const
+{
+    return m_actionVisible;
+}
+
+bool ToolBarLayoutDelegate::isHidden() const
+{
+    return DisplayHint::isDisplayHintSet(m_displayHint, DisplayHint::AlwaysHide);
+}
+
+bool ToolBarLayoutDelegate::isIconOnly() const
+{
+    return DisplayHint::isDisplayHintSet(m_displayHint, DisplayHint::IconOnly);
+}
+
+bool ToolBarLayoutDelegate::isKeepVisible() const
+{
+    return DisplayHint::isDisplayHintSet(m_displayHint, DisplayHint::KeepVisible);
+}
+
+bool ToolBarLayoutDelegate::isVisible() const
+{
+    return m_iconVisible || m_fullVisible;
+}
+
+void ToolBarLayoutDelegate::hide()
+{
+    m_iconVisible = false;
+    m_fullVisible = false;
+    ensureItemVisibility();
+}
+
+void ToolBarLayoutDelegate::showFull()
+{
+    m_iconVisible = false;
+    m_fullVisible = true;
+}
+
+void ToolBarLayoutDelegate::showIcon()
+{
+    m_iconVisible = true;
+    m_fullVisible = false;
+}
+
+void ToolBarLayoutDelegate::show()
+{
+    ensureItemVisibility();
+}
+
+void ToolBarLayoutDelegate::setPosition(qreal x, qreal y)
+{
+    m_full->setX(x);
+    m_icon->setX(x);
+    m_full->setY(y);
+    m_icon->setY(y);
+}
+
+void ToolBarLayoutDelegate::setHeight(qreal height)
+{
+    m_full->setHeight(height);
+    m_icon->setHeight(height);
+}
+
+qreal ToolBarLayoutDelegate::width() const
+{
+    if (m_iconVisible) {
+        return m_icon->width();
+    }
+    return m_full->width();
+}
+
+qreal ToolBarLayoutDelegate::height() const
+{
+    if (m_iconVisible) {
+        return m_icon->height();
+    }
+    return m_full->height();
+}
+
+qreal ToolBarLayoutDelegate::implicitWidth() const
+{
+    if (m_iconVisible) {
+        return m_icon->implicitWidth();
+    }
+    return m_full->implicitWidth();
+}
+
+qreal ToolBarLayoutDelegate::implicitHeight() const
+{
+    if (m_iconVisible) {
+        return m_icon->implicitHeight();
+    }
+    return m_full->implicitHeight();
+}
+
+qreal ToolBarLayoutDelegate::maxHeight() const
+{
+    return std::max(m_full->height(), m_icon->height());
+}
+
+qreal ToolBarLayoutDelegate::iconWidth() const
+{
+    return m_icon->width();
+}
+
+qreal ToolBarLayoutDelegate::fullWidth() const
+{
+    return m_full->width();
+}
+
+void ToolBarLayoutDelegate::actionVisibleChanged()
+{
+    m_actionVisible = m_action->property("visible").toBool();
+    m_parent->relayout();
+}
+
+void ToolBarLayoutDelegate::displayHintChanged()
+{
+    m_displayHint = DisplayHint::DisplayHints{m_action->property("displayHint").toInt()};
+    m_parent->relayout();
+}
+
+void ToolBarLayoutDelegate::cleanupIncubators()
+{
+    if (m_fullIncubator && m_fullIncubator->isFinished()) {
+        delete m_fullIncubator;
+        m_fullIncubator = nullptr;
+    }
+
+    if (m_iconIncubator && m_iconIncubator->isFinished()) {
+        delete m_iconIncubator;
+        m_iconIncubator = nullptr;
+    }
+}
diff --git a/src/toolbarlayoutdelegate.h b/src/toolbarlayoutdelegate.h
new file mode 100644 (file)
index 0000000..106652a
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+#ifndef TOOLBARLAYOUTDELEGATE_H
+#define TOOLBARLAYOUTDELEGATE_H
+
+#include "enums.h"
+#include <QQmlIncubator>
+#include <QQuickItem>
+
+class ToolBarLayout;
+
+/*
+ * A helper subclass of QQmlIncubator that allows us to do some things more
+ * easily.
+ */
+class ToolBarDelegateIncubator : public QQmlIncubator
+{
+public:
+    ToolBarDelegateIncubator(QQmlComponent *component, QQmlContext *context);
+
+    void setStateCallback(std::function<void(QQuickItem *)> callback);
+    void setCompletedCallback(std::function<void(ToolBarDelegateIncubator *)> callback);
+
+    void create();
+
+    bool isFinished();
+
+private:
+    void setInitialState(QObject *object) override;
+    void statusChanged(QQmlIncubator::Status status) override;
+
+    QQmlComponent *m_component;
+    QQmlContext *m_context;
+    std::function<void(QQuickItem *)> m_stateCallback;
+    std::function<void(ToolBarDelegateIncubator *)> m_completedCallback;
+    bool m_finished = false;
+};
+
+/*
+ * A helper class to encapsulate some of the delegate functionality used by
+ * ToolBarLayout. Primarily, this hides some of the difference that delegates
+ * are two items instead of one.
+ */
+class ToolBarLayoutDelegate : public QObject
+{
+    Q_OBJECT
+public:
+    ToolBarLayoutDelegate(ToolBarLayout *parent);
+    ~ToolBarLayoutDelegate() override;
+
+    QObject *action() const;
+    void setAction(QObject *action);
+    void createItems(QQmlComponent *fullComponent, QQmlComponent *iconComponent, std::function<void(QQuickItem *)> callback);
+
+    bool isReady() const;
+    bool isActionVisible() const;
+    bool isHidden() const;
+    bool isIconOnly() const;
+    bool isKeepVisible() const;
+
+    bool isVisible() const;
+
+    void hide();
+    void showIcon();
+    void showFull();
+    void show();
+
+    void setPosition(qreal x, qreal y);
+    void setHeight(qreal height);
+
+    qreal width() const;
+    qreal height() const;
+    qreal implicitWidth() const;
+    qreal implicitHeight() const;
+    qreal maxHeight() const;
+    qreal iconWidth() const;
+    qreal fullWidth() const;
+
+private:
+    Q_SLOT void actionVisibleChanged();
+    Q_SLOT void displayHintChanged();
+    inline void ensureItemVisibility()
+    {
+        if (m_full) {
+            m_full->setVisible(m_fullVisible);
+        }
+        if (m_icon) {
+            m_icon->setVisible(m_iconVisible);
+        }
+    }
+    void cleanupIncubators();
+
+    ToolBarLayout *m_parent = nullptr;
+    QObject *m_action = nullptr;
+    QQuickItem *m_full = nullptr;
+    QQuickItem *m_icon = nullptr;
+    ToolBarDelegateIncubator *m_fullIncubator = nullptr;
+    ToolBarDelegateIncubator *m_iconIncubator = nullptr;
+
+    DisplayHint::DisplayHints m_displayHint = DisplayHint::NoPreference;
+    bool m_ready = false;
+    bool m_actionVisible = true;
+    bool m_fullVisible = false;
+    bool m_iconVisible = false;
+};
+
+#endif // TOOLBARLAYOUTDELEGATE_H
diff --git a/src/wheelhandler.cpp b/src/wheelhandler.cpp
new file mode 100644 (file)
index 0000000..d3d17f0
--- /dev/null
@@ -0,0 +1,692 @@
+/*
+ *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "wheelhandler.h"
+#include "settings.h"
+#include <QQuickItem>
+#include <QQuickWindow>
+#include <QWheelEvent>
+
+KirigamiWheelEvent::KirigamiWheelEvent(QObject *parent)
+    : QObject(parent)
+{
+}
+
+KirigamiWheelEvent::~KirigamiWheelEvent()
+{
+}
+
+void KirigamiWheelEvent::initializeFromEvent(QWheelEvent *event)
+{
+    m_x = event->position().x();
+    m_y = event->position().y();
+    m_angleDelta = event->angleDelta();
+    m_pixelDelta = event->pixelDelta();
+    m_buttons = event->buttons();
+    m_modifiers = event->modifiers();
+    m_accepted = false;
+    m_inverted = event->inverted();
+}
+
+qreal KirigamiWheelEvent::x() const
+{
+    return m_x;
+}
+
+qreal KirigamiWheelEvent::y() const
+{
+    return m_y;
+}
+
+QPointF KirigamiWheelEvent::angleDelta() const
+{
+    return m_angleDelta;
+}
+
+QPointF KirigamiWheelEvent::pixelDelta() const
+{
+    return m_pixelDelta;
+}
+
+int KirigamiWheelEvent::buttons() const
+{
+    return m_buttons;
+}
+
+int KirigamiWheelEvent::modifiers() const
+{
+    return m_modifiers;
+}
+
+bool KirigamiWheelEvent::inverted() const
+{
+    return m_inverted;
+}
+
+bool KirigamiWheelEvent::isAccepted()
+{
+    return m_accepted;
+}
+
+void KirigamiWheelEvent::setAccepted(bool accepted)
+{
+    m_accepted = accepted;
+}
+
+///////////////////////////////
+
+WheelFilterItem::WheelFilterItem(QQuickItem *parent)
+    : QQuickItem(parent)
+{
+    setEnabled(false);
+}
+
+///////////////////////////////
+
+WheelHandler::WheelHandler(QObject *parent)
+    : QObject(parent)
+    , m_filterItem(new WheelFilterItem(nullptr))
+{
+    m_filterItem->installEventFilter(this);
+
+    m_wheelScrollingTimer.setSingleShot(true);
+    m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
+    m_wheelScrollingTimer.callOnTimeout([this]() {
+        setScrolling(false);
+    });
+
+    connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, [this](int scrollLines) {
+        m_defaultPixelStepSize = 20 * scrollLines;
+        if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
+            m_verticalStepSize = m_defaultPixelStepSize;
+            Q_EMIT verticalStepSizeChanged();
+        }
+        if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
+            m_horizontalStepSize = m_defaultPixelStepSize;
+            Q_EMIT horizontalStepSizeChanged();
+        }
+    });
+}
+
+WheelHandler::~WheelHandler()
+{
+    delete m_filterItem;
+}
+
+QQuickItem *WheelHandler::target() const
+{
+    return m_flickable;
+}
+
+void WheelHandler::setTarget(QQuickItem *target)
+{
+    if (m_flickable == target) {
+        return;
+    }
+
+    if (target && !target->inherits("QQuickFlickable")) {
+        qmlWarning(this) << "target must be a QQuickFlickable";
+        return;
+    }
+
+    if (m_flickable) {
+        m_flickable->removeEventFilter(this);
+        disconnect(m_flickable, nullptr, m_filterItem, nullptr);
+        disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
+    }
+
+    m_flickable = target;
+    m_filterItem->setParentItem(target);
+
+    if (target) {
+        target->installEventFilter(this);
+
+        // Stack WheelFilterItem over the Flickable's scrollable content
+        m_filterItem->stackAfter(target->property("contentItem").value<QQuickItem*>());
+        // Make it fill the Flickable
+        m_filterItem->setWidth(target->width());
+        m_filterItem->setHeight(target->height());
+        connect(target, &QQuickItem::widthChanged, m_filterItem, [this, target]() {
+            m_filterItem->setWidth(target->width());
+        });
+        connect(target, &QQuickItem::heightChanged, m_filterItem, [this, target]() {
+            m_filterItem->setHeight(target->height());
+        });
+    }
+
+    _k_rebindScrollBars();
+
+    Q_EMIT targetChanged();
+}
+
+void WheelHandler::_k_rebindScrollBars()
+{
+    struct ScrollBarAttached {
+        QObject *attached = nullptr;
+        QQuickItem *vertical = nullptr;
+        QQuickItem *horizontal = nullptr;
+    };
+
+    ScrollBarAttached attachedToFlickable;
+    ScrollBarAttached attachedToScrollView;
+
+    if (m_flickable) {
+        // Get ScrollBars so that we can filter them too, even if they're not
+        // in the bounds of the Flickable
+        const auto flickableChildren = m_flickable->children();
+        for (const auto child : flickableChildren) {
+            if (child->inherits("QQuickScrollBarAttached")) {
+                attachedToFlickable.attached = child;
+                attachedToFlickable.vertical = child->property("vertical").value<QQuickItem *>();
+                attachedToFlickable.horizontal = child->property("horizontal").value<QQuickItem *>();
+                break;
+            }
+        }
+
+        // Check ScrollView if there are no scrollbars attached to the Flickable.
+        // We need to check if the parent inherits QQuickScrollView in case the
+        // parent is another Flickable that already has a Kirigami WheelHandler.
+        auto flickableParent = m_flickable->parentItem();
+        if (flickableParent && flickableParent->inherits("QQuickScrollView")) {
+            const auto siblings = flickableParent->children();
+            for (const auto child : siblings) {
+                if (child->inherits("QQuickScrollBarAttached")) {
+                    attachedToScrollView.attached = child;
+                    attachedToScrollView.vertical = child->property("vertical").value<QQuickItem *>();
+                    attachedToScrollView.horizontal = child->property("horizontal").value<QQuickItem *>();
+                    break;
+                }
+            }
+        }
+    }
+
+    // Dilemma: ScrollBars can be attached to both ScrollView and Flickable,
+    // but only one of them should be shown anyway. Let's prefer Flickable.
+
+    struct ChosenScrollBar {
+        QObject *attached = nullptr;
+        QQuickItem *scrollBar = nullptr;
+    };
+
+    ChosenScrollBar vertical;
+    if (attachedToFlickable.vertical) {
+        vertical.attached = attachedToFlickable.attached;
+        vertical.scrollBar = attachedToFlickable.vertical;
+    } else if (attachedToScrollView.vertical) {
+        vertical.attached = attachedToScrollView.attached;
+        vertical.scrollBar = attachedToScrollView.vertical;
+    }
+
+    ChosenScrollBar horizontal;
+    if (attachedToFlickable.horizontal) {
+        horizontal.attached = attachedToFlickable.attached;
+        horizontal.scrollBar = attachedToFlickable.horizontal;
+    } else if (attachedToScrollView.horizontal) {
+        horizontal.attached = attachedToScrollView.attached;
+        horizontal.scrollBar = attachedToScrollView.horizontal;
+    }
+
+    // Flickable may get re-parented to or out of a ScrollView, so we need to
+    // redo the discovery process. This is especially important for
+    // Kirigami.ScrollablePage component.
+    if (m_flickable) {
+        if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
+            // But if both scrollbars are already those from the preferred
+            // Flickable, there's no need for rediscovery.
+            disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
+        } else {
+            connect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars, Qt::UniqueConnection);
+        }
+    }
+
+    if (m_verticalScrollBar != vertical.scrollBar) {
+        if (m_verticalScrollBar) {
+            m_verticalScrollBar->removeEventFilter(this);
+            disconnect(m_verticalChangedConnection);
+        }
+        m_verticalScrollBar = vertical.scrollBar;
+        if (vertical.scrollBar) {
+            vertical.scrollBar->installEventFilter(this);
+            m_verticalChangedConnection = connect(vertical.attached, SIGNAL(verticalChanged()), this, SLOT(_k_rebindScrollBars()));
+        }
+    }
+
+    if (m_horizontalScrollBar != horizontal.scrollBar) {
+        if (m_horizontalScrollBar) {
+            m_horizontalScrollBar->removeEventFilter(this);
+            disconnect(m_horizontalChangedConnection);
+        }
+        m_horizontalScrollBar = horizontal.scrollBar;
+        if (horizontal.scrollBar) {
+            horizontal.scrollBar->installEventFilter(this);
+            m_horizontalChangedConnection = connect(horizontal.attached, SIGNAL(horizontalChanged()), this, SLOT(_k_rebindScrollBars()));
+        }
+    }
+}
+
+qreal WheelHandler::verticalStepSize() const
+{
+    return m_verticalStepSize;
+}
+
+void WheelHandler::setVerticalStepSize(qreal stepSize)
+{
+    m_explicitVStepSize = true;
+    if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
+        return;
+    }
+    // Mimic the behavior of QQuickScrollBar when stepSize is 0
+    if (qFuzzyIsNull(stepSize)) {
+        resetVerticalStepSize();
+        return;
+    }
+    m_verticalStepSize = stepSize;
+    Q_EMIT verticalStepSizeChanged();
+}
+
+void WheelHandler::resetVerticalStepSize()
+{
+    m_explicitVStepSize = false;
+    if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
+        return;
+    }
+    m_verticalStepSize = m_defaultPixelStepSize;
+    Q_EMIT verticalStepSizeChanged();
+}
+
+qreal WheelHandler::horizontalStepSize() const
+{
+    return m_horizontalStepSize;
+}
+
+void WheelHandler::setHorizontalStepSize(qreal stepSize)
+{
+    m_explicitHStepSize = true;
+    if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
+        return;
+    }
+    // Mimic the behavior of QQuickScrollBar when stepSize is 0
+    if (qFuzzyIsNull(stepSize)) {
+        resetHorizontalStepSize();
+        return;
+    }
+    m_horizontalStepSize = stepSize;
+    Q_EMIT horizontalStepSizeChanged();
+}
+
+void WheelHandler::resetHorizontalStepSize()
+{
+    m_explicitHStepSize = false;
+    if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
+        return;
+    }
+    m_horizontalStepSize = m_defaultPixelStepSize;
+    Q_EMIT horizontalStepSizeChanged();
+}
+
+Qt::KeyboardModifiers WheelHandler::pageScrollModifiers() const
+{
+    return m_pageScrollModifiers;
+}
+
+void WheelHandler::setPageScrollModifiers(Qt::KeyboardModifiers modifiers)
+{
+    if (m_pageScrollModifiers == modifiers) {
+        return;
+    }
+    m_pageScrollModifiers = modifiers;
+    Q_EMIT pageScrollModifiersChanged();
+}
+
+void WheelHandler::resetPageScrollModifiers()
+{
+    setPageScrollModifiers(m_defaultPageScrollModifiers);
+}
+
+bool WheelHandler::filterMouseEvents() const
+{
+    return m_filterMouseEvents;
+}
+
+void WheelHandler::setFilterMouseEvents(bool enabled)
+{
+    if (m_filterMouseEvents == enabled) {
+        return;
+    }
+    m_filterMouseEvents = enabled;
+    Q_EMIT filterMouseEventsChanged();
+}
+
+bool WheelHandler::keyNavigationEnabled() const
+{
+    return m_keyNavigationEnabled;
+}
+
+void WheelHandler::setKeyNavigationEnabled(bool enabled)
+{
+    if (m_keyNavigationEnabled == enabled) {
+        return;
+    }
+    m_keyNavigationEnabled = enabled;
+    Q_EMIT keyNavigationEnabledChanged();
+}
+
+void WheelHandler::setScrolling(bool scrolling)
+{
+    if (m_wheelScrolling == scrolling) {
+        if (m_wheelScrolling) {
+            m_wheelScrollingTimer.start();
+        }
+        return;
+    }
+    m_wheelScrolling = scrolling;
+    m_filterItem->setEnabled(m_wheelScrolling);
+}
+
+bool WheelHandler::scrollFlickable(QPointF pixelDelta, QPointF angleDelta, Qt::KeyboardModifiers modifiers)
+{
+    if (!m_flickable || (pixelDelta.isNull() && angleDelta.isNull())) {
+        return false;
+    }
+
+    const qreal width = m_flickable->width();
+    const qreal height = m_flickable->height();
+    const qreal contentWidth = m_flickable->property("contentWidth").toReal();
+    const qreal contentHeight = m_flickable->property("contentHeight").toReal();
+    const qreal contentX = m_flickable->property("contentX").toReal();
+    const qreal contentY = m_flickable->property("contentY").toReal();
+    const qreal topMargin = m_flickable->property("topMargin").toReal();
+    const qreal bottomMargin = m_flickable->property("bottomMargin").toReal();
+    const qreal leftMargin = m_flickable->property("leftMargin").toReal();
+    const qreal rightMargin = m_flickable->property("rightMargin").toReal();
+    const qreal originX = m_flickable->property("originX").toReal();
+    const qreal originY = m_flickable->property("originY").toReal();
+    const qreal pageWidth = width - leftMargin - rightMargin;
+    const qreal pageHeight = height - topMargin - bottomMargin;
+    const auto window = m_flickable->window();
+    const qreal devicePixelRatio = window != nullptr ? window->devicePixelRatio() : qGuiApp->devicePixelRatio();
+
+    // HACK: Only transpose deltas when not using xcb in order to not conflict with xcb's own delta transposing
+    if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String("xcb")) {
+        angleDelta = angleDelta.transposed();
+        pixelDelta = pixelDelta.transposed();
+    }
+
+    const qreal xTicks = angleDelta.x() / 120;
+    const qreal yTicks = angleDelta.y() / 120;
+    qreal xChange;
+    qreal yChange;
+    bool scrolled = false;
+
+    // Scroll X
+    if (contentWidth > pageWidth) {
+        // Use page size with pageScrollModifiers. Matches QScrollBar, which uses QAbstractSlider behavior.
+        if (modifiers & m_pageScrollModifiers) {
+            xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
+        } else if (pixelDelta.x() != 0) {
+            xChange = pixelDelta.x();
+        } else {
+            xChange = xTicks * m_horizontalStepSize;
+        }
+
+        // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
+
+        qreal minXExtent = leftMargin - originX;
+        qreal maxXExtent = width - (contentWidth + rightMargin + originX);
+
+        qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
+        // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
+        // Rounding prevents fractional positioning from causing text to be
+        // clipped off on the top and bottom.
+        // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
+        // after to make position match pixels on the screen more closely.
+        newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
+        if (contentX != newContentX) {
+            scrolled = true;
+            m_flickable->setProperty("contentX", newContentX);
+        }
+    }
+
+    // Scroll Y
+    if (contentHeight > pageHeight) {
+        if (modifiers & m_pageScrollModifiers) {
+            yChange = qBound(-pageHeight, yTicks * pageHeight, pageHeight);
+        } else if (pixelDelta.y() != 0) {
+            yChange = pixelDelta.y();
+        } else {
+            yChange = yTicks * m_verticalStepSize;
+        }
+
+        // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
+
+        qreal minYExtent = topMargin - originY;
+        qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
+
+        qreal newContentY = qBound(-minYExtent, contentY - yChange, -maxYExtent);
+        // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
+        // Rounding prevents fractional positioning from causing text to be
+        // clipped off on the top and bottom.
+        // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
+        // after to make position match pixels on the screen more closely.
+        newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
+        if (contentY != newContentY) {
+            scrolled = true;
+            m_flickable->setProperty("contentY", newContentY);
+        }
+    }
+
+    return scrolled;
+}
+
+bool WheelHandler::scrollUp(qreal stepSize)
+{
+    if (qFuzzyIsNull(stepSize)) {
+        return false;
+    } else if (stepSize < 0) {
+        stepSize = m_verticalStepSize;
+    }
+    // contentY uses reversed sign
+    return scrollFlickable(QPointF(0, stepSize));
+}
+
+bool WheelHandler::scrollDown(qreal stepSize)
+{
+    if (qFuzzyIsNull(stepSize)) {
+        return false;
+    } else if (stepSize < 0) {
+        stepSize = m_verticalStepSize;
+    }
+    // contentY uses reversed sign
+    return scrollFlickable(QPointF(0, -stepSize));
+}
+
+bool WheelHandler::scrollLeft(qreal stepSize)
+{
+    if (qFuzzyIsNull(stepSize)) {
+        return false;
+    } else if (stepSize < 0) {
+        stepSize = m_horizontalStepSize;
+    }
+    // contentX uses reversed sign
+    return scrollFlickable(QPoint(stepSize, 0));
+}
+
+bool WheelHandler::scrollRight(qreal stepSize)
+{
+    if (qFuzzyIsNull(stepSize)) {
+        return false;
+    } else if (stepSize < 0) {
+        stepSize = m_horizontalStepSize;
+    }
+    // contentX uses reversed sign
+    return scrollFlickable(QPoint(-stepSize, 0));
+}
+
+bool WheelHandler::eventFilter(QObject *watched, QEvent *event)
+{
+    auto item = qobject_cast<QQuickItem*>(watched);
+    if (!item || !item->isEnabled()) {
+        return false;
+    }
+
+    qreal contentWidth = 0;
+    qreal contentHeight = 0;
+    qreal pageWidth = 0;
+    qreal pageHeight = 0;
+    if (m_flickable) {
+        contentWidth = m_flickable->property("contentWidth").toReal();
+        contentHeight = m_flickable->property("contentHeight").toReal();
+        pageWidth = m_flickable->width() - m_flickable->property("leftMargin").toReal() - m_flickable->property("rightMargin").toReal();
+        pageHeight = m_flickable->height() - m_flickable->property("topMargin").toReal() - m_flickable->property("bottomMargin").toReal();
+    }
+
+    // The code handling touch, mouse and hover events is mostly copied/adapted from QQuickScrollView::childMouseEventFilter()
+    switch (event->type()) {
+    case QEvent::Wheel: {
+        // QQuickScrollBar::interactive handling Matches behavior in QQuickScrollView::eventFilter()
+        if (m_filterMouseEvents) {
+            if (m_verticalScrollBar) {
+                m_verticalScrollBar->setProperty("interactive", true);
+            }
+            if (m_horizontalScrollBar) {
+                m_horizontalScrollBar->setProperty("interactive", true);
+            }
+        }
+        QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
+
+        // NOTE: On X11 with libinput, pixelDelta is identical to angleDelta when using a mouse that shouldn't use pixelDelta.
+        // If faulty pixelDelta, reset pixelDelta to (0,0).
+        if (wheelEvent->pixelDelta() == wheelEvent->angleDelta()) {
+            // In order to change any of the data, we have to create a whole new QWheelEvent from its constructor.
+            QWheelEvent newWheelEvent(
+                wheelEvent->position(),
+                wheelEvent->globalPosition(),
+                QPoint(0,0), // pixelDelta
+                wheelEvent->angleDelta(),
+                wheelEvent->buttons(),
+                wheelEvent->modifiers(),
+                wheelEvent->phase(),
+                wheelEvent->inverted(),
+                wheelEvent->source()
+            );
+            m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
+        } else {
+            m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
+        }
+
+        Q_EMIT wheel(&m_kirigamiWheelEvent);
+
+        if (m_kirigamiWheelEvent.isAccepted()) {
+            return true;
+        }
+
+        bool scrolled = false;
+        if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
+            // Don't use pixelDelta from the event unless angleDelta is not available
+            // because scrolling by pixelDelta is too slow on Wayland with libinput.
+            QPointF pixelDelta = m_kirigamiWheelEvent.angleDelta().isNull() ? m_kirigamiWheelEvent.pixelDelta() : QPoint(0, 0);
+            scrolled = scrollFlickable(pixelDelta,
+                                       m_kirigamiWheelEvent.angleDelta(),
+                                       Qt::KeyboardModifiers(m_kirigamiWheelEvent.modifiers()));
+        }
+        setScrolling(scrolled);
+
+        // NOTE: Wheel events created by touchpad gestures with pixel deltas will cause scrolling to jump back
+        // to where scrolling started unless the event is always accepted before it reaches the Flickable.
+        bool flickableWillUseGestureScrolling = !(wheelEvent->source() == Qt::MouseEventNotSynthesized || wheelEvent->pixelDelta().isNull());
+        return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
+    }
+
+    case QEvent::TouchBegin: {
+        m_wasTouched = true;
+        if (!m_filterMouseEvents) {
+            break;
+        }
+        if (m_verticalScrollBar) {
+            m_verticalScrollBar->setProperty("interactive", false);
+        }
+        if (m_horizontalScrollBar) {
+            m_horizontalScrollBar->setProperty("interactive", false);
+        }
+        break;
+    }
+
+    case QEvent::TouchEnd: {
+        m_wasTouched = false;
+        break;
+    }
+
+    case QEvent::MouseButtonPress: {
+        // NOTE: Flickable does not handle touch events, only synthesized mouse events
+        m_wasTouched = static_cast<QMouseEvent *>(event)->source() != Qt::MouseEventNotSynthesized;
+        if (!m_filterMouseEvents) {
+            break;
+        }
+        if (!m_wasTouched) {
+            if (m_verticalScrollBar) {
+                m_verticalScrollBar->setProperty("interactive", true);
+            }
+            if (m_horizontalScrollBar) {
+                m_horizontalScrollBar->setProperty("interactive", true);
+            }
+            break;
+        }
+        return !m_wasTouched && item == m_flickable;
+    }
+
+    case QEvent::MouseMove:
+    case QEvent::MouseButtonRelease: {
+        setScrolling(false);
+        if (!m_filterMouseEvents) {
+            break;
+        }
+        if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized && item == m_flickable) {
+            return true;
+        }
+        break;
+    }
+
+    case QEvent::HoverEnter:
+    case QEvent::HoverMove: {
+        if (!m_filterMouseEvents) {
+            break;
+        }
+        if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
+            if (m_verticalScrollBar) {
+                m_verticalScrollBar->setProperty("interactive", true);
+            }
+            if (m_horizontalScrollBar) {
+                m_horizontalScrollBar->setProperty("interactive", true);
+            }
+        }
+        break;
+    }
+
+    case QEvent::KeyPress: {
+        if (!m_keyNavigationEnabled) {
+            break;
+        }
+        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+        bool horizontalScroll = keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
+        switch (keyEvent->key()) {
+        case Qt::Key_Up: return scrollUp();
+        case Qt::Key_Down: return scrollDown();
+        case Qt::Key_Left: return scrollLeft();
+        case Qt::Key_Right: return scrollRight();
+        case Qt::Key_PageUp: return horizontalScroll ? scrollLeft(pageWidth) : scrollUp(pageHeight);
+        case Qt::Key_PageDown: return horizontalScroll ? scrollRight(pageWidth) : scrollDown(pageHeight);
+        case Qt::Key_Home: return horizontalScroll ? scrollLeft(contentWidth) : scrollUp(contentHeight);
+        case Qt::Key_End: return horizontalScroll ? scrollRight(contentWidth) : scrollDown(contentHeight);
+        default: break;
+        }
+        break;
+    }
+
+    default: break;
+    }
+
+    return false;
+}
diff --git a/src/wheelhandler.h b/src/wheelhandler.h
new file mode 100644 (file)
index 0000000..402d2a7
--- /dev/null
@@ -0,0 +1,387 @@
+/* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
+ * SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QGuiApplication>
+#include <QObject>
+#include <QPoint>
+#include <QQuickItem>
+#include <QStyleHints>
+#include <QtQml>
+
+class QWheelEvent;
+class WheelHandler;
+
+/**
+ * Describes the mouse wheel event
+ */
+class KirigamiWheelEvent : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the x coordinate of the mouse pointer.
+     */
+    Q_PROPERTY(qreal x READ x CONSTANT)
+
+    /**
+     * @brief This property holds the y coordinate of the mouse pointer.
+     */
+    Q_PROPERTY(qreal y READ y CONSTANT)
+
+    /**
+     * @brief This property holds the distance the wheel is rotated in degrees.
+     *
+     * The x and y coordinates indicate the horizontal and vertical wheels respectively.
+     * A positive value indicates it was rotated up/right, negative, bottom/left
+     * This value is more likely to be set in traditional mice.
+     */
+    Q_PROPERTY(QPointF angleDelta READ angleDelta CONSTANT)
+
+    /**
+     * @brief This property provides the delta in screen pixels available on high resolution trackpads.
+     */
+    Q_PROPERTY(QPointF pixelDelta READ pixelDelta CONSTANT)
+
+    /**
+     * @brief This property contains an OR combination of the buttons that were pressed during the wheel.
+     *
+     * Possible values in a combination are:
+     * * ``Qt.LeftButton``
+     * * ``Qt.MiddleButton``
+     * * ``Qt.RightButton``
+     */
+    Q_PROPERTY(int buttons READ buttons CONSTANT)
+
+    /**
+     * @brief This property holds the keyboard modifiers that were pressed during the wheel event.
+     *
+     * Possible values in a combination are:
+     * * ``Qt.NoModifier`` (default, no modifiers)
+     * * ``Qt.ControlModifier``
+     * * ``Qt.ShiftModifier``
+     * ...
+     */
+    Q_PROPERTY(int modifiers READ modifiers CONSTANT)
+
+    /**
+     * @brief This property holds whether the delta values are inverted.
+     *
+     * The returned delta may be inverted on some platforms, so positive values would mean bottom/left.
+     */
+    Q_PROPERTY(bool inverted READ inverted CONSTANT)
+
+    /**
+     * @brief This property sets whether the event should be accepted or dropped.
+     * If set, the event shouldn't be managed anymore, for instance it can be used
+     * to block the handler to manage the scroll of a view on some scenarios.
+     *
+     * @code
+     * // This handler handles automatically the scroll of
+     * // flickableItem, unless Ctrl is pressed, in this case the
+     * // app has custom code to handle Ctrl+wheel zooming
+     * Kirigami.WheelHandler {
+     *   target: flickableItem
+     *   blockTargetWheel: true
+     *   scrollFlickableTarget: true
+     *   onWheel: {
+     *        if (wheel.modifiers & Qt.ControlModifier) {
+     *            wheel.accepted = true;
+     *            // Handle scaling of the view
+     *       }
+     *   }
+     * }
+     * @endcode
+     *
+     */
+    Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
+
+public:
+    KirigamiWheelEvent(QObject *parent = nullptr);
+    ~KirigamiWheelEvent() override;
+
+    void initializeFromEvent(QWheelEvent *event);
+
+    qreal x() const;
+    qreal y() const;
+    QPointF angleDelta() const;
+    QPointF pixelDelta() const;
+    int buttons() const;
+    int modifiers() const;
+    bool inverted() const;
+    bool isAccepted();
+    void setAccepted(bool accepted);
+
+private:
+    qreal m_x = 0;
+    qreal m_y = 0;
+    QPointF m_angleDelta;
+    QPointF m_pixelDelta;
+    Qt::MouseButtons m_buttons = Qt::NoButton;
+    Qt::KeyboardModifiers m_modifiers = Qt::NoModifier;
+    bool m_inverted = false;
+    bool m_accepted = false;
+};
+
+class WheelFilterItem : public QQuickItem
+{
+    Q_OBJECT
+public:
+    WheelFilterItem(QQuickItem *parent = nullptr);
+};
+
+/**
+ * @brief Handles scrolling for a Flickable and 2 attached ScrollBars.
+ *
+ * WheelHandler filters events from a QtQuick.Flickable,
+ * a vertical QtQuick.Controls.ScrollBar and a horizontal QtQuick.Controls.ScrollBar.
+ * Wheel and KeyPress events (when ::keyNavigationEnabled is true) are
+ * used to scroll the Flickable. When ::filterMouseEvents is true, WheelHandler blocks mouse button
+ * input from reaching the Flickable and sets the
+ * <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-scrollbar.html#interactive-prop">interactive</a>
+ * property of the scrollbars to @c false when touch input is used.
+ *
+ * Wheel event handling behavior:
+ *
+ * - Pixel delta is ignored unless angle delta is not available because pixel delta scrolling is too slow. Qt Widgets doesn't use pixel delta either, so the
+ * default scroll speed should be consistent with Qt Widgets.
+ * - When using angle delta, scroll using the step increments defined by ::verticalStepSize and ::horizontalStepSize.
+ * - When one of the keyboard modifiers in ::pageScrollModifiers is used, scroll by pages.
+ * - When using a device that doesn't use 120 angle delta unit increments such as a touchpad, the ::verticalStepSize, ::horizontalStepSize and page increments
+ * (if using page scrolling) will be multiplied by `angle delta / 120` to keep scrolling smooth.
+ * - If scrolling has happened in the last 400ms, use an internal QQuickItem stacked over the Flickable's contentItem to catch wheel events and use those wheel
+ * events to scroll, if possible. This prevents controls inside the Flickable's contentItem that allow scrolling to change the value (e.g., Sliders, SpinBoxes)
+ * from conflicting with scrolling the page.
+ *
+ * Common usage with a Flickable:
+ *
+ * @include wheelhandler/FlickableUsage.qml
+ *
+ * Common usage inside of a ScrollView template:
+ *
+ * @include wheelhandler/ScrollViewUsage.qml
+ *
+ */
+class WheelHandler : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * @brief This property holds the Flickable that the WheelHandler will control.
+     */
+    Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged FINAL)
+
+    /**
+     * @brief This property holds the vertical step size.
+     *
+     * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`.
+     * This is consistent with the default increment for QScrollArea.
+     *
+     * @see ::horizontalStepSize
+     * @since KDE Frameworks 5.89
+     */
+    Q_PROPERTY(qreal verticalStepSize READ verticalStepSize
+               WRITE setVerticalStepSize RESET resetVerticalStepSize
+               NOTIFY verticalStepSizeChanged FINAL)
+
+    /**
+     * @brief This property holds the horizontal step size.
+     *
+     * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`.
+     * This is consistent with the default increment for QScrollArea.
+     *
+     * @see ::verticalStepSize
+     * @since KDE Frameworks 5.89
+     */
+    Q_PROPERTY(qreal horizontalStepSize READ horizontalStepSize
+               WRITE setHorizontalStepSize RESET resetHorizontalStepSize
+               NOTIFY horizontalStepSizeChanged FINAL)
+
+    /**
+     * @brief This property holds the keyboard modifiers that will be used to start page scrolling.
+     *
+     * The default value is equivalent to `Qt.ControlModifier | Qt.ShiftModifier`. This matches QScrollBar,
+     * which uses QAbstractSlider behavior.
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_PROPERTY(Qt::KeyboardModifiers pageScrollModifiers READ pageScrollModifiers
+               WRITE setPageScrollModifiers RESET resetPageScrollModifiers
+               NOTIFY pageScrollModifiersChanged FINAL)
+
+    /**
+     * @brief This property holds whether the WheelHandler filters mouse events like a QtQuick.Controls.ScrollView would.
+     *
+     * Touch events are allowed to flick the view and they make the scrollbars not interactive.
+     *
+     * Mouse events are not allowed to flick the view and they make the scrollbars interactive.
+     *
+     * Hover events on the scrollbars and wheel events on anything also make the scrollbars interactive when this property is set to true.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_PROPERTY(bool filterMouseEvents READ filterMouseEvents
+               WRITE setFilterMouseEvents NOTIFY filterMouseEventsChanged FINAL)
+
+    /**
+     * @brief This property holds whether the WheelHandler handles keyboard scrolling.
+     *
+     * - Left arrow scrolls a step to the left.
+     * - Right arrow scrolls a step to the right.
+     * - Up arrow scrolls a step upwards.
+     * - Down arrow scrolls a step downwards.
+     * - PageUp scrolls to the previous page.
+     * - PageDown scrolls to the next page.
+     * - Home scrolls to the beginning.
+     * - End scrolls to the end.
+     * - When Alt is held, scroll horizontally when using PageUp, PageDown, Home or End.
+     *
+     * default: ``false``
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_PROPERTY(bool keyNavigationEnabled READ keyNavigationEnabled
+               WRITE setKeyNavigationEnabled NOTIFY keyNavigationEnabledChanged FINAL)
+
+    /**
+     * @brief This property holds whether the WheelHandler blocks all wheel events from reaching the Flickable.
+     *
+     * When this property is false, scrolling the Flickable with WheelHandler will only block an event from reaching the Flickable if the Flickable is actually
+     * scrolled by WheelHandler.
+     *
+     * NOTE: Wheel events created by touchpad gestures with pixel deltas will always be accepted no matter what. This is because they will cause the Flickable
+     * to jump back to where scrolling started unless the events are always accepted before they reach the Flickable.
+     *
+     * default: ``true``
+     */
+    Q_PROPERTY(bool blockTargetWheel MEMBER m_blockTargetWheel NOTIFY blockTargetWheelChanged)
+
+    /**
+     * @brief This property holds whether the WheelHandler can use wheel events to scroll the Flickable.
+     *
+     * default: ``true``
+     */
+    Q_PROPERTY(bool scrollFlickableTarget MEMBER m_scrollFlickableTarget NOTIFY scrollFlickableTargetChanged)
+
+public:
+    explicit WheelHandler(QObject *parent = nullptr);
+    ~WheelHandler() override;
+
+    QQuickItem *target() const;
+    void setTarget(QQuickItem *target);
+
+    qreal verticalStepSize() const;
+    void setVerticalStepSize(qreal stepSize);
+    void resetVerticalStepSize();
+
+    qreal horizontalStepSize() const;
+    void setHorizontalStepSize(qreal stepSize);
+    void resetHorizontalStepSize();
+
+    Qt::KeyboardModifiers pageScrollModifiers() const;
+    void setPageScrollModifiers(Qt::KeyboardModifiers modifiers);
+    void resetPageScrollModifiers();
+
+    bool filterMouseEvents() const;
+    void setFilterMouseEvents(bool enabled);
+
+    bool keyNavigationEnabled() const;
+    void setKeyNavigationEnabled(bool enabled);
+
+    /**
+     * Scroll up one step. If the ::stepSize parameter is less than 0, the ::verticalStepSize will be used.
+     *
+     * returns @c true if the contentItem was moved.
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_INVOKABLE bool scrollUp(qreal stepSize = -1);
+
+    /**
+     * Scroll down one step. If the ::stepSize parameter is less than 0, the ::verticalStepSize will be used.
+     *
+     * returns @c true if the contentItem was moved.
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_INVOKABLE bool scrollDown(qreal stepSize = -1);
+
+    /**
+     * Scroll left one step. If the ::stepSize parameter is less than 0, the ::horizontalStepSize will be used.
+     *
+     * returns @c true if the contentItem was moved.
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_INVOKABLE bool scrollLeft(qreal stepSize = -1);
+
+    /**
+     * Scroll right one step. If the ::stepSize parameter is less than 0, the ::horizontalStepSize will be used.
+     *
+     * returns @c true if the contentItem was moved.
+     *
+     * @since KDE Frameworks 5.89
+     */
+    Q_INVOKABLE bool scrollRight(qreal stepSize = -1);
+
+Q_SIGNALS:
+    void targetChanged();
+    void verticalStepSizeChanged();
+    void horizontalStepSizeChanged();
+    void pageScrollModifiersChanged();
+    void filterMouseEventsChanged();
+    void keyNavigationEnabledChanged();
+    void blockTargetWheelChanged();
+    void scrollFlickableTargetChanged();
+
+    /**
+     * @brief This signal is emitted when a wheel event reaches the event filter, just before scrolling is handled.
+     *
+     * Accepting the wheel event in the `onWheel` signal handler prevents scrolling from happening.
+     */
+    void wheel(KirigamiWheelEvent *wheel);
+
+protected:
+    bool eventFilter(QObject *watched, QEvent *event) override;
+
+private Q_SLOTS:
+    void _k_rebindScrollBars();
+
+private:
+    void setScrolling(bool scrolling);
+    bool scrollFlickable(QPointF pixelDelta,
+                         QPointF angleDelta = {},
+                         Qt::KeyboardModifiers modifiers = Qt::NoModifier);
+
+    QPointer<QQuickItem> m_flickable;
+    QPointer<QQuickItem> m_verticalScrollBar;
+    QPointer<QQuickItem> m_horizontalScrollBar;
+    QMetaObject::Connection m_verticalChangedConnection;
+    QMetaObject::Connection m_horizontalChangedConnection;
+    QPointer<QQuickItem> m_filterItem;
+    // Matches QScrollArea and QTextEdit
+    qreal m_defaultPixelStepSize = 20 * QGuiApplication::styleHints()->wheelScrollLines();
+    qreal m_verticalStepSize = m_defaultPixelStepSize;
+    qreal m_horizontalStepSize = m_defaultPixelStepSize;
+    bool m_explicitVStepSize = false;
+    bool m_explicitHStepSize = false;
+    bool m_wheelScrolling = false;
+    constexpr static qreal m_wheelScrollingDuration = 400;
+    bool m_filterMouseEvents = false;
+    bool m_keyNavigationEnabled = false;
+    bool m_wasTouched = false;
+    bool m_blockTargetWheel = true;
+    bool m_scrollFlickableTarget = true;
+    // Same as QXcbWindow.
+    constexpr static Qt::KeyboardModifiers m_defaultHorizontalScrollModifiers = Qt::AltModifier;
+    // Same as QScrollBar/QAbstractSlider.
+    constexpr static Qt::KeyboardModifiers m_defaultPageScrollModifiers = Qt::ControlModifier | Qt::ShiftModifier;
+    Qt::KeyboardModifiers m_pageScrollModifiers = m_defaultPageScrollModifiers;
+    QTimer m_wheelScrollingTimer;
+    KirigamiWheelEvent m_kirigamiWheelEvent;
+};
diff --git a/templates/CMakeLists.txt b/templates/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f69b2ac
--- /dev/null
@@ -0,0 +1,3 @@
+set(apptemplate_DIRS kirigami)
+
+kde_package_app_templates(TEMPLATES ${apptemplate_DIRS} INSTALL_DIR ${KDE_INSTALL_KAPPTEMPLATESDIR})
diff --git a/templates/kirigami/CMakeLists.txt b/templates/kirigami/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c4c7bf3
--- /dev/null
@@ -0,0 +1,41 @@
+# SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+project(%{APPNAMELC} VERSION 0.1)
+
+include(FeatureSummary)
+
+set(QT5_MIN_VERSION 5.15)
+set(KF5_MIN_VERSION 5.83)
+
+find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
+
+include(KDEInstallDirs)
+include(KDECMakeSettings)
+include(KDECompilerSettings NO_POLICY_SCOPE)
+include(ECMSetupVersion)
+include(ECMGenerateHeaders)
+include(ECMPoQmTools)
+
+ecm_setup_version(${PROJECT_VERSION}
+    VARIABLE_PREFIX %{APPNAMEUC}
+    VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/version-%{APPNAMELC}.h"
+)
+
+find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg)
+find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 CoreAddons Config I18n)
+
+if (ANDROID)
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
+endif()
+
+add_subdirectory(src)
+
+install(PROGRAMS org.kde.%{APPNAMELC}.desktop DESTINATION ${KDE_INSTALL_APPDIR})
+install(FILES org.kde.%{APPNAMELC}.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
+
+feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/templates/kirigami/LICENSES/BSD-3-Clause.txt b/templates/kirigami/LICENSES/BSD-3-Clause.txt
new file mode 100644 (file)
index 0000000..0741db7
--- /dev/null
@@ -0,0 +1,26 @@
+Copyright (c) <year> <owner>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/templates/kirigami/LICENSES/CC0-1.0.txt b/templates/kirigami/LICENSES/CC0-1.0.txt
new file mode 100644 (file)
index 0000000..0e259d4
--- /dev/null
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/templates/kirigami/LICENSES/FSFAP.txt b/templates/kirigami/LICENSES/FSFAP.txt
new file mode 100644 (file)
index 0000000..c96c65e
--- /dev/null
@@ -0,0 +1,3 @@
+Copying and distribution of this file, with or without modification, are permitted
+in any medium without royalty provided the copyright notice and this notice
+are preserved.  This file is offered as-is, without any warranty.
diff --git a/templates/kirigami/LICENSES/GPL-2.0-or-later.txt b/templates/kirigami/LICENSES/GPL-2.0-or-later.txt
new file mode 100644 (file)
index 0000000..3b6070f
--- /dev/null
@@ -0,0 +1,311 @@
+GNU GENERAL PUBLIC LICENSE
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software
+is covered by the GNU Lesser General Public License instead.) You can apply
+it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And you
+must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its recipients
+to know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will individually
+obtain patent licenses, in effect making the program proprietary. To prevent
+this, we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms
+of this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation
+in the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running the Program
+is not restricted, and the output from the Program is covered only if its
+contents constitute a work based on the Program (independent of having been
+made by running the Program). Whether that is true depends on what the Program
+does.
+
+1. You may copy and distribute verbatim copies of the Program's source code
+as you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence
+of any warranty; and give any other recipients of the Program a copy of this
+License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it,
+thus forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that
+you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or
+in part contains or is derived from the Program or any part thereof, to be
+licensed as a whole at no charge to all third parties under the terms of this
+License.
+
+c) If the modified program normally reads commands interactively when run,
+you must cause it, when started running for such interactive use in the most
+ordinary way, to print or display an announcement including an appropriate
+copyright notice and a notice that there is no warranty (or else, saying that
+you provide a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this License.
+(Exception: if the Program itself is interactive but does not normally print
+such an announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Program.
+
+In addition, mere aggregation of another work not based on the Program with
+the Program (or with a work based on the Program) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under Section
+2) in object code or executable form under the terms of Sections 1 and 2 above
+provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source code,
+which must be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give
+any third party, for a charge no more than your cost of physically performing
+source distribution, a complete machine-readable copy of the corresponding
+source code, to be distributed under the terms of Sections 1 and 2 above on
+a medium customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable. However, as a special exception, the source code distributed
+need not include anything that is normally distributed (in either source or
+binary form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component itself
+accompanies the executable.
+
+If distribution of executable or object code is made by offering access to
+copy from a designated place, then offering equivalent access to copy the
+source code from the same place counts as distribution of the source code,
+even though third parties are not compelled to copy the source along with
+the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except
+as expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses terminated
+so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Program or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Program
+(or any work based on the Program), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor
+to copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of
+the rights granted herein. You are not responsible for enforcing compliance
+by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Program at all. For example, if a
+patent license would not permit royalty-free redistribution of the Program
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system, which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of
+the General Public License from time to time. Such new versions will be similar
+in spirit to the present version, but may differ in detail to address new
+problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Program does not specify a version number of this License, you may choose
+any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing and reuse
+of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach
+them to the start of each source file to most effectively convey the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+one line to give the program's name and an idea of what it does. Copyright
+(C) yyyy 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
diff --git a/templates/kirigami/android/AndroidManifest.xml b/templates/kirigami/android/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..e99438d
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!--
+  - SPDX-License-Identifier: BSD-3-Clause
+  - SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.kde.%{APPNAMELC}"
+          android:versionName="${versionName}"
+          android:versionCode="${versionCode}"
+          android:installLocation="auto">
+    <application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="%{APPNAME}" android:icon="@drawable/logo">
+        <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
+                  android:name="org.qtproject.qt5.android.bindings.QtActivity"
+                  android:label="%{APPNAME}"
+                  android:windowSoftInputMode="adjustResize"
+                  android:launchMode="singleTop">
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+
+            <meta-data android:name="android.app.lib_name" android:value="%{APPNAMELC}"/>
+            <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
+            <meta-data android:name="android.app.repository" android:value="default"/>
+            <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
+            <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
+            <meta-data android:name="android.app.extract_android_style" android:value="minimal"/>
+
+            <!-- Deploy Qt libs as part of package -->
+            <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%%BUNDLE_LOCAL_QT_LIBS%%% --"/>
+            <meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
+            <!-- Run with local libs -->
+            <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%%USE_LOCAL_QT_LIBS%%% --"/>
+            <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
+            <meta-data android:name="android.app.load_local_libs" android:value="-- %%%INSERT_LOCAL_LIBS%%% --"/>
+            <meta-data android:name="android.app.load_local_jars" android:value="-- %%%INSERT_LOCAL_JARS%%% --"/>
+            <meta-data android:name="android.app.static_init_classes" android:value="-- %%%INSERT_INIT_CLASSES%%% --"/>
+            <!--  Messages maps -->
+            <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
+            <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
+            <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
+
+            <!-- Splash screen -->
+            <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splash"/>
+
+            <!-- Background running -->
+            <meta-data android:name="android.app.background_running" android:value="false"/>
+
+            <!-- auto screen scale factor -->
+            <meta-data android:name="android.app.auto_screen_scale_factor" android:value="true"/>
+        </activity>
+    </application>
+    <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
+</manifest>
diff --git a/templates/kirigami/android/build.gradle b/templates/kirigami/android/build.gradle
new file mode 100644 (file)
index 0000000..1882912
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+    SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+    SPDX-License-Identifier: BSD-3-Clause
+*/
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.6.4'
+    }
+}
+
+repositories {
+    google()
+    jcenter()
+}
+
+
+apply plugin: 'com.android.application'
+apply from: '../version.gradle'
+def timestamp = (int)(new Date().getTime()/1000)
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+}
+
+android {
+    /*******************************************************
+     * The following variables:
+     * - androidBuildToolsVersion,
+     * - androidCompileSdkVersion
+     * - qt5AndroidDir - holds the path to qt android files
+     *                   needed to build any Qt application
+     *                   on Android.
+     *
+     * are defined in gradle.properties file. This file is
+     * updated by QtCreator and androiddeployqt tools.
+     * Changing them manually might break the compilation!
+     *******************************************************/
+
+    compileSdkVersion androidCompileSdkVersion.toInteger()
+
+    buildToolsVersion androidBuildToolsVersion
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
+            aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
+            res.srcDirs = [qt5AndroidDir + '/res', 'res']
+            resources.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            assets.srcDirs = ['assets']
+            jniLibs.srcDirs = ['libs']
+       }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    defaultConfig {
+        minSdkVersion qtMinSdkVersion
+        targetSdkVersion qtTargetSdkVersion
+        manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
+    }
+
+}
+
diff --git a/templates/kirigami/android/res/drawable/logo.png b/templates/kirigami/android/res/drawable/logo.png
new file mode 100644 (file)
index 0000000..a63448d
Binary files /dev/null and b/templates/kirigami/android/res/drawable/logo.png differ
diff --git a/templates/kirigami/android/res/drawable/splash.xml b/templates/kirigami/android/res/drawable/splash.xml
new file mode 100644 (file)
index 0000000..d69509e
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:width="128dp" android:height="128dp" android:gravity="center">
+        <bitmap
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:scaleType="fitXY"
+            android:src="@drawable/logo"/>
+    </item>
+</layer-list>
diff --git a/templates/kirigami/android/version.gradle.in b/templates/kirigami/android/version.gradle.in
new file mode 100644 (file)
index 0000000..9ddd1bc
--- /dev/null
@@ -0,0 +1,7 @@
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+// SPDX-License-Identifier: BSD-3-Clause
+
+ext {
+    projectVersionFull = "@PROJECT_VERSION@"
+}
+
diff --git a/templates/kirigami/kirigami-app.png b/templates/kirigami/kirigami-app.png
new file mode 100644 (file)
index 0000000..8df7987
Binary files /dev/null and b/templates/kirigami/kirigami-app.png differ
diff --git a/templates/kirigami/kirigami.kdevtemplate b/templates/kirigami/kirigami.kdevtemplate
new file mode 100644 (file)
index 0000000..7ff09ff
--- /dev/null
@@ -0,0 +1,70 @@
+# KDE Config File
+[General]
+Name=Kirigami Application
+Name[ar]=تطبيق كيريغامي
+Name[az]=Kirigami tətbiqi
+Name[bg]=Приложение на Kirigami
+Name[ca]=Aplicació de Kirigami
+Name[ca@valencia]=Aplicació de Kirigami
+Name[cs]=Aplikace Kirigami
+Name[de]=Kirigami-Anwendung
+Name[en_GB]=Kirigami Application
+Name[es]=Aplicación de Kirigami
+Name[eu]=Kirigami aplikazioa
+Name[fi]=Kirigami-sovellus
+Name[fr]=Application Kirigami
+Name[gl]=Aplicación de Kirigami
+Name[ia]=Application de Kirigami
+Name[it]=Applicazione Kirigami
+Name[ka]=აპლიკაცია Kirigami
+Name[ko]=Kirigami 프로그램
+Name[nl]=Kirigami-toepassing
+Name[nn]=Kirigami-program
+Name[pl]=Aplikacja Kirigami
+Name[pt]=Aplicação do Kirigami
+Name[pt_BR]=Aplicativo Kirigami
+Name[ro]=Aplicație Kirigami
+Name[ru]=Приложение Kirigami
+Name[sk]=Aplikácia Kirigami
+Name[sl]=Aplikacija Kirigami
+Name[sv]=Kirigami-program
+Name[tr]=Kirigami Uygulaması
+Name[uk]=Програма на Kirigami
+Name[x-test]=xxKirigami Applicationxx
+Name[zh_CN]=Kirigami 应用程序
+Name[zh_TW]=Kirigami 應用程式
+Comment=Convergent application using Qt Quick Controls 2 and Kirigami
+Comment[ar]=تطبيق متقارب باستخدام QT Quick Controls 2 و كيريغامي
+Comment[az]=Qt Quick Controls 2 və Kirigami istifadə edən istifadəçi dostu proqrams
+Comment[bg]=Конвергиране на приложение чрез Qt Quick Controls 2 и Kirigami
+Comment[ca]=Aplicació convergent que usa els Qt Quick Controls 2 i el Kirigami
+Comment[ca@valencia]=Aplicació convergent que utilitza els Qt Quick Controls 2 i Kirigami
+Comment[de]=Eine konvergente Anwendung mit Qt Quick Controls 2 und Kirigami
+Comment[en_GB]=Convergent application using Qt Quick Controls 2 and Kirigami
+Comment[es]=Aplicación convergente que usa Qt Quick Controls 2 y Kirigami
+Comment[eu]=«Qt Quick Controls 2» eta Kirigami erabiltzen dituen aplikazio konbergente bat
+Comment[fi]=Qt Quick Controls 2:ta ja Kirigamia käyttävä mukautuva sovellus
+Comment[fr]=Une application convergente utilisant les modules « Qt QuickControls2 » et Kirigami
+Comment[gl]=Aplicación converxente que usa Qt Quick Controls 2 e Kirigami.
+Comment[ia]=Application convergente usante Controlos Rapide de QT e Kirigami
+Comment[it]=Applicazione convergente che utilizza Qt Quick Controls 2 e Kirigami
+Comment[ka]=კონვერგენტული პროგრამა Qt Quick Controls 2-ის და Kirigami-ის გამოყენებით
+Comment[ko]=Qt Quick Controls 2와 Kirigami를 사용하는 통합형 프로그램
+Comment[nl]=Convergente toepassing met Qt Quick Controls 2 en Kirigami
+Comment[nn]=Program for ulike typar brukarflater med Qt Quick Controls 2 og Kirigami
+Comment[pl]=Adaptująca się aplikacja, używająca Qt Quick Controls 2 oraz Kirigami
+Comment[pt]=Aplicação convergente que usa os Qt Quick Controls 2 e o Kirigami
+Comment[pt_BR]=Aplicativo convergente usando Qt Quick Controls 2 e Kirigami
+Comment[ro]=Aplicație convergentă folosind Qt Quick Controls 2 și Kirigami
+Comment[ru]=Конвергентное приложение, использующее Qt Quick Controls 2 и Kirigami
+Comment[sl]=Konvergentna aplikacija, ki uporablja kontrole Qt Quick in Kirigami
+Comment[sv]=Konvergent program som använder Qt Quick Controls 2 och Kirigami
+Comment[tr]=Qt Quick Controls 2 ve Kirigami kullanan yakınsak uygulama
+Comment[uk]=Зручна програма з використанням засобів керування Qt Quick 2 та Kirigami
+Comment[x-test]=xxConvergent application using Qt Quick Controls 2 and Kirigamixx
+Comment[zh_CN]=基于 Qt Quick Controls 2 和 Kirigami 构建的桌面与移动平台通用应用程序
+Comment[zh_TW]=使用 Qt Quick Controls 2 和 Kirigami 的跨平台應用程式
+
+Category=Qt/Graphical
+Icon=kirigami-app.png
+ShowFilesAfterGeneration=src/main.cpp
diff --git a/templates/kirigami/org.kde.%{APPNAMELC}.desktop b/templates/kirigami/org.kde.%{APPNAMELC}.desktop
new file mode 100644 (file)
index 0000000..dfe88e9
--- /dev/null
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+[Desktop Entry]
+Name=%{APPNAME}
+Comment=%{APPNAME} Kirigami Application
+Version=1.0
+Exec=%{APPNAMELC}
+Icon=applications-development
+Type=Application
+Terminal=false
+# Add an actual main category here (and possibly applicable additional ones)
+#   https://specifications.freedesktop.org/menu-spec/latest/apa.html#main-category-registry
+Categories=Qt;KDE;
diff --git a/templates/kirigami/org.kde.%{APPNAMELC}.json b/templates/kirigami/org.kde.%{APPNAMELC}.json
new file mode 100644 (file)
index 0000000..8d2a190
--- /dev/null
@@ -0,0 +1,28 @@
+{
+    "id": "org.kde.%{APPNAMELC}",
+    "runtime": "org.kde.Platform",
+    "runtime-version": "5.15",
+    "sdk": "org.kde.Sdk",
+    "command": "%{APPNAMELC}",
+    "tags": ["nightly"],
+    "desktop-file-name-suffix": " (Nightly)",
+    "finish-args": [
+        "--share=ipc",
+        "--share=network",
+        "--socket=x11",
+        "--socket=wayland",
+        "--device=dri",
+        "--filesystem=home"
+    ],
+    "separate-locales": false,
+
+    "modules": [
+        {
+            "name": "%{APPNAMELC}",
+            "buildsystem": "cmake-ninja",
+            "builddir": true,
+            "sources": [ { "type": "dir", "path": ".", "skip": [".git"] } ]
+        }
+    ]
+}
+
diff --git a/templates/kirigami/org.kde.%{APPNAMELC}.metainfo.xml b/templates/kirigami/org.kde.%{APPNAMELC}.metainfo.xml
new file mode 100644 (file)
index 0000000..795ef61
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  SPDX-License-Identifier: FSFAP
+  SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+ -->
+<component type="desktop-application">
+  <id>org.kde.%{APPNAMELC}</id>
+  <name>%{APPNAME} Kirigami Application</name>
+  <summary>A short summary describing what this software is about</summary>
+  <metadata_license>A permissive license for this metadata, e.g. "FSFAP" + Update the SPDX tags above!</metadata_license>
+  <project_license>The license of this software as SPDX string, e.g. "GPL-2.0-or-later"</project_license>
+  <developer_name>The software vendor name, e.g. "ACME Corporation"</developer_name>
+  <description>
+    <p>Multiple paragraphs of long description, describing this software component.</p>
+    <p>You can also use ordered and unordered lists:</p>
+    <ul>
+      <li>Feature 1</li>
+      <li>Feature 2</li>
+    </ul>
+    <p>Keep in mind to XML-escape characters, and that this is not HTML markup.</p>
+  </description>
+</component>
diff --git a/templates/kirigami/src/%{APPNAMELC}config.kcfg b/templates/kirigami/src/%{APPNAMELC}config.kcfg
new file mode 100644 (file)
index 0000000..1c0dac1
--- /dev/null
@@ -0,0 +1,16 @@
+<?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" >
+<!--
+SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+SPDX-License-Identifier: LGPL-2.0-or-later
+-->
+    <group name="General">
+        <entry name="someSetting" type="Bool">
+            <label>Some setting description</label>
+            <default>true</default>
+        </entry>
+    </group>
+</kcfg>
diff --git a/templates/kirigami/src/%{APPNAMELC}config.kcfgc b/templates/kirigami/src/%{APPNAMELC}config.kcfgc
new file mode 100644 (file)
index 0000000..f112ed3
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+# SPDX-License-Identifier: LGPL-2.0-or-later
+
+File=%{APPNAMELC}config.kcfg
+ClassName=%{APPNAME}Config
+Mutators=true
+DefaultValueGetters=true
+GenerateProperties=true
+ParentInConstructor=true
+Singleton=true
diff --git a/templates/kirigami/src/CMakeLists.txt b/templates/kirigami/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4a0ada6
--- /dev/null
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+add_executable(%{APPNAMELC}
+    main.cpp
+    about.cpp
+    app.cpp
+    resources.qrc)
+
+target_link_libraries(%{APPNAMELC}
+    Qt5::Core
+    Qt5::Gui
+    Qt5::Qml
+    Qt5::Quick
+    Qt5::QuickControls2
+    Qt5::Svg
+    KF5::I18n
+    KF5::CoreAddons
+    KF5::ConfigCore
+    KF5::ConfigGui)
+
+if (ANDROID)
+    kirigami_package_breeze_icons(ICONS
+        list-add
+        help-about
+        application-exit
+        applications-graphics
+    )
+endif()
+
+kconfig_add_kcfg_files(%{APPNAMELC} GENERATE_MOC %{APPNAMELC}config.kcfgc)
+install(TARGETS %{APPNAMELC} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/templates/kirigami/src/about.cpp b/templates/kirigami/src/about.cpp
new file mode 100644 (file)
index 0000000..83a7c47
--- /dev/null
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// PDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+#include "about.h"
+
+KAboutData AboutType::aboutData() const
+{
+    return KAboutData::applicationData();
+}
diff --git a/templates/kirigami/src/about.h b/templates/kirigami/src/about.h
new file mode 100644 (file)
index 0000000..b403988
--- /dev/null
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+#pragma once
+
+#include <QObject>
+#include <KAboutData>
+
+class AboutType : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(KAboutData aboutData READ aboutData CONSTANT)
+public:
+    [[nodiscard]] KAboutData aboutData() const;
+};
diff --git a/templates/kirigami/src/app.cpp b/templates/kirigami/src/app.cpp
new file mode 100644 (file)
index 0000000..332ac19
--- /dev/null
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+#include "app.h"
+#include <KSharedConfig>
+#include <KWindowConfig>
+#include <QQuickWindow>
+
+void App::restoreWindowGeometry(QQuickWindow *window, const QString &group) const
+{
+    KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
+    KConfigGroup windowGroup(&dataResource, QStringLiteral("Window-") + group);
+    KWindowConfig::restoreWindowSize(window, windowGroup);
+    KWindowConfig::restoreWindowPosition(window, windowGroup);
+}
+
+void App::saveWindowGeometry(QQuickWindow *window, const QString &group) const
+{
+    KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
+    KConfigGroup windowGroup(&dataResource, QStringLiteral("Window-") + group);
+    KWindowConfig::saveWindowPosition(window, windowGroup);
+    KWindowConfig::saveWindowSize(window, windowGroup);
+    dataResource.sync();
+}
diff --git a/templates/kirigami/src/app.h b/templates/kirigami/src/app.h
new file mode 100644 (file)
index 0000000..ba49a48
--- /dev/null
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+#pragma once
+
+#include <QObject>
+
+class QQuickWindow;
+
+class App : public QObject
+{
+    Q_OBJECT
+
+public:
+    // Restore current window geometry
+    Q_INVOKABLE void restoreWindowGeometry(QQuickWindow *window, const QString &group = QStringLiteral("main")) const;
+    // Save current window geometry
+    Q_INVOKABLE void saveWindowGeometry(QQuickWindow *window, const QString &group = QStringLiteral("main")) const;
+};
diff --git a/templates/kirigami/src/contents/ui/About.qml b/templates/kirigami/src/contents/ui/About.qml
new file mode 100644 (file)
index 0000000..ff4cdb2
--- /dev/null
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as Controls
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+import org.kde.%{APPNAME} 1.0
+
+Kirigami.AboutPage {
+    aboutData: AboutType.aboutData
+}
diff --git a/templates/kirigami/src/contents/ui/main.qml b/templates/kirigami/src/contents/ui/main.qml
new file mode 100644 (file)
index 0000000..f2120a7
--- /dev/null
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as Controls
+import QtQuick.Layouts 1.15
+import org.kde.kirigami 2.19 as Kirigami
+import org.kde.%{APPNAME} 1.0
+
+Kirigami.ApplicationWindow {
+    id: root
+
+    title: i18n("%{APPNAME}")
+
+    minimumWidth: Kirigami.Units.gridUnit * 20
+    minimumHeight: Kirigami.Units.gridUnit * 20
+
+    onClosing: App.saveWindowGeometry(root)
+
+    onWidthChanged: saveWindowGeometryTimer.restart()
+    onHeightChanged: saveWindowGeometryTimer.restart()
+    onXChanged: saveWindowGeometryTimer.restart()
+    onYChanged: saveWindowGeometryTimer.restart()
+
+    Component.onCompleted: App.restoreWindowGeometry(root)
+
+    // This timer allows to batch update the window size change to reduce
+    // the io load and also work around the fact that x/y/width/height are
+    // changed when loading the page and overwrite the saved geometry from
+    // the previous session.
+    Timer {
+        id: saveWindowGeometryTimer
+        interval: 1000
+        onTriggered: App.saveWindowGeometry(root)
+    }
+
+    property int counter: 0
+
+    globalDrawer: Kirigami.GlobalDrawer {
+        title: i18n("%{APPNAME}")
+        titleIcon: "applications-graphics"
+        isMenu: !root.isMobile
+        actions: [
+            Kirigami.Action {
+                text: i18n("Plus One")
+                icon.name: "list-add"
+                onTriggered: {
+                    counter += 1
+                }
+            },
+            Kirigami.Action {
+                text: i18n("About %{APPNAME}")
+                icon.name: "help-about"
+                onTriggered: pageStack.layers.push('qrc:About.qml')
+            },
+            Kirigami.Action {
+                text: i18n("Quit")
+                icon.name: "application-exit"
+                onTriggered: Qt.quit()
+            }
+        ]
+    }
+
+    contextDrawer: Kirigami.ContextDrawer {
+        id: contextDrawer
+    }
+
+    pageStack.initialPage: page
+
+    Kirigami.Page {
+        id: page
+
+        Layout.fillWidth: true
+
+        title: i18n("Main Page")
+
+        actions.main: Kirigami.Action {
+            text: i18n("Plus One")
+            icon.name: "list-add"
+            tooltip: i18n("Add one to the counter")
+            onTriggered: {
+                counter += 1
+            }
+        }
+
+        ColumnLayout {
+            width: page.width
+
+            anchors.centerIn: parent
+
+            Kirigami.Heading {
+                Layout.alignment: Qt.AlignCenter
+                text: counter == 0 ? i18n("Hello, World!") : counter
+            }
+
+            Controls.Button {
+                Layout.alignment: Qt.AlignHCenter
+                text: "+ 1"
+                onClicked: counter += 1
+            }
+        }
+    }
+}
diff --git a/templates/kirigami/src/main.cpp b/templates/kirigami/src/main.cpp
new file mode 100644 (file)
index 0000000..4109dd7
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    SPDX-License-Identifier: GPL-2.0-or-later
+    SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+*/
+
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QUrl>
+#include <QtQml>
+
+#include "about.h"
+#include "app.h"
+#include "version-%{APPNAMELC}.h"
+#include <KAboutData>
+#include <KLocalizedContext>
+#include <KLocalizedString>
+
+#include "%{APPNAMELC}config.h"
+
+Q_DECL_EXPORT int main(int argc, char *argv[])
+{
+    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    QApplication app(argc, argv);
+    QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
+    QCoreApplication::setApplicationName(QStringLiteral("%{APPNAME}"));
+
+    KAboutData aboutData(
+                         // The program name used internally.
+                         QStringLiteral("%{APPNAME}"),
+                         // A displayable program name string.
+                         i18nc("@title", "%{APPNAME}"),
+                         // The program version string.
+                         QStringLiteral(%{APPNAMEUC}_VERSION_STRING),
+                         // Short description of what the app does.
+                         i18n("Application Description"),
+                         // The license this code is released under.
+                         KAboutLicense::GPL,
+                         // Copyright Statement.
+                         i18n("(c) %{CURRENT_YEAR}"));
+    aboutData.addAuthor(i18nc("@info:credit", "%{AUTHOR}"),
+                        i18nc("@info:credit", "Author Role"),
+                        QStringLiteral("%{EMAIL}"),
+                        QStringLiteral("https://yourwebsite.com"));
+    KAboutData::setApplicationData(aboutData);
+
+    QQmlApplicationEngine engine;
+
+    auto config = %{APPNAME}Config::self();
+
+    qmlRegisterSingletonInstance("org.kde.%{APPNAME}", 1, 0, "Config", config);
+
+    AboutType about;
+    qmlRegisterSingletonInstance("org.kde.%{APPNAME}", 1, 0, "AboutType", &about);
+
+    App application;
+    qmlRegisterSingletonInstance("org.kde.%{APPNAME}", 1, 0, "App", &application);
+
+    engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
+    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
+
+    if (engine.rootObjects().isEmpty()) {
+        return -1;
+    }
+
+    return app.exec();
+}
diff --git a/templates/kirigami/src/resources.qrc b/templates/kirigami/src/resources.qrc
new file mode 100644 (file)
index 0000000..9c5b3b4
--- /dev/null
@@ -0,0 +1,10 @@
+<!--
+    SPDX-License-Identifier: CC0-1.0
+    SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>
+-->
+<RCC>
+    <qresource prefix="/">
+        <file alias="main.qml">contents/ui/main.qml</file>
+        <file alias="About.qml">contents/ui/About.qml</file>
+    </qresource>
+</RCC>
diff --git a/tests/BasicListItemTest.qml b/tests/BasicListItemTest.qml
new file mode 100644 (file)
index 0000000..3d5ead2
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Nate Graham <nate@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    GridLayout {
+        anchors.fill: parent
+        anchors.margins: Kirigami.Units.gridUnit
+
+        rows: 3
+        rowSpacing: Kirigami.Units.gridUnit
+        columns: 3
+        columnSpacing: Kirigami.Units.gridUnit
+
+        // Icon + Label
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        icon: "edit-bomb"
+                        text: "Boom!"
+                    }
+                }
+            }
+        }
+
+        // Label + space reserved for icon
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label + space reserved for icon"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        text: "Boom!"
+                        reserveSpaceForIcon: true
+                    }
+                }
+            }
+        }
+
+        // Icon + Label + leading and trailing items
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label + leading and trailing items"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        leading: Rectangle {
+                            radius: width * 0.5
+                            width: Kirigami.Units.largeSpacing
+                            height: Kirigami.Units.largeSpacing
+                            Kirigami.Theme.colorSet: Kirigami.Theme.View
+                            color: Kirigami.Theme.neutralTextColor
+                        }
+                        leadingFillVertically: false
+
+                        icon: "edit-bomb"
+                        text: "Boom!"
+
+                        trailing: QQC2.Button {
+                            text: "Defuse the bomb!"
+                            icon.name: "edit-delete"
+                        }
+                    }
+                }
+            }
+        }
+
+        // Icon + Label + subtitle
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label + subtitle"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        icon: "edit-bomb"
+                        text: "Boom!"
+                        subtitle: "smaller boom"
+                    }
+                }
+            }
+        }
+
+        // Icon + Label + space reserved for subtitle
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label + space reserved for subtitle"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        icon: "edit-bomb"
+                        text: "Boom!"
+                        reserveSpaceForSubtitle: true
+                    }
+                }
+            }
+        }
+
+        // Icon + Label + subtitle + leading and trailing items
+        ColumnLayout {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            Kirigami.Heading {
+                text: "Icon + Label + subtitle + leading and trailing items"
+                level: 3
+                Layout.fillWidth: true
+                wrapMode: Text.Wrap
+            }
+            QQC2.ScrollView {
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                Component.onCompleted: {
+                    if (background) {
+                        background.visible = true;
+                    }
+                }
+                ListView {
+                    model: 3
+                    delegate: Kirigami.BasicListItem {
+                        leading: Rectangle {
+                            radius: width * 0.5
+                            width: Kirigami.Units.largeSpacing
+                            height: Kirigami.Units.largeSpacing
+                            Kirigami.Theme.colorSet: Kirigami.Theme.View
+                            color: Kirigami.Theme.neutralTextColor
+                        }
+                        leadingFillVertically: false
+
+                        icon: "edit-bomb"
+                        text: "Boom!"
+                        subtitle: "smaller boom"
+
+                        trailing: QQC2.Button {
+                            text: "Defuse the bomb!"
+                            icon.name: "edit-delete"
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/CardTest.qml b/tests/CardTest.qml
new file mode 100644 (file)
index 0000000..deb990e
--- /dev/null
@@ -0,0 +1,73 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Item {
+    width: 600
+    height: 600
+
+    Kirigami.Card {
+        width: 300
+        anchors.centerIn: parent
+
+        banner.title: "Card"
+        banner.titleIcon: "document-new"
+        banner.titleAlignment: alignCombo.currentValue
+        banner.source: "/usr/share/wallpapers/Next/contents/screenshot.png"
+
+        headerOrientation: orientationCombo.currentValue
+
+        contentItem: Label {
+            text: "Card Contents"
+        }
+
+        actions: [
+            Kirigami.Action {
+                icon.name: "document-new"
+                text: "Action 1"
+            },
+            Kirigami.Action {
+                icon.name: "document-new"
+                text: "Action 2"
+            }
+        ]
+    }
+
+    RowLayout {
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+
+        ComboBox {
+            id: alignCombo
+
+            model: [
+                { text: "Top Left", align: Qt.AlignLeft | Qt.AlignTop },
+                { text: "Top Center", align: Qt.AlignHCenter | Qt.AlignTop },
+                { text: "Top Right", align: Qt.AlignRight | Qt.AlignTop },
+                { text: "Center Left", align: Qt.AlignLeft | Qt.AlignVCenter },
+                { text: "Center", align: Qt.AlignHCenter | Qt.AlignVCenter },
+                { text: "Center Right", align: Qt.AlignRight | Qt.AlignVCenter },
+                { text: "Bottom Left", align: Qt.AlignLeft | Qt.AlignBottom },
+                { text: "Bottom Center", align: Qt.AlignHCenter | Qt.AlignBottom },
+                { text: "Bottom Right", align: Qt.AlignRight | Qt.AlignBottom }
+            ]
+
+            textRole: "text"
+            valueRole: "align"
+        }
+
+        ComboBox {
+            id: orientationCombo
+
+            model: [
+                { text: "Vertical", orientation: Qt.Vertical },
+                { text: "Horizontal", orientation: Qt.Horizontal }
+            ]
+
+            textRole: "text"
+            valueRole: "orientation"
+        }
+    }
+}
diff --git a/tests/KeyboardListTest.qml b/tests/KeyboardListTest.qml
new file mode 100644 (file)
index 0000000..87816c7
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: main
+
+    pageStack.initialPage: Kirigami.ScrollablePage {
+        ListView {
+            model: 10
+            delegate: Rectangle {
+                width: 100
+                height: 30
+                color: ListView.isCurrentItem ? "red" : "white"
+            }
+        }
+    }
+}
diff --git a/tests/KeyboardTest.qml b/tests/KeyboardTest.qml
new file mode 100644 (file)
index 0000000..2cf179f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: main
+
+    Component {
+        id: keyPage
+        Kirigami.Page {
+            id: page
+
+            // Don't remove, used in autotests
+            readonly property alias lastKey: see.text
+
+            Label {
+                id: see
+                anchors.centerIn: parent
+                color: page.activeFocus ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor
+            }
+
+            Keys.onPressed: event => {
+                if (event.text) {
+                    see.text = event.text
+                } else {
+                    see.text = event.key
+                }
+            }
+
+            Keys.onEnterPressed: main.showPassiveNotification("page!")
+        }
+    }
+
+    header: Label {
+        padding: Kirigami.Units.largeSpacing
+        text: `focus: ${main.activeFocusItem}, current: ${main.pageStack.currentIndex}`
+    }
+
+    Component.onCompleted: {
+        main.pageStack.push(keyPage)
+        main.pageStack.push(keyPage)
+    }
+}
diff --git a/tests/NavigationTabBarTest.qml b/tests/NavigationTabBarTest.qml
new file mode 100644 (file)
index 0000000..783efb2
--- /dev/null
@@ -0,0 +1,95 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.ApplicationWindow {
+    width: 640
+    height: 480
+    visible: true
+
+    QQC2.SwipeView {
+        id: swipeView
+
+        anchors.fill: parent
+        currentIndex: navTabBar.currentIndex
+
+        QQC2.Page {
+            contentItem: QQC2.Label {
+                text: "page1"
+                horizontalAlignment: Text.AlignHCenter
+            }
+        }
+        QQC2.Page {
+            contentItem: QQC2.Label {
+                text: "page2"
+                horizontalAlignment: Text.AlignHCenter
+            }
+        }
+        QQC2.Page {
+            contentItem: QQC2.Label {
+                text: "page3"
+                horizontalAlignment: Text.AlignHCenter
+            }
+        }
+        QQC2.Page {
+            contentItem: QQC2.Label {
+                text: "page4"
+                horizontalAlignment: Text.AlignHCenter
+            }
+        }
+        onCurrentIndexChanged: navTabBar.currentIndex = swipeView.currentIndex
+    }
+
+    footer: Kirigami.NavigationTabBar {
+        id: navTabBar
+
+        currentIndex: swipeView.currentIndex
+
+        Kirigami.NavigationTabButton {
+            visible: true
+            width: navTabBar.buttonWidth
+            icon.name: "document-save"
+            text: `test ${tabIndex + 1}`
+            QQC2.ButtonGroup.group: navTabBar.tabGroup
+        }
+        Kirigami.NavigationTabButton {
+            visible: false
+            width: navTabBar.buttonWidth
+            icon.name: "document-send"
+            text: `test ${tabIndex + 1}`
+            QQC2.ButtonGroup.group: navTabBar.tabGroup
+        }
+        actions: [
+            Kirigami.Action {
+                visible: true
+                icon.name: "edit-copy"
+                icon.height: 32
+                icon.width: 32
+                text: "test 3"
+                checked: true
+            },
+            Kirigami.Action {
+                visible: true
+                icon.name: "edit-cut"
+                text: "test 4"
+                checkable: true
+            },
+            Kirigami.Action {
+                visible: false
+                icon.name: "edit-paste"
+                text: "test 5"
+            },
+            Kirigami.Action {
+                visible: true
+                icon.source: "../logo.png"
+                text: "test 6"
+                checkable: true
+            }
+        ]
+    }
+}
diff --git a/tests/OverlayFocusTest.qml b/tests/OverlayFocusTest.qml
new file mode 100644 (file)
index 0000000..7617ddb
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  SPDX-FileCopyrightText: 2021 Ismael Asensio <isma.af@gmail.com>
+ *  SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Rectangle {
+    id: background
+
+    implicitWidth: 600
+    implicitHeight: 600
+    color: Kirigami.Theme.backgroundColor
+
+    Kirigami.FormLayout {
+        id: layout
+        anchors.centerIn: parent
+
+        QQC2.Button {
+            Layout.fillWidth: true
+            text: "Open overlay sheet"
+            onClicked: sheet.open()
+        }
+    }
+
+    Kirigami.OverlaySheet {
+        id: sheet
+        parent: background
+
+        header: QQC2.TextField {
+            id: headerText
+            focus: true
+        }
+        footer: QQC2.TextField {
+            id: footerText
+        }
+
+        ListView {
+            id: content
+            model: 10
+
+            delegate: Kirigami.BasicListItem {
+                label: "Item " + modelData
+            }
+        }
+    }
+}
diff --git a/tests/OverlayTest.qml b/tests/OverlayTest.qml
new file mode 100644 (file)
index 0000000..073ab80
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  SPDX-FileCopyrightText: 2022 Aleix Pol <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    height: 720
+    width: 360
+    visible: true
+
+    Kirigami.OverlaySheet {
+        id: sheet
+
+        title: "Certificate Viewer"
+
+        ColumnLayout {
+            QQC2.DialogButtonBox {
+                Layout.fillWidth: true
+
+                QQC2.Button {
+                    QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.ActionRole
+                    text: "Export…"
+                }
+
+                QQC2.Button {
+                    QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.DestructiveRole
+                    text: "Close"
+                    icon.name: "dialog-close"
+                }
+            }
+        }
+    }
+
+    Timer {
+        interval: 150
+        running: true
+        onTriggered: sheet.open()
+    }
+}
diff --git a/tests/ShadowedImageTest.qml b/tests/ShadowedImageTest.qml
new file mode 100644 (file)
index 0000000..593e520
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    width: 600
+    height: 800
+    visible: true
+
+    pageStack.initialPage: Kirigami.Page {
+        leftPadding: 0
+        rightPadding: 0
+        topPadding: 0
+        bottomPadding: 0
+
+        Column {
+            anchors.centerIn: parent
+
+            Kirigami.ShadowedImage {
+                width: 400
+                height: 300
+
+                color: Kirigami.Theme.highlightColor
+
+                source: "/usr/share/wallpapers/Next/contents/images/1024x768.jpg"
+
+                radius: radiusSlider.value
+
+                shadow.size: sizeSlider.value
+                shadow.xOffset: xOffsetSlider.value
+                shadow.yOffset: yOffsetSlider.value
+
+                border.width: borderWidthSlider.value
+                border.color: Kirigami.Theme.textColor
+
+                corners.topLeftRadius: topLeftSlider.value
+                corners.topRightRadius: topRightSlider.value
+                corners.bottomLeftRadius: bottomLeftSlider.value
+                corners.bottomRightRadius: bottomRightSlider.value
+            }
+
+            Kirigami.FormLayout {
+                Item { Kirigami.FormData.isSection: true }
+
+                Slider { id: radiusSlider; from: 0; to: 200; Kirigami.FormData.label: "Overall Radius" }
+                Slider { id: topLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Left Radius" }
+                Slider { id: topRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Right Radius" }
+                Slider { id: bottomLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Left Radius" }
+                Slider { id: bottomRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Right Radius" }
+
+                Slider { id: sizeSlider; from: 0; to: 100; Kirigami.FormData.label: "Shadow Size" }
+                Slider { id: xOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow X-Offset" }
+                Slider { id: yOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow Y-Offset" }
+
+                Slider { id: borderWidthSlider; from: 0; to: 50; Kirigami.FormData.label: "Border Width" }
+            }
+        }
+    }
+}
diff --git a/tests/ShadowedRectangleTest.qml b/tests/ShadowedRectangleTest.qml
new file mode 100644 (file)
index 0000000..c62ae36
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    width: 600
+    height: 800
+    visible: true
+
+    pageStack.initialPage: Kirigami.Page {
+        leftPadding: 0
+        rightPadding: 0
+        topPadding: 0
+        bottomPadding: 0
+
+        Column {
+            anchors.centerIn: parent
+
+            Kirigami.ShadowedRectangle {
+                width: 400
+                height: 300
+
+                color: Kirigami.Theme.highlightColor
+
+                radius: radiusSlider.value
+
+                shadow.size: sizeSlider.value
+                shadow.xOffset: xOffsetSlider.value
+                shadow.yOffset: yOffsetSlider.value
+
+                border.width: borderWidthSlider.value
+                border.color: Kirigami.Theme.textColor
+
+                corners.topLeftRadius: topLeftSlider.value
+                corners.topRightRadius: topRightSlider.value
+                corners.bottomLeftRadius: bottomLeftSlider.value
+                corners.bottomRightRadius: bottomRightSlider.value
+            }
+
+            Kirigami.FormLayout {
+                Item { Kirigami.FormData.isSection: true }
+
+                Slider { id: radiusSlider; from: 0; to: 200; Kirigami.FormData.label: "Overall Radius" }
+                Slider { id: topLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Left Radius" }
+                Slider { id: topRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Right Radius" }
+                Slider { id: bottomLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Left Radius" }
+                Slider { id: bottomRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Right Radius" }
+
+                Slider { id: sizeSlider; from: 0; to: 100; Kirigami.FormData.label: "Shadow Size" }
+                Slider { id: xOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow X-Offset" }
+                Slider { id: yOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow Y-Offset" }
+
+                Slider { id: borderWidthSlider; from: 0; to: 50; Kirigami.FormData.label: "Border Width" }
+            }
+        }
+    }
+}
diff --git a/tests/actionsMenu.qml b/tests/actionsMenu.qml
new file mode 100644 (file)
index 0000000..fd1625c
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: main
+
+    pageStack.initialPage: Kirigami.Page {
+        QQC2.Button {
+            text: "button"
+            onClicked: menu.popup()
+            QQC2.Menu {
+                id: menu
+
+                QQC2.MenuItem { text: "xxx" }
+                QQC2.MenuItem { text: "xxx" }
+                QQC2.Menu {
+                    title: "yyy"
+                    QQC2.MenuItem { text: "yyy" }
+                    QQC2.MenuItem { text: "yyy" }
+                }
+            }
+        }
+
+        title: "aaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa"
+        actions {
+            main:  Kirigami.Action { icon.name: "kate"; text: "BonDia" }
+            left : Kirigami.Action { icon.name: "kate"; text: "BonDia" }
+            right: Kirigami.Action { icon.name: "kate"; text: "BonDia" }
+        }
+
+        QQC2.ActionGroup {
+            id: group
+        }
+
+        actions.contextualActions: [
+            Kirigami.Action {
+                text: "submenus"
+                icon.name: "kalgebra"
+
+                Kirigami.Action { text: "xxx"; onTriggered: console.log("xxx") }
+                Kirigami.Action { text: "xxx"; onTriggered: console.log("xxx") }
+                Kirigami.Action { text: "xxx"; onTriggered: console.log("xxx") }
+                Kirigami.Action {
+                    text: "yyy"
+                    Kirigami.Action { text: "yyy" }
+                    Kirigami.Action { text: "yyy" }
+                    Kirigami.Action { text: "yyy" }
+                    Kirigami.Action { text: "yyy" }
+                }
+            },
+            Kirigami.Action {
+                id: optionsAction
+                text: "Options"
+                icon.name: "kate"
+
+                Kirigami.Action {
+                    QQC2.ActionGroup.group: group
+                    text: "A"
+                    checkable: true
+                    checked: true
+                }
+                Kirigami.Action {
+                    QQC2.ActionGroup.group: group
+                    text: "B"
+                    checkable: true
+                }
+                Kirigami.Action {
+                    QQC2.ActionGroup.group: group
+                    text: "C"
+                    checkable: true
+                }
+            },
+            Kirigami.Action { text: "stuffing..." },
+            Kirigami.Action { text: "stuffing..." },
+            Kirigami.Action { text: "stuffing..." },
+            Kirigami.Action { text: "stuffing..." },
+            Kirigami.Action { text: "stuffing..." }
+        ]
+    }
+}
diff --git a/tests/cardsList.qml b/tests/cardsList.qml
new file mode 100644 (file)
index 0000000..d6b84c4
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+
+    Component {
+        id: delegateComponent
+        Kirigami.Card {
+            contentItem: Label { text: ourlist.prefix + index }
+        }
+    }
+
+    pageStack.initialPage: Kirigami.ScrollablePage {
+
+        Kirigami.CardsListView {
+            id: ourlist
+            property string prefix: "ciao "
+
+            delegate: delegateComponent
+
+            model: 100
+        }
+    }
+}
diff --git a/tests/swipeListItemTest.qml b/tests/swipeListItemTest.qml
new file mode 100644 (file)
index 0000000..0af244b
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
+ *
+ *  SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import org.kde.kirigami 2.20 as Kirigami
+
+Kirigami.ApplicationWindow {
+    id: main
+
+    pageStack.initialPage: Kirigami.ScrollablePage {
+        ListView {
+            model: 25
+            delegate: Kirigami.SwipeListItem {
+                supportsMouseEvents: false
+                actions: [
+                    Kirigami.Action {
+                        iconName: "go-up"
+                    }
+                ]
+                Label {
+                    elide: Text.ElideRight
+                    text: "big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana big banana"
+                }
+            }
+        }
+    }
+}
diff --git a/tests/wheelhandler/ScrollView.qml b/tests/wheelhandler/ScrollView.qml
new file mode 100644 (file)
index 0000000..f4bad04
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Templates 2.15 as T
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+T.ScrollView {
+    id: control
+    implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+                            contentWidth + leftPadding + rightPadding)
+    implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+                             contentHeight + topPadding + bottomPadding)
+
+    leftPadding: mirrored && T.ScrollBar.vertical && T.ScrollBar.vertical.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.vertical.width : 0
+    rightPadding: !mirrored && T.ScrollBar.vertical && T.ScrollBar.vertical.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.vertical.width : 0
+    bottomPadding: T.ScrollBar.horizontal && T.ScrollBar.horizontal.visible && !Kirigami.Settings.isMobile ? T.ScrollBar.horizontal.height : 0
+
+    data: [
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: control.contentItem
+        }
+    ]
+
+    T.ScrollBar.vertical: QQC2.ScrollBar {
+        parent: control
+        x: control.mirrored ? 0 : control.width - width
+        y: control.topPadding
+        height: control.availableHeight
+        active: control.T.ScrollBar.horizontal.active
+        stepSize: wheelHandler.verticalStepSize / control.contentHeight
+    }
+
+    T.ScrollBar.horizontal: QQC2.ScrollBar {
+        parent: control
+        x: control.leftPadding
+        y: control.height - height
+        width: control.availableWidth
+        active: control.T.ScrollBar.vertical.active
+        stepSize: wheelHandler.horizontalStepSize / control.contentWidth
+    }
+}
diff --git a/tests/wheelhandler/WheelHandlerFlickableTest.qml b/tests/wheelhandler/WheelHandlerFlickableTest.qml
new file mode 100644 (file)
index 0000000..4e4f65a
--- /dev/null
@@ -0,0 +1,100 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.ApplicationWindow {
+    id: root
+    width: flickable.implicitWidth
+    height: flickable.implicitHeight
+    visible: true
+
+    Flickable {
+        id: flickable
+        anchors.fill: parent
+        implicitWidth: wheelHandler.horizontalStepSize * 10 + leftMargin + rightMargin
+        implicitHeight: wheelHandler.verticalStepSize * 10 + topMargin + bottomMargin
+
+        leftMargin: QQC2.ScrollBar.vertical.visible && QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        rightMargin: QQC2.ScrollBar.vertical.visible && !QQC2.ScrollBar.vertical.mirrored ? QQC2.ScrollBar.vertical.width : 0
+        bottomMargin: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.height : 0
+
+        contentWidth: contentItem.childrenRect.width
+        contentHeight: contentItem.childrenRect.height
+
+        Kirigami.WheelHandler {
+            id: wheelHandler
+            target: flickable
+            filterMouseEvents: true
+            keyNavigationEnabled: true
+        }
+
+        QQC2.ScrollBar.vertical: QQC2.ScrollBar {
+            parent: flickable.parent
+            height: flickable.height - flickable.topMargin - flickable.bottomMargin
+            x: mirrored ? 0 : flickable.width - width
+            y: flickable.topMargin
+            active: flickable.QQC2.ScrollBar.horizontal.active
+            stepSize: wheelHandler.verticalStepSize / flickable.contentHeight
+        }
+
+        QQC2.ScrollBar.horizontal: QQC2.ScrollBar {
+            parent: flickable.parent
+            width: flickable.width - flickable.leftMargin - flickable.rightMargin
+            x: flickable.leftMargin
+            y: flickable.height - height
+            active: flickable.QQC2.ScrollBar.vertical.active
+            stepSize: wheelHandler.horizontalStepSize / flickable.contentWidth
+        }
+
+        Grid {
+            columns: Math.sqrt(visibleChildren.length)
+            Repeater {
+                model: 500
+                delegate: Rectangle {
+                    implicitWidth: wheelHandler.horizontalStepSize
+                    implicitHeight: wheelHandler.verticalStepSize
+                    gradient: Gradient {
+                        orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                        GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                        GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    }
+                }
+            }
+            QQC2.Button {
+                id: enableSliderButton
+                width: wheelHandler.horizontalStepSize
+                height: wheelHandler.verticalStepSize
+                contentItem: QQC2.Label {
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                    text: "Enable Slider"
+                    wrapMode: Text.Wrap
+                }
+                checked: true
+            }
+            QQC2.Slider {
+                id: slider
+                enabled: enableSliderButton.checked
+                width: wheelHandler.horizontalStepSize
+                height: wheelHandler.verticalStepSize
+            }
+            Repeater {
+                model: 500
+                delegate: Rectangle {
+                    implicitWidth: wheelHandler.horizontalStepSize
+                    implicitHeight: wheelHandler.verticalStepSize
+                    gradient: Gradient {
+                        orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                        GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                        GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/wheelhandler/WheelHandlerScrollViewTest.qml b/tests/wheelhandler/WheelHandlerScrollViewTest.qml
new file mode 100644 (file)
index 0000000..5d66365
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Templates 2.15 as T
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.ApplicationWindow {
+    id: root
+    width: 200 * Qt.styleHints.wheelScrollLines + scrollView.leftPadding + scrollView.rightPadding
+    height: 200 * Qt.styleHints.wheelScrollLines + scrollView.topPadding + scrollView.bottomPadding
+    visible: true
+    ScrollView {
+        id: scrollView
+        anchors.fill: parent
+        Grid {
+            columns: Math.sqrt(visibleChildren.length)
+            Repeater {
+                model: 500
+                delegate: Rectangle {
+                    implicitWidth: 20 * Qt.styleHints.wheelScrollLines
+                    implicitHeight: 20 * Qt.styleHints.wheelScrollLines
+                    gradient: Gradient {
+                        orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                        GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                        GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    }
+                }
+            }
+            QQC2.Button {
+                id: enableSliderButton
+                width: 20 * Qt.styleHints.wheelScrollLines
+                height: 20 * Qt.styleHints.wheelScrollLines
+                contentItem: QQC2.Label {
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                    text: "Enable Slider"
+                    wrapMode: Text.Wrap
+                }
+                checked: true
+            }
+            QQC2.Slider {
+                id: slider
+                enabled: enableSliderButton.checked
+                width: 20 * Qt.styleHints.wheelScrollLines
+                height: 20 * Qt.styleHints.wheelScrollLines
+            }
+            Repeater {
+                model: 500
+                delegate: Rectangle {
+                    implicitWidth: 20 * Qt.styleHints.wheelScrollLines
+                    implicitHeight: 20 * Qt.styleHints.wheelScrollLines
+                    gradient: Gradient {
+                        orientation: index % 2 ? Gradient.Vertical : Gradient.Horizontal
+                        GradientStop { position: 0; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                        GradientStop { position: 1; color: Qt.rgba(Math.random(),Math.random(),Math.random(),1) }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/wheelhandler/WheelHandlerScrollViewTextAreaTest.qml b/tests/wheelhandler/WheelHandlerScrollViewTextAreaTest.qml
new file mode 100644 (file)
index 0000000..fef326d
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+
+import QtQuick 2.15
+import QtQml 2.15
+import QtQuick.Templates 2.15 as T
+import QtQuick.Controls 2.15 as QQC2
+
+import org.kde.kirigami 2.20 as Kirigami
+
+QQC2.ApplicationWindow {
+    id: root
+    width: 600
+    height: 600
+    visible: true
+
+    ScrollView {
+        id: scrollView
+        anchors.fill: parent
+        QQC2.TextArea {
+            text: "Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+        }
+    }
+}
diff --git a/tests/wheelhandler/scrollableqtextedit.ui b/tests/wheelhandler/scrollableqtextedit.ui
new file mode 100644 (file)
index 0000000..45c5b0d
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <property name="leftMargin">
+     <number>0</number>
+    </property>
+    <property name="topMargin">
+     <number>0</number>
+    </property>
+    <property name="rightMargin">
+     <number>0</number>
+    </property>
+    <property name="bottomMargin">
+     <number>0</number>
+    </property>
+    <property name="spacing">
+     <number>0</number>
+    </property>
+    <item row="0" column="0">
+     <widget class="QTextEdit" name="textEdit">
+      <property name="readOnly">
+       <bool>false</bool>
+      </property>
+      <property name="html">
+       <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+      </property>
+      <property name="acceptRichText">
+       <bool>false</bool>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>