├── .gitignore
├── Changelog.md
├── ReadMe.md
├── Theming.md
├── build
├── install
├── kpac
├── package
├── contents
│ ├── config
│ │ ├── config.qml
│ │ └── main.xml
│ ├── icons
│ │ ├── view-list-alphabetically.svg
│ │ ├── view-list-categorically.svg
│ │ └── view-tilesonly.svg
│ └── ui
│ │ ├── AppContextMenu.qml
│ │ ├── AppObject.qml
│ │ ├── AppToolButton.qml
│ │ ├── AppToolButtonStyle.qml
│ │ ├── AppletConfig.qml
│ │ ├── AppsModel.qml
│ │ ├── AppsView.qml
│ │ ├── Base64JsonString.qml
│ │ ├── ButtonShadow.qml
│ │ ├── FlatButton.qml
│ │ ├── Hierarchy.md
│ │ ├── HoverOutlineButtonEffect.qml
│ │ ├── HoverOutlineButtonStyle.qml
│ │ ├── HoverOutlineEffect.qml
│ │ ├── JumpToLetterView.qml
│ │ ├── JumpToSectionButton.qml
│ │ ├── JumpToSectionView.qml
│ │ ├── KickerAppModel.qml
│ │ ├── KickerListModel.qml
│ │ ├── KickerListView.qml
│ │ ├── KickerSectionHeader.qml
│ │ ├── LauncherIcon.qml
│ │ ├── MenuListItem.qml
│ │ ├── Popup.qml
│ │ ├── RoundShadow.qml
│ │ ├── SearchField.qml
│ │ ├── SearchFiltersView.qml
│ │ ├── SearchFiltersViewItem.qml
│ │ ├── SearchModel.qml
│ │ ├── SearchResultsList.qml
│ │ ├── SearchResultsModel.qml
│ │ ├── SearchResultsView.qml
│ │ ├── SearchStackView.qml
│ │ ├── SearchView.qml
│ │ ├── SidebarContextMenu.qml
│ │ ├── SidebarFavouritesView.qml
│ │ ├── SidebarItem.qml
│ │ ├── SidebarItemRepeater.qml
│ │ ├── SidebarMenu.qml
│ │ ├── SidebarMenuShadows.qml
│ │ ├── SidebarView.qml
│ │ ├── SidebarViewButton.qml
│ │ ├── TileEditorColorField.qml
│ │ ├── TileEditorColorGroup.qml
│ │ ├── TileEditorField.qml
│ │ ├── TileEditorFileField.qml
│ │ ├── TileEditorGroupBox.qml
│ │ ├── TileEditorPresetTileButton.qml
│ │ ├── TileEditorPresetTiles.qml
│ │ ├── TileEditorRectField.qml
│ │ ├── TileEditorSpinBox.qml
│ │ ├── TileEditorView.qml
│ │ ├── TileGrid.qml
│ │ ├── TileGridPresets.qml
│ │ ├── TileGridSplash.qml
│ │ ├── TileItem.qml
│ │ ├── TileItemView.qml
│ │ ├── Utils.js
│ │ ├── config
│ │ ├── ConfigExportLayout.qml
│ │ ├── ConfigGeneral.qml
│ │ ├── ConfigurationShortcuts.qml
│ │ └── TextAreaBase64JsonString.qml
│ │ ├── lib
│ │ ├── ConfigAdvanced.qml
│ │ ├── Logger.qml
│ │ ├── Requests.js
│ │ └── XdgUserDir.qml
│ │ ├── libconfig
│ │ ├── CheckBox.qml
│ │ ├── ColorField.qml
│ │ ├── ComboBox.qml
│ │ ├── FormKCM.qml
│ │ ├── Heading.qml
│ │ ├── IconField.qml
│ │ ├── RadioButtonGroup.qml
│ │ ├── SpinBox.qml
│ │ ├── TextArea.qml
│ │ └── TextAreaStringList.qml
│ │ └── main.qml
├── metadata.json
└── translate
│ ├── ReadMe.md
│ ├── de.po
│ ├── es.po
│ ├── fa.po
│ ├── fi.po
│ ├── fr.po
│ ├── he.po
│ ├── hr.po
│ ├── id.po
│ ├── ja.po
│ ├── ko.po
│ ├── nl.po
│ ├── pl.po
│ ├── pt.po
│ ├── pt_BR.po
│ ├── ro.po
│ ├── ru.po
│ ├── sl.po
│ ├── template.pot
│ ├── tr.po
│ ├── zh_CN.po
│ └── zh_TW.po
└── uninstall
/.gitignore:
--------------------------------------------------------------------------------
1 | *.plasmoid
2 | *.qmlc
3 | *.jsc
4 | *.mo
5 |
--------------------------------------------------------------------------------
/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Tiled Menu
2 |
3 | https://store.kde.org/p/2142716/
4 |
5 | A menu based on Windows 10's Start Menu.
6 |
7 | * Supports:
8 | * Pin/Favourite apps/files through the context menu (or by dragging them from dolphin).
9 | * Resizing (permanently) the size of the menu by Meta + Right Clicking and dragging.
10 | * Any size tile 1x1, 2x2, 4x4, 4x2, 1x3, etc.
11 | * Easily edit the background image of a tile.
12 | * Customizable sidebar shortcuts.
13 | * Jump to Letter/Category (can also default to this view)
14 | * Defaulting to only showing the tiles.
15 | * Labeling Groups of Tiles + Move Groups of Tiles + Sorting items in the group
16 | * Does not support (Win10):
17 | * Tile Groups ("Folders")
18 |
19 | ## Screenshots
20 |
21 | 
22 |
23 | ## Theming
24 |
25 | Read the [theming guide](Theming.md) to develop Desktop/Icon Themes for this widget.
26 |
27 | ## Translating
28 |
29 | See the [package/translate](package/translate) folder for instructions on translating.
30 |
--------------------------------------------------------------------------------
/Theming.md:
--------------------------------------------------------------------------------
1 | Fair warning, I might look for `widgets/tilemenu.svg` in the future for a the tile background (normal/hover) + sidebar (closed/opened) + sidebar buttons (normal/hover/pressed). So don't get toooo comfortable since I'm still developing this widget.
2 |
3 | ## Desktop Theme .svgs
4 |
5 | https://techbase.kde.org/Development/Tutorials/Plasma5/ThemeDetails
6 |
7 | * Sidebar / Power Menu
8 | * Background
9 | * Defaults to drawing `theme.backgroundColor` at 50% transparency when closed, and 0% transparency when open. It used to be black (`#000`) in older versions.
10 | * `widgets/frame.svg` prefix: `raised` Background
11 | * Note that `theme.backgroundColor` is drawn undrneath the svg when the sidebar is open since 90% of themes have a transparent image.
12 | * App List
13 | * Items
14 | * `widgets/button.svg`
15 | * Scrollbar
16 | * `widgets/scrollbar.svg`
17 | * Search Box
18 | * `widgets/lineedit.svg`
19 |
20 |
21 | ## Color Theme
22 |
23 | * `theme.backgroundColor`
24 | * Drawn under the sidebar background svg when using the desktop theme.
25 | * `theme.buttonBackgroundColor`
26 | * The default tile background color.
27 |
28 |
29 | ## Icons
30 |
31 | * Sidebar
32 | * `open-menu-symbolic` Menu
33 | * `view-sort-ascending-symbolic` Apps
34 | * `system-search-symbolic` Search
35 | * `open-menu-symbolic` Menu
36 | * ...
37 | * `folder-open-symbolic` File Manager
38 | * `configure` Settings
39 | * `system-shutdown-symbolic` Power
40 | * `system-lock-screen` Lock
41 | * `system-log-out` Logout
42 | * `system-save-session` Save Session
43 | * `system-switch-user` Switch User
44 | * `system-suspend` Suspend
45 | * `system-suspend-hibernate` Hibernate
46 | * `system-reboot` Reboot
47 | * `system-shutdown` Shutdown
48 | * Search View
49 | * Filter Bar
50 | * `system-search-symbolic` All/Default Filter
51 | * `window` Apps Filter
52 | * `document-new` File Filter
53 | * `globe` Bookmarks Filter
54 |
55 |
--------------------------------------------------------------------------------
/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Version 22
3 |
4 | packageDir="package"
5 | i18nDir="package/translate"
6 | qtMinVer="6.6"
7 | kfMinVer="6.0"
8 | plasmaMinVer="6.0"
9 | filenameTag="plasma${plasmaMinVer}"
10 | filenameTag=`echo "$filenameTag" | sed 's/\./\-/'`
11 |
12 | function printHelp() {
13 | echo "sh ./build For building a .zip for KDE Store"
14 | echo "sh ./build --i18n-only To package for distros like Arch we only need to convert"
15 | echo " translation files .po => .mo with gettext's msgfmt."
16 | }
17 |
18 | # Builds .zip or .plasmoid for uploading to https://store.kde.org
19 | function buildZip() {
20 | python3 ./kpac --dir "$packageDir" --i18ndir "$i18nDir" \
21 | build --tag "$filenameTag"
22 | }
23 |
24 | # For distributing with distro packaging (like AUR) we only need to convert
25 | # the translation *.po files to *.mo files with gettext's msgfmt command.
26 | # Eg: "package/translate/fr.po" => "package/contents/locale/fr/LC_MESSAGES/plasma_applet_com.github.zren.widgetname.mo"
27 | function buildI18nOnly() {
28 | python3 ./kpac --dir "$packageDir" --i18ndir "$i18nDir" \
29 | i18n --no-merge
30 | }
31 |
32 | showHelp=false
33 | i18nOnly=false
34 | for arg in "$@"; do
35 | case "$arg" in
36 | --i18n-only) i18nOnly=true;;
37 | -h|--help) showHelp=true;;
38 | *) ;;
39 | esac
40 | done
41 |
42 | if $showHelp; then
43 | # sh ./build --help
44 | printHelp
45 | elif $i18nOnly; then
46 | # sh ./build --i18n-only
47 | buildI18nOnly
48 | else
49 | # sh ./build
50 | buildZip
51 | fi
52 |
--------------------------------------------------------------------------------
/install:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Version 8
3 |
4 | # This script detects if the widget is already installed.
5 | # If it is, it will use --upgrade instead and restart plasmashell.
6 | # Eg: kpackagetool6 --type "Plasma/Applet" --install package
7 | # Eg: kpackagetool6 --type "Plasma/Applet" --upgrade package
8 | # Eg: killall plasmashell ; kstart plasmashell
9 |
10 | if [ -f "$PWD/package/metadata.json" ]; then # Plasma6 (and later versions of Plasma5)
11 | packageNamespace=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPlugin", {}).get("Id", ""))' < "$PWD/package/metadata.json"`
12 | packageServiceType=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPackageStructure",""))' < "$PWD/package/metadata.json"`
13 | if [ -z "$packageServiceType" ]; then # desktoptojson will set KPlugin.ServiceTypes[0] instead of KPackageStructure
14 | packageServiceType=`python3 -c 'import sys, json; print((json.load(sys.stdin).get("KPlugin", {}).get("ServiceTypes", [])+[""])[0])' < "$PWD/package/metadata.json"`
15 | echo "[warning] metadata.json needs KPackageStructure set in Plasma6"
16 | fi
17 | elif [ -f "$PWD/package/metadata.desktop" ]; then # Plasma5
18 | packageNamespace=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
19 | packageServiceType=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-ServiceTypes"`
20 | else
21 | echo "[error] Could not find 'package/metadata.json' or 'package/metadata.desktop'"
22 | exit 1
23 | fi
24 | echo "Namespace: ${packageNamespace}"
25 | echo "Type: ${packageServiceType}"
26 | if [ -z "$packageServiceType" ]; then
27 | echo "[error] Could not parse metadata"
28 | exit 1
29 | fi
30 |
31 |
32 | if command -v kpackagetool6 &> /dev/null ; then kpackagetool="kpackagetool6" # Plasma6
33 | elif command -v kpackagetool5 &> /dev/null ; then kpackagetool="kpackagetool5" # Plasma5
34 | else
35 | echo "[error] Could not find 'kpackagetool6'"
36 | exit 1
37 | fi
38 | if command -v kstart &> /dev/null ; then kstart="kstart" # Plasma6
39 | elif command -v kstart5 &> /dev/null ; then kstart="kstart5" # Plasma5
40 | else
41 | echo "[error] Could not find 'kstart'"
42 | exit 1
43 | fi
44 | restartPlasmashell=false
45 |
46 | for arg in "$@"; do
47 | case "$arg" in
48 | -r) restartPlasmashell=true;;
49 | --restart) restartPlasmashell=true;;
50 | *) ;;
51 | esac
52 | done
53 |
54 | isAlreadyInstalled=false
55 | "$kpackagetool" --type="${packageServiceType}" --show="$packageNamespace" &> /dev/null
56 | if [ $? == 0 ]; then
57 | isAlreadyInstalled=true
58 | fi
59 |
60 | if $isAlreadyInstalled; then
61 | # Eg: kpackagetool6 --type "Plasma/Applet" --upgrade package
62 | "$kpackagetool" -t "${packageServiceType}" -u package
63 | restartPlasmashell=true
64 | else
65 | # Eg: kpackagetool6 --type "Plasma/Applet" --install package
66 | "$kpackagetool" -t "${packageServiceType}" -i package
67 | fi
68 |
69 | if $restartPlasmashell; then
70 | killall plasmashell
71 | "$kstart" plasmashell
72 | fi
73 |
--------------------------------------------------------------------------------
/package/contents/config/config.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.configuration
3 |
4 | ConfigModel {
5 | ConfigCategory {
6 | name: i18n("General")
7 | icon: "configure"
8 | source: "config/ConfigGeneral.qml"
9 | }
10 | ConfigCategory {
11 | name: i18n("Import/Export Layout")
12 | icon: "grid-rectangular"
13 | source: "config/ConfigExportLayout.qml"
14 | }
15 | ConfigCategory {
16 | name: i18n("Advanced")
17 | icon: "applications-development"
18 | source: "lib/ConfigAdvanced.qml"
19 | visible: false
20 | }
21 | ConfigCategory {
22 | name: i18nd("plasma_shell_org.kde.plasma.desktop", "Keyboard Shortcuts")
23 | icon: "preferences-desktop-keyboard"
24 | source: "config/ConfigurationShortcuts.qml"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/package/contents/config/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | false
8 |
9 |
10 |
11 | start-here-kde-symbolic
12 |
13 |
14 | true
15 |
16 |
17 | true
18 |
19 |
20 | false
21 |
22 |
23 | false
24 |
25 |
26 |
27 |
28 | krunner_systemsettings,Dictionary,services,calculator,shell,org.kde.windowedwidgets,org.kde.datetime,baloosearch,locations,unitconverter
29 |
30 |
31 |
32 | true
33 |
34 |
35 |
36 | 1
37 |
38 |
39 | 5
40 |
41 |
42 |
43 | xdg:DOCUMENTS,xdg:PICTURES,org.kde.dolphin.desktop,systemsettings.desktop
44 |
45 |
46 |
47 | Alphabetical
48 |
49 |
50 |
51 |
52 | org.kde.konsole.desktop
53 |
54 |
55 | org.kde.plasma-systemmonitor.desktop
56 |
57 |
58 | org.kde.dolphin.desktop
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 0.8
69 |
70 |
71 | 5
72 |
73 |
74 | false
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | false
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | false
92 |
93 |
94 | false
95 |
96 |
97 | false
98 |
99 |
100 |
101 | left
102 |
103 |
104 |
105 | left
106 |
107 |
108 |
109 | after
110 |
111 |
112 | 36
113 |
114 |
115 | 48
116 |
117 |
118 | 350
119 |
120 |
121 | 6
122 |
123 |
124 | 620
125 |
126 |
127 | 48
128 |
129 |
130 | 30
131 |
132 |
133 | 36
134 |
135 |
136 |
146 |
147 |
153 |
154 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/package/contents/icons/view-list-alphabetically.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/package/contents/icons/view-tilesonly.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/package/contents/ui/AppContextMenu.qml:
--------------------------------------------------------------------------------
1 | // Based off kicker's ActionMenu
2 | import QtQuick
3 | import org.kde.plasma.extras as PlasmaExtras
4 |
5 | Item {
6 | id: root
7 |
8 | property QtObject menu
9 | property Item visualParent
10 | property bool opened: menu ? (menu.status != PlasmaExtras.Menu.Closed) : false
11 | property int tileIndex: -1
12 |
13 | signal closed
14 | signal populateMenu(var menu)
15 |
16 | onOpenedChanged: {
17 | if (!opened) {
18 | closed()
19 | }
20 | }
21 |
22 | onClosed: destroyMenu()
23 |
24 | function open(x, y) {
25 | refreshMenu()
26 |
27 | if (menu.content.length === 0) {
28 | return
29 | }
30 |
31 | if (x && y) {
32 | menu.open(x, y)
33 | } else {
34 | menu.open()
35 | }
36 | }
37 |
38 | function destroyMenu() {
39 | if (menu) {
40 | menu.destroy()
41 | // menu = null // Don't null here. Binding loop: onOpended=false => closed() => destroyMenu() => menu=null => opened=false
42 | logger.debug('AppContextMenu.destroyMenu', menu)
43 | }
44 | }
45 |
46 | function refreshMenu() {
47 | destroyMenu()
48 | menu = contextMenuComponent.createObject(root)
49 | populateMenu(menu)
50 | }
51 |
52 | Component {
53 | id: contextMenuComponent
54 |
55 | PlasmaExtras.Menu {
56 | id: contextMenu
57 | visualParent: root.visualParent
58 |
59 | function newSeperator() {
60 | return Qt.createQmlObject("import org.kde.plasma.extras as PlasmaExtras; PlasmaExtras.MenuItem { separator: true }", contextMenu)
61 | }
62 | function newMenuItem() {
63 | return Qt.createQmlObject("import org.kde.plasma.extras as PlasmaExtras; PlasmaExtras.MenuItem {}", contextMenu)
64 | }
65 |
66 | function addPinToMenuAction(favoriteId) {
67 | var menuItem = menu.newMenuItem()
68 | if (tileGrid.hasAppTile(favoriteId)) {
69 | menuItem.text = i18n("Unpin from Menu")
70 | menuItem.icon = "list-remove"
71 | menuItem.clicked.connect(function() {
72 | if (root.tileIndex >= 0) {
73 | tileGrid.removeIndex(root.tileIndex)
74 | } else {
75 | tileGrid.removeApp(favoriteId)
76 | }
77 | })
78 | } else {
79 | menuItem.text = i18n("Pin to Menu")
80 | menuItem.icon = "bookmark-new"
81 | menuItem.clicked.connect(function() {
82 | tileGrid.addApp(favoriteId)
83 | })
84 | }
85 | menu.addMenuItem(menuItem)
86 | }
87 |
88 | // https://invent.kde.org/plasma/plasma-desktop/-/blob/Plasma/5.8/applets/taskmanager/package/contents/ui/ContextMenu.qml#L75
89 | // https://invent.kde.org/plasma/plasma-desktop/-/blob/Plasma/5.27/applets/taskmanager/package/contents/ui/ContextMenu.qml#L75
90 | // https://invent.kde.org/plasma/plasma-desktop/-/blob/master/applets/taskmanager/package/contents/ui/ContextMenu.qml
91 | function addActionList(actionList, listModel, index) {
92 | // .desktop file Exec actions
93 | // ------
94 | // Pin to Taskbar / Desktop / Panel
95 | // ------
96 | // Recent Documents
97 | // ------
98 | // ...
99 | // ------
100 | // Edit Application
101 | actionList.forEach(function(actionItem) {
102 | // console.log(index, actionItem.actionId, actionItem.actionArgument, actionItem.text)
103 | var menuItem = menu.newMenuItem()
104 | menuItem.text = actionItem.text ? actionItem.text : ""
105 | menuItem.enabled = actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true)
106 | menuItem.separator = actionItem.type == "separator"
107 | menuItem.section = actionItem.type == "title"
108 | menuItem.icon = actionItem.icon ? actionItem.icon : null
109 | menuItem.clicked.connect(function() {
110 | listModel.triggerIndexAction(index, actionItem.actionId, actionItem.actionArgument)
111 | })
112 |
113 | //--- Overrides
114 | if (actionItem.actionId == 'addToDesktop') {
115 | // Remove (user should just drag it)
116 | menu.removeMenuItem(menuItem)
117 | } else if (actionItem.actionId == 'addToPanel') {
118 | // Remove (user should just drag it)
119 | // User usually means to add it to taskmanager anyways.
120 | menu.removeMenuItem(menuItem)
121 | } else if (actionItem.actionId == 'addToTaskManager') {
122 | menuItem.text = i18n("Pin to Taskbar")
123 | menuItem.icon = "bookmark-new"
124 | } else if (actionItem.actionId == 'editApplication') {
125 | // menuItem.text = i18n("Properties")
126 | }
127 |
128 | })
129 | }
130 | }
131 | }
132 |
133 | Component {
134 | id: contextMenuItemComponent
135 |
136 | PlasmaExtras.MenuItem {
137 | property variant actionItem
138 |
139 | text: actionItem.text ? actionItem.text : ""
140 | enabled: actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true)
141 | separator: actionItem.type == "separator"
142 | section: actionItem.type == "title"
143 | icon: actionItem.icon ? actionItem.icon : null
144 |
145 | onClicked: {
146 | actionClicked(actionItem.actionId, actionItem.actionArgument)
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/package/contents/ui/AppObject.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | QtObject {
4 | id: appObj
5 |
6 | property var tile: null
7 |
8 | readonly property bool isGroup: tile && tile.tileType == "group"
9 | readonly property bool isLauncher: !isGroup
10 |
11 | readonly property color defaultBackgroundColor: isGroup ? "transparent" : config.defaultTileColor
12 | readonly property bool defaultShowIcon: isGroup ? false : true
13 | readonly property int defaultTileW: isGroup ? 6 : 2
14 | readonly property int defaultTileH: isGroup ? 1 : 2
15 |
16 | readonly property string favoriteId: tile && tile.url || ''
17 | readonly property var app: favoriteId ? appsModel.tileGridModel.getApp(favoriteId) : null
18 | readonly property string appLabel: app ? app.display : ""
19 | readonly property string appUrl: app ? app.url : ""
20 | readonly property var appIcon: app ? app.decoration : null
21 | readonly property string labelText: tile && tile.label || appLabel || appUrl || ""
22 | readonly property var iconSource: tile && tile.icon || appIcon
23 | readonly property bool iconFill: tile && typeof tile.iconFill !== "undefined" ? tile.iconFill : false
24 | readonly property bool showIcon: tile && typeof tile.showIcon !== "undefined" ? tile.showIcon : defaultShowIcon
25 | readonly property bool showLabel: tile && typeof tile.showLabel !== "undefined" ? tile.showLabel : true
26 | readonly property color backgroundColor: tile && typeof tile.backgroundColor !== "undefined" ? tile.backgroundColor : defaultBackgroundColor
27 | readonly property string backgroundImage: tile && typeof tile.backgroundImage !== "undefined" ? tile.backgroundImage : ""
28 | readonly property bool backgroundGradient: tile && typeof tile.gradient !== "undefined" ? tile.gradient : config.defaultTileGradient
29 |
30 | readonly property int tileX: tile && typeof tile.x !== "undefined" ? tile.x : 0
31 | readonly property int tileY: tile && typeof tile.y !== "undefined" ? tile.y : 0
32 | readonly property int tileW: tile && typeof tile.w !== "undefined" ? tile.w : defaultTileW
33 | readonly property int tileH: tile && typeof tile.h !== "undefined" ? tile.h : defaultTileH
34 |
35 |
36 | // onTileChanged: console.log('onTileChanged', JSON.stringify(tile))
37 | // onAppLabelChanged: console.log('onAppLabelChanged', appLabel)
38 |
39 | function hasActionList() {
40 | return app ? appsModel.tileGridModel.indexHasActionList(app.indexInModel) : false
41 | }
42 |
43 | function getActionList() {
44 | return app ? appsModel.tileGridModel.getActionListAtIndex(app.indexInModel) : []
45 | }
46 |
47 | function addActionList(menu) {
48 | if (hasActionList()) {
49 | var actionList = getActionList()
50 | menu.addActionList(actionList, appsModel.tileGridModel, appObj.app.indexInModel)
51 | }
52 | }
53 |
54 | readonly property var groupRect: {
55 | if (isGroup) {
56 | return tileGrid.getGroupAreaRect(tile)
57 | } else {
58 | return null
59 | }
60 | }
61 | property Connections tileGridConnection: Connections {
62 | target: tileGrid
63 | function onTileModelChanged() {
64 | if (appObj.isGroup) {
65 | appObj.groupRectChanged()
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/package/contents/ui/AppToolButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.kirigami as Kirigami
3 |
4 | MouseArea {
5 | id: control
6 | hoverEnabled: true
7 |
8 | property alias hovered: control.containsMouse
9 | property string iconName: ""
10 | property var iconSource: null
11 | property string text: ""
12 |
13 | Kirigami.MnemonicData.enabled: control.enabled && control.visible
14 | Kirigami.MnemonicData.label: control.text
15 |
16 | property font font: Kirigami.Theme.defaultFont
17 | property real minimumWidth: 0
18 | property real minimumHeight: 0
19 | property bool flat: true
20 |
21 | property int paddingTop: styleLoader.item ? styleLoader.item.paddingTop : 0
22 | property int paddingLeft: styleLoader.item ? styleLoader.item.paddingLeft : 0
23 | property int paddingRight: styleLoader.item ? styleLoader.item.paddingRight : 0
24 | property int paddingBottom: styleLoader.item ? styleLoader.item.paddingBottom : 0
25 |
26 | Loader {
27 | id: styleLoader
28 | anchors.fill: parent
29 | asynchronous: true
30 | // source: "AppToolButtonStyle.qml"
31 | source: "HoverOutlineButtonStyle.qml"
32 | property var mouseArea: control
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/package/contents/ui/AppToolButtonStyle.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.ksvg as KSvg
3 |
4 | Item {
5 | id: style
6 |
7 | property int paddingTop: surfaceNormal.margins.top
8 | property int paddingLeft: surfaceNormal.margins.left
9 | property int paddingRight: surfaceNormal.margins.right
10 | property int paddingBottom: surfaceNormal.margins.bottom
11 |
12 | ButtonShadow {
13 | id: shadow
14 | visible: control.activeFocus
15 | anchors.fill: parent
16 | enabledBorders: surfaceNormal.enabledBorders
17 | state: {
18 | if (control.pressed) {
19 | return "hidden"
20 | } else if (control.containsMouse) {
21 | return "hover"
22 | } else if (control.activeFocus) {
23 | return "focus"
24 | } else {
25 | return "shadow"
26 | }
27 | }
28 | }
29 | KSvg.FrameSvgItem {
30 | id: surfaceNormal
31 | anchors.fill: parent
32 | imagePath: "widgets/button"
33 | prefix: "normal"
34 | enabledBorders: "AllBorders"
35 | }
36 | KSvg.FrameSvgItem {
37 | id: surfacePressed
38 | anchors.fill: parent
39 | imagePath: "widgets/button"
40 | prefix: "pressed"
41 | enabledBorders: surfaceNormal.enabledBorders
42 | opacity: 0
43 | }
44 |
45 | state: (control.pressed || control.checked ? "pressed" : (control.containsMouse ? "hover" : "normal"))
46 |
47 | states: [
48 | State { name: "normal"
49 | PropertyChanges {
50 | target: surfaceNormal
51 | opacity: 0
52 | }
53 | PropertyChanges {
54 | target: surfacePressed
55 | opacity: 0
56 | }
57 | },
58 | State { name: "hover"
59 | PropertyChanges {
60 | target: surfaceNormal
61 | opacity: 1
62 | }
63 | PropertyChanges {
64 | target: surfacePressed
65 | opacity: 0
66 | }
67 | },
68 | State { name: "pressed"
69 | PropertyChanges {
70 | target: surfaceNormal
71 | opacity: 0
72 | }
73 | PropertyChanges {
74 | target: surfacePressed
75 | opacity: 1
76 | }
77 | }
78 | ]
79 |
80 | transitions: [
81 | Transition {
82 | //Cross fade from pressed to normal
83 | ParallelAnimation {
84 | NumberAnimation { target: surfaceNormal; property: "opacity"; duration: 100 }
85 | NumberAnimation { target: surfacePressed; property: "opacity"; duration: 100 }
86 | }
87 | }
88 | ]
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/package/contents/ui/AppletConfig.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Window
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.core as PlasmaCore
5 |
6 | Item {
7 | function setAlpha(c, a) {
8 | var c2 = Qt.darker(c, 1)
9 | c2.a = a
10 | return c2
11 | }
12 |
13 | //--- Sizes
14 | readonly property int panelIconSize: 24 * Screen.devicePixelRatio
15 | readonly property int flatButtonSize: plasmoid.configuration.sidebarButtonSize * Screen.devicePixelRatio
16 | readonly property int flatButtonIconSize: plasmoid.configuration.sidebarIconSize * Screen.devicePixelRatio
17 | readonly property int sidebarWidth: flatButtonSize
18 | readonly property int sidebarMinOpenWidth: 200 * Screen.devicePixelRatio
19 | readonly property int sidebarRightMargin: 4 * Screen.devicePixelRatio
20 | readonly property int sidebarPopupButtonSize: plasmoid.configuration.sidebarPopupButtonSize * Screen.devicePixelRatio
21 | readonly property int appListWidth: plasmoid.configuration.appListWidth * Screen.devicePixelRatio
22 | readonly property int tileEditorMinWidth: Math.max(350, 350 * Screen.devicePixelRatio)
23 | readonly property int minimumHeight: flatButtonSize * 5 // Issue #125
24 |
25 | property bool showSearch: false
26 | property bool isEditingTile: false
27 | readonly property int appAreaWidth: {
28 | if (isEditingTile) {
29 | return tileEditorMinWidth
30 | } else if (showSearch) {
31 | return appListWidth
32 | } else {
33 | return 0
34 | }
35 | }
36 | readonly property bool hideSearchField: plasmoid.configuration.hideSearchField
37 | readonly property int leftSectionWidth: sidebarWidth + sidebarRightMargin + appAreaWidth
38 |
39 | readonly property real tileScale: plasmoid.configuration.tileScale
40 | readonly property int cellBoxUnits: 80
41 | readonly property int cellMarginUnits: plasmoid.configuration.tileMargin
42 | readonly property int cellSizeUnits: cellBoxUnits - cellMarginUnits*2
43 | readonly property int cellSize: cellSizeUnits * tileScale * Screen.devicePixelRatio
44 | readonly property real cellMargin: cellMarginUnits * tileScale * Screen.devicePixelRatio
45 | readonly property real cellPushedMargin: cellMargin * 2
46 | readonly property int cellBoxSize: cellMargin + cellSize + cellMargin
47 | readonly property int tileGridWidth: plasmoid.configuration.favGridCols * cellBoxSize
48 |
49 | readonly property int favCellWidth: 60 * Screen.devicePixelRatio
50 | readonly property int favCellPushedMargin: 5 * Screen.devicePixelRatio
51 | readonly property int favCellPadding: 3 * Screen.devicePixelRatio
52 | readonly property int favColWidth: ((favCellWidth + favCellPadding * 2) * 2) // = 132 (Medium Size)
53 | readonly property int favViewDefaultWidth: (favColWidth * 3) * Screen.devicePixelRatio
54 | readonly property int favSmallIconSize: 32 * Screen.devicePixelRatio
55 | readonly property int favMediumIconSize: 72 * Screen.devicePixelRatio
56 | readonly property int favGridWidth: (plasmoid.configuration.favGridCols/2) * favColWidth
57 |
58 | readonly property int searchFieldHeight: plasmoid.configuration.searchFieldHeight * Screen.devicePixelRatio
59 |
60 | readonly property int popupWidth: {
61 | if (plasmoid.configuration.fullscreen) {
62 | return Screen.desktopAvailableWidth
63 | } else {
64 | return leftSectionWidth + tileGridWidth
65 | }
66 | }
67 | readonly property int popupHeight: {
68 | if (plasmoid.configuration.fullscreen) {
69 | return Screen.desktopAvailableHeight
70 | } else {
71 | // implicit Math.floor() when cast as int
72 | var dPR = Screen.devicePixelRatio
73 | var pH3 = plasmoid.configuration.popupHeight
74 | var pH4 = pH3 * dPR
75 | var pH5 = Math.floor(pH4)
76 | // console.log('pH.get', 'dPR='+dPR, 'pH3='+pH3, 'pH4='+pH4, 'pH5='+pH5)
77 | return pH5
78 | }
79 | }
80 |
81 | readonly property int menuItemHeight: plasmoid.configuration.menuItemHeight * Screen.devicePixelRatio
82 |
83 | readonly property int searchFilterRowHeight: {
84 | if (plasmoid.configuration.appListWidth >= 310) {
85 | return flatButtonSize // 60px
86 | } else if (plasmoid.configuration.appListWidth >= 250) {
87 | return flatButtonSize*3/4 // 45px
88 | } else {
89 | return flatButtonSize/2 // 30px
90 | }
91 | }
92 |
93 | //--- Colors
94 | readonly property color themeButtonBgColor: {
95 | if (PlasmaCore.Theme.themeName == "oxygen") {
96 | return "#20FFFFFF"
97 | } else {
98 | return Kirigami.Theme.backgroundColor
99 | }
100 | }
101 | readonly property color defaultTileColor: plasmoid.configuration.defaultTileColor || themeButtonBgColor
102 | readonly property bool defaultTileGradient: plasmoid.configuration.defaultTileGradient
103 | readonly property color sidebarBackgroundColor: plasmoid.configuration.sidebarBackgroundColor || Kirigami.Theme.backgroundColor
104 | readonly property color menuItemTextColor2: setAlpha(Kirigami.Theme.textColor, 0.6)
105 | readonly property color favHoverOutlineColor: setAlpha(Kirigami.Theme.textColor, 0.8)
106 | readonly property color flatButtonBgHoverColor: themeButtonBgColor
107 | readonly property color flatButtonBgColor: Qt.rgba(flatButtonBgHoverColor.r, flatButtonBgHoverColor.g, flatButtonBgHoverColor.b, 0)
108 | readonly property color flatButtonBgPressedColor: Kirigami.Theme.highlightColor
109 | readonly property color flatButtonCheckedColor: Kirigami.Theme.highlightColor
110 |
111 | //--- Style
112 | // Tiles
113 | readonly property int tileLabelAlignment: {
114 | var val = plasmoid.configuration.tileLabelAlignment
115 | if (val === 'center') {
116 | return Text.AlignHCenter
117 | } else if (val === 'right') {
118 | return Text.AlignRight
119 | } else { // left
120 | return Text.AlignLeft
121 | }
122 | }
123 | readonly property int groupLabelAlignment: {
124 | var val = plasmoid.configuration.groupLabelAlignment
125 | if (val === 'center') {
126 | return Text.AlignHCenter
127 | } else if (val === 'right') {
128 | return Text.AlignRight
129 | } else { // left
130 | return Text.AlignLeft
131 | }
132 | }
133 |
134 | // App Description Enum (hidden, after, below)
135 | readonly property bool appDescriptionVisible: plasmoid.configuration.appDescription !== 'hidden'
136 | readonly property bool appDescriptionBelow: plasmoid.configuration.appDescription == 'below'
137 |
138 | //--- Settings
139 | // Search
140 | readonly property bool searchResultsMerged: plasmoid.configuration.searchResultsMerged
141 | readonly property bool searchResultsCustomSort: plasmoid.configuration.searchResultsCustomSort
142 | readonly property int searchResultsDirection: plasmoid.configuration.searchResultsReversed ? ListView.BottomToTop : ListView.TopToBottom
143 |
144 | //--- Tile Data
145 | property var tileModel: Base64JsonString {
146 | configKey: 'tileModel'
147 | defaultValue: []
148 |
149 | // defaultValue: [
150 | // {
151 | // "x": 0,
152 | // "y": 0,
153 | // "w": 2,
154 | // "h": 2,
155 | // "url": "org.kde.dolphin.desktop",
156 | // "label": "Files",
157 | // },
158 | // {
159 | // "x": 2,
160 | // "y": 1,
161 | // "w": 1,
162 | // "h": 1,
163 | // "url": "virtualbox.desktop",
164 | // "iconFill": true,
165 | // },
166 | // {
167 | // "x": 2,
168 | // "y": 0,
169 | // "w": 1,
170 | // "h": 1,
171 | // "url": "org.kde.ark.desktop",
172 | // },
173 | // ]
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/package/contents/ui/AppsView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 |
4 | QQC2.ScrollView {
5 | id: appsView
6 | property alias listView: appsListView
7 |
8 | // The horizontal ScrollBar always appears in QQC2 for some reason.
9 | // The PC3 is drawn as if it thinks the scrollWidth is 0, which is
10 | // possible since it inits at width=350px, then changes to 0px until
11 | // the popup is opened before it returns to 350px.
12 | QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
13 |
14 | KickerListView {
15 | id: appsListView
16 |
17 | section.property: 'sectionKey'
18 | // section.criteria: ViewSection.FirstCharacter
19 |
20 | model: appsModel.allAppsModel // Should be populated by the time this is created
21 |
22 | section.delegate: KickerSectionHeader {
23 | enableJumpToSection: true
24 | }
25 |
26 | delegate: MenuListItem {
27 | secondRowVisible: config.appDescriptionBelow
28 | description: config.appDescriptionVisible ? modelDescription : ''
29 | }
30 |
31 | iconSize: config.menuItemHeight
32 | showItemUrl: false
33 | }
34 |
35 | function scrollToTop() {
36 | appsListView.positionViewAtBeginning()
37 | }
38 |
39 | function jumpToSection(section) {
40 | for (var i = 0; i < appsListView.model.count; i++) {
41 | var app = appsListView.model.get(i)
42 | if (section == app.sectionKey) {
43 | appsListView.currentIndex = i
44 | appsListView.positionViewAtIndex(i, ListView.Beginning)
45 | break
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/package/contents/ui/Base64JsonString.qml:
--------------------------------------------------------------------------------
1 | // Version 4
2 |
3 | import QtQuick
4 |
5 | QtObject {
6 | property string configKey
7 | readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
8 | property variant value: { return {} }
9 | property variant defaultValue: { return {} }
10 | property bool writing: false
11 | property bool loadOnConfigChange: true
12 | signal loaded()
13 |
14 | Component.onCompleted: {
15 | load()
16 | }
17 |
18 | onConfigValueChanged: {
19 | if (loadOnConfigChange && !writing) {
20 | load()
21 | }
22 | }
23 |
24 | onDefaultValueChanged: {
25 | if (configValue === '') { // Optimization
26 | load()
27 | }
28 | }
29 |
30 | function getBase64Json(key, defaultValue) {
31 | if (configValue === '') {
32 | return defaultValue
33 | }
34 | var val = Qt.atob(configValue) // decode base64
35 | val = JSON.parse(val)
36 | return val
37 | }
38 |
39 | function setBase64Json(key, data) {
40 | var val = JSON.stringify(data)
41 | val = Qt.btoa(val)
42 | writing = true
43 | plasmoid.configuration[key] = val
44 | writing = false
45 | }
46 |
47 | function set(obj) {
48 | setBase64Json(configKey, obj)
49 | }
50 |
51 | function setItemProperty(key1, key2, val) {
52 | var item = value[key1] || {}
53 | item[key2] = val
54 | value[key1] = item
55 | set(value)
56 | valueChanged()
57 | }
58 |
59 | function getItemProperty(key1, key2, def) {
60 | var item = value[key1] || {}
61 | return typeof item[key2] !== "undefined" ? item[key2] : def
62 | }
63 |
64 | function load() {
65 | // console.log('load')
66 | // console.log('configKey', configKey)
67 | // console.log('plasmoid.configuration[key]', plasmoid.configuration[configKey])
68 | value = getBase64Json(configKey, defaultValue)
69 | loaded()
70 | }
71 |
72 | function save() {
73 | // console.log('save')
74 | // console.log('configKey', configKey)
75 | // console.log('plasmoid.configuration[key]', plasmoid.configuration[configKey])
76 | setBase64Json(configKey, value || defaultValue)
77 | }
78 |
79 | onValueChanged: {
80 | // console.log('onValueChanged', configKey, value)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/package/contents/ui/ButtonShadow.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 by Daker Fernandes Pinheiro
3 | * Copyright (C) 2011 by Marco Martin
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 2, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public
16 | * License along with this program; if not, write to the
17 | * Free Software Foundation, Inc.,
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
19 | */
20 |
21 | /**Documented API
22 | Inherits:
23 | Item
24 |
25 | Imports:
26 | QtQuick 2.1
27 | org.kde.plasma.core
28 |
29 | Description:
30 | TODO i need more info here
31 |
32 |
33 | Properties:
34 | **/
35 |
36 | import QtQuick
37 | import org.kde.kirigami as Kirigami
38 | import org.kde.ksvg as KSvg
39 |
40 | Item {
41 | id: main
42 | state: parent.state
43 | //used to tell apart this implementation with the touch components one
44 | property bool hasOverState: true
45 | property alias enabledBorders: shadow.enabledBorders
46 |
47 | KSvg.FrameSvgItem {
48 | id: hover
49 |
50 | anchors {
51 | fill: parent
52 | leftMargin: -margins.left
53 | topMargin: -margins.top
54 | rightMargin: -margins.right
55 | bottomMargin: -margins.bottom
56 | }
57 | opacity: 0
58 | imagePath: "widgets/button"
59 | prefix: "hover"
60 | }
61 |
62 | KSvg.FrameSvgItem {
63 | id: shadow
64 |
65 | anchors {
66 | fill: parent
67 | leftMargin: -margins.left
68 | topMargin: -margins.top
69 | rightMargin: -margins.right
70 | bottomMargin: -margins.bottom
71 | }
72 | imagePath: "widgets/button"
73 | prefix: "shadow"
74 | }
75 |
76 | states: [
77 | State {
78 | name: "shadow"
79 | PropertyChanges {
80 | target: shadow
81 | opacity: 1
82 | }
83 | PropertyChanges {
84 | target: hover
85 | opacity: 0
86 | prefix: "hover"
87 | }
88 | },
89 | State {
90 | name: "hover"
91 | PropertyChanges {
92 | target: shadow
93 | opacity: 0
94 | }
95 | PropertyChanges {
96 | target: hover
97 | opacity: 1
98 | prefix: "hover"
99 | }
100 | },
101 | State {
102 | name: "focus"
103 | PropertyChanges {
104 | target: shadow
105 | opacity: 0
106 | }
107 | PropertyChanges {
108 | target: hover
109 | opacity: 1
110 | prefix: "focus"
111 | }
112 | },
113 | State {
114 | name: "hidden"
115 | PropertyChanges {
116 | target: shadow
117 | opacity: 0
118 | }
119 | PropertyChanges {
120 | target: hover
121 | opacity: 0
122 | prefix: "hover"
123 | }
124 | }
125 | ]
126 |
127 | transitions: [
128 | Transition {
129 | PropertyAnimation {
130 | properties: "opacity"
131 | duration: Kirigami.Units.longDuration
132 | easing.type: Easing.OutQuad
133 | }
134 | }
135 | ]
136 | }
137 |
--------------------------------------------------------------------------------
/package/contents/ui/FlatButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 | import QtQuick.Layouts
4 | import org.kde.kirigami as Kirigami
5 | import org.kde.plasma.components as PlasmaComponents3
6 |
7 | QQC2.ToolButton {
8 | id: flatButton
9 |
10 | icon.name: ""
11 | property bool expanded: true
12 | text: ""
13 | display: expanded ? QQC2.AbstractButton.TextBesideIcon : QQC2.AbstractButton.IconOnly
14 | property string label: expanded ? text : ""
15 | property bool labelVisible: text != ""
16 | property color backgroundColor: config.flatButtonBgColor
17 | property color backgroundHoverColor: config.flatButtonBgHoverColor
18 | property color backgroundPressedColor: config.flatButtonBgPressedColor
19 | property color checkedColor: config.flatButtonCheckedColor
20 | property bool zoomOnPush: true
21 |
22 | // http://doc.qt.io/qt-5/qt.html#Edge-enum
23 | property int checkedEdge: 0 // 0 = all edges
24 | property int checkedEdgeWidth: 2 * Screen.devicePixelRatio
25 |
26 | property int buttonHeight: config.flatButtonSize
27 | property int iconSize: config.flatButtonIconSize
28 | readonly property int _iconSize: Math.min(buttonHeight, iconSize)
29 | implicitHeight: buttonHeight
30 |
31 | // contentItem: RowLayout {
32 | // id: labelRowLayout
33 | // // spacing: Kirigami.Units.smallSpacing
34 | // spacing: 0
35 | // scale: control.zoomOnPush && control.pressed ? (height-5) / height : 1
36 | // Behavior on scale { NumberAnimation { duration: 200 } }
37 |
38 | // Item {
39 | // id: iconContainer
40 | // Layout.fillHeight: true
41 | // implicitWidth: height
42 | // visible: !!icon.source
43 |
44 | // Kirigami.Icon {
45 | // id: icon
46 | // source: control.icon.name
47 | // implicitWidth: control._iconSize
48 | // implicitHeight: control._iconSize
49 | // anchors.centerIn: parent
50 | // // colorGroup: Kirigami.Theme.Button
51 | // }
52 |
53 | // // Rectangle { border.color: "#f00"; anchors.fill: parent; border.width: 1; color: "transparent"; }
54 | // }
55 |
56 | // Item {
57 | // id: spacingItem
58 | // Layout.fillHeight: true
59 | // implicitWidth: 4 * Screen.devicePixelRatio
60 | // visible: control.labelVisible
61 |
62 | // // Rectangle { border.color: "#f00"; anchors.fill: parent; border.width: 1; color: "transparent"; }
63 | // }
64 |
65 | // PlasmaComponents3.Label {
66 | // id: label
67 | // text: QtQuickControlsPrivate.StyleHelpers.stylizeMnemonics(control.text)
68 | // font: control.font || Kirigami.Theme.defaultFont
69 | // visible: control.labelVisible
70 | // horizontalAlignment: Text.AlignLeft
71 | // verticalAlignment: Text.AlignVCenter
72 | // Layout.fillWidth: true
73 |
74 | // // Rectangle { border.color: "#f00"; anchors.fill: parent; border.width: 1; color: "transparent"; }
75 | // }
76 |
77 | // Item {
78 | // id: rightPaddingItem
79 | // Layout.fillHeight: true
80 | // property int iconMargin: (iconContainer.width - icon.width)/2
81 | // property int iconPadding: icon.width * (16-12)/16
82 | // implicitWidth: iconMargin + iconPadding
83 | // visible: control.labelVisible
84 |
85 | // // Rectangle { border.color: "#f00"; anchors.fill: parent; border.width: 1; color: "transparent"; }
86 | // }
87 | // }
88 |
89 | // background: Item {
90 | // Rectangle {
91 | // id: background
92 | // anchors.fill: parent
93 | // color: flatButton.backgroundColor
94 | // }
95 |
96 | // Rectangle {
97 | // id: checkedOutline
98 | // color: flatButton.checkedColor
99 | // visible: control.checked
100 | // anchors.left: parent.left
101 | // anchors.top: parent.top
102 | // anchors.right: parent.right
103 | // anchors.bottom: parent.bottom
104 |
105 | // states: [
106 | // State {
107 | // when: control.checkedEdge === 0
108 | // PropertyChanges {
109 | // target: checkedOutline
110 | // anchors.fill: checkedOutline.parent
111 | // color: "transparent"
112 | // border.color: flatButton.checkedColor
113 | // }
114 | // },
115 | // State {
116 | // when: control.checkedEdge == Qt.TopEdge
117 | // PropertyChanges {
118 | // target: checkedOutline
119 | // anchors.bottom: undefined
120 | // height: control.checkedEdgeWidth
121 | // }
122 | // },
123 | // State {
124 | // when: control.checkedEdge == Qt.LeftEdge
125 | // PropertyChanges {
126 | // target: checkedOutline
127 | // anchors.right: undefined
128 | // width: control.checkedEdgeWidth
129 | // }
130 | // },
131 | // State {
132 | // when: control.checkedEdge == Qt.RightEdge
133 | // PropertyChanges {
134 | // target: checkedOutline
135 | // anchors.left: undefined
136 | // width: control.checkedEdgeWidth
137 | // }
138 | // },
139 | // State {
140 | // when: control.checkedEdge == Qt.BottomEdge
141 | // PropertyChanges {
142 | // target: checkedOutline
143 | // anchors.top: undefined
144 | // height: control.checkedEdgeWidth
145 | // }
146 | // }
147 | // ]
148 | // }
149 |
150 | // states: [
151 | // State {
152 | // name: "hovering"
153 | // when: !control.pressed && control.hovered
154 | // PropertyChanges {
155 | // target: background
156 | // color: flatButton.backgroundHoverColor
157 | // }
158 | // },
159 | // State {
160 | // name: "pressed"
161 | // when: control.pressed
162 | // PropertyChanges {
163 | // target: background
164 | // color: flatButton.backgroundPressedColor
165 | // }
166 | // }
167 | // ]
168 |
169 | // transitions: [
170 | // Transition {
171 | // to: "hovering"
172 | // ColorAnimation { duration: 200 }
173 | // },
174 | // Transition {
175 | // to: "pressed"
176 | // ColorAnimation { duration: 100 }
177 | // }
178 | // ]
179 | // }
180 | }
181 |
--------------------------------------------------------------------------------
/package/contents/ui/Hierarchy.md:
--------------------------------------------------------------------------------
1 | Main
2 | SearchModel
3 | SearchResultsModel
4 | LauncherIcon
5 | Popup
6 | SearchView
7 | SidebarView
8 | SearchResultsView
9 | SearchResultsList
10 | SearchField
11 | TileGrid
12 |
13 |
14 | krunner_id (filename)
15 |
16 | org.kde.activities (activityrunner)
17 | Audio Player Control Runner (audioplayercontrol)
18 | baloosearch (baloosearch)
19 | bookmarks (bookmarks)
20 | calculator (calculator)
21 | unitconverter (converter)
22 | org.kde.datetime (datetime)
23 | Dictionary (dictionary)
24 | Kill Runner (kill)
25 | kwin (kwin)
26 | locations (locations)
27 | places (places)
28 | Name=plasma-desktop (plasma)
29 | PowerDevil (powerdevil)
30 | services (services)
31 | desktopsessions (sessions)
32 | shell (shell)
33 | Spell Checker (spellchecker)
34 | webshortcuts (webshortcuts)
35 | org.kde.windowedwidgets (windowedwidgets)
36 | windows (windows)
37 |
--------------------------------------------------------------------------------
/package/contents/ui/HoverOutlineButtonEffect.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | HoverOutlineEffect {
4 | id: hoverOutlineButtonEffect
5 | anchors.fill: parent
6 | hoverRadius: Math.max(width/2, height)
7 | pressedRadius: width
8 | mouseArea: __mouseArea
9 | }
10 |
--------------------------------------------------------------------------------
/package/contents/ui/HoverOutlineButtonStyle.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | Item {
4 | id: style
5 | property int paddingTop: 0
6 | property int paddingRight: 0
7 | property int paddingBottom: 0
8 | property int paddingLeft: 0
9 |
10 | Loader {
11 | id: hoverOutlineEffectLoader
12 | anchors.fill: parent
13 | active: mouseArea.containsMouse
14 | visible: active
15 | source: "HoverOutlineButtonEffect.qml"
16 |
17 | property var __mouseArea: mouseArea
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/package/contents/ui/HoverOutlineEffect.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.kirigami as Kirigami
3 |
4 |
5 | // https://doc.qt.io/qt-5/graphicaleffects.html
6 | // https://doc.qt.io/qt-6/qtgraphicaleffects5-index.html
7 | // import QtGraphicalEffects 1.0 // TODO Deprecated in Qt6
8 | import Qt5Compat.GraphicalEffects as QtGraphicalEffects
9 |
10 | Item {
11 | id: hoverOutlineEffect
12 | property int hoverOutlineSize: 1 * Screen.devicePixelRatio
13 | property int hoverRadius: 40
14 | property int pressedRadius: hoverRadius
15 | property bool useOutlineMask: true
16 |
17 | property var mouseArea
18 | property bool hovered: mouseArea ? mouseArea.containsMouse : false
19 | property bool pressed: mouseArea ? mouseArea.pressed : false
20 | property int mouseX: mouseArea ? mouseArea.mouseX : width/2
21 | property int mouseY: mouseArea ? mouseArea.mouseY : height/2
22 |
23 | property int effectRadius: hoverOutlineEffect.pressed ? pressedRadius : hoverRadius
24 | Behavior on effectRadius {
25 | NumberAnimation {
26 | duration: Kirigami.Units.longDuration
27 | }
28 | }
29 |
30 | visible: hoverOutlineEffect.hovered
31 |
32 | function alpha(c, a) {
33 | return Qt.rgba(c.r, c.g, c.b, a)
34 | }
35 | property color effectColor: Kirigami.Theme.textColor
36 | property color fillColor: alpha(effectColor, 1/16)
37 | property color pressedFillColor: alpha(effectColor, 4/16)
38 | property color borderColor: alpha(effectColor, 8/16)
39 |
40 | Rectangle {
41 | id: hoverSolidFill
42 | anchors.fill: parent
43 | anchors.margins: hoverOutlineSize
44 | color: fillColor
45 | }
46 |
47 | Rectangle {
48 | id: hoverOutline
49 | visible: !hoverOutlineEffect.useOutlineMask
50 | anchors.fill: parent
51 | // color: "transparent"
52 | color: hoverOutlineEffect.pressed ? pressedFillColor : fillColor
53 | border.color: borderColor
54 | border.width: hoverOutlineSize
55 |
56 | Behavior on color {
57 | ColorAnimation {
58 | duration: Kirigami.Units.longDuration
59 | }
60 | }
61 | }
62 |
63 | QtGraphicalEffects.RadialGradient {
64 | id: hoverOutlineMask
65 | visible: false
66 | anchors.fill: parent
67 | horizontalOffset: hoverOutlineEffect.visible ? hoverOutlineEffect.mouseX - width/2 : 0
68 | verticalOffset: hoverOutlineEffect.visible ? hoverOutlineEffect.mouseY - height/2 : 0
69 | horizontalRadius: effectRadius
70 | verticalRadius: effectRadius
71 | gradient: Gradient {
72 | GradientStop { position: 0.0; color: "#FFFFFFFF" }
73 | GradientStop { position: 1; color: "#00FFFFFF" }
74 | }
75 | }
76 |
77 | QtGraphicalEffects.OpacityMask {
78 | anchors.fill: parent
79 | visible: hoverOutlineEffect.useOutlineMask
80 | source: hoverOutline
81 | maskSource: hoverOutlineMask
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/package/contents/ui/JumpToLetterView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | // import org.kde.plasma.components as PlasmaComponents3
3 |
4 | JumpToSectionView {
5 | id: jumpToLetterView
6 |
7 | squareView: appsModel.order == "alphabetical"
8 |
9 | onUpdate: {
10 | // console.log('jumpToLetterView.update()')
11 | var sections = []
12 | for (var i = 0; i < appsModel.allAppsModel.count; i++) {
13 | var app = appsModel.allAppsModel.get(i)
14 | var section = app.sectionKey
15 | if (sections.indexOf(section) == -1) {
16 | sections.push(section)
17 | }
18 | }
19 | availableSections = sections
20 | // console.log('jumpToLetterView.update.availableSections', sections)
21 |
22 | if (appsModel.order == "alphabetical") {
23 | sections = presetSections.slice() // shallow copy
24 | for (var i = 0; i < availableSections.length; i++) {
25 | var section = availableSections[i]
26 | if (sections.indexOf(section) == -1) {
27 | sections.push(section)
28 | }
29 | }
30 | allSections = sections
31 | } else {
32 | allSections = availableSections
33 | }
34 | // console.log('jumpToLetterView.update.allSections', allSections)
35 | }
36 |
37 | presetSections: [
38 | appsModel.recentAppsSectionKey,
39 | '&',
40 | '0-9',
41 | 'A', 'B', 'C', 'D', 'E', 'F',
42 | 'G', 'H', 'I', 'J', 'K', 'L',
43 | 'M', 'N', 'O', 'P', 'Q', 'R',
44 | 'S', 'T', 'U', 'V', 'W', 'X',
45 | 'Y', 'Z',
46 | ]
47 |
48 | // delegate: PlasmaComponents3.ToolButton {
49 | // width: jumpToLetterView.cellWidth
50 | // height: jumpToLetterView.cellHeight
51 |
52 | // readonly property string section: modelData || ''
53 | // readonly property bool isRecentApps: section == i18n("Recent Apps")
54 |
55 | // enabled: availableSections.indexOf(section) >= 0
56 |
57 | // font.pixelSize: height * 0.6
58 |
59 | // icon.name: {
60 | // if (jumpToLetterView.squareView) {
61 | // if (isRecentApps) {
62 | // return 'view-history'
63 | // } else {
64 | // return ''
65 | // }
66 | // } else {
67 | // return 'view-list-tree'
68 | // }
69 | // }
70 | // text: {
71 | // if (isRecentApps) {
72 | // return '' // Use '◷' icon
73 | // } else if (section == '0-9') {
74 | // return '#'
75 | // } else {
76 | // return section
77 | // }
78 | // }
79 |
80 | // onClicked: {
81 | // appsView.show()
82 | // appsView.jumpToSection(section)
83 | // }
84 | // }
85 | }
86 |
--------------------------------------------------------------------------------
/package/contents/ui/JumpToSectionButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.components as PlasmaComponents3
5 |
6 | AppToolButton {
7 | id: control
8 |
9 | RowLayout {
10 | id: buttonContent
11 | anchors.fill: parent
12 | anchors.topMargin: control.paddingTop
13 | anchors.leftMargin: control.paddingLeft
14 | anchors.rightMargin: control.paddingRight
15 | anchors.bottomMargin: control.paddingBottom
16 |
17 | opacity: control.enabled ? 1 : 0.5
18 | spacing: Kirigami.Units.smallSpacing
19 |
20 | Layout.preferredHeight: Math.max(Kirigami.Units.iconSizes.small, label.implicitHeight)
21 |
22 | Kirigami.Icon {
23 | id: icon
24 | source: control.iconName || control.iconSource
25 |
26 | implicitHeight: label.implicitHeight
27 | implicitWidth: implicitHeight
28 |
29 | Layout.minimumWidth: valid ? parent.height: 0
30 | Layout.maximumWidth: Layout.minimumWidth
31 | visible: valid
32 | Layout.minimumHeight: Layout.minimumWidth
33 | Layout.maximumHeight: Layout.minimumWidth
34 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
35 | active: control.containsMouse
36 | }
37 |
38 | PlasmaComponents3.Label {
39 | id: label
40 | Layout.minimumWidth: implicitWidth
41 | text: control.Kirigami.MnemonicData.richTextLabel
42 | font: control.font || Kirigami.Theme.defaultFont
43 | visible: control.text != ""
44 | Layout.fillWidth: true
45 | height: parent.height
46 | color: control.containsMouse ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
47 | horizontalAlignment: icon.valid ? Text.AlignLeft : Text.AlignHCenter
48 | verticalAlignment: Text.AlignVCenter
49 | elide: Text.ElideRight
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/package/contents/ui/JumpToSectionView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.core as PlasmaCore
4 |
5 | GridView {
6 | id: jumpToSectionView
7 |
8 | Layout.fillWidth: true
9 | Layout.fillHeight: true
10 |
11 | clip: true
12 |
13 | property bool squareView: false
14 |
15 | Connections {
16 | target: appsModel.allAppsModel
17 | function onRefreshed() { jumpToLetterView.update() }
18 | }
19 |
20 | signal update()
21 |
22 | property var availableSections: []
23 | property var presetSections: []
24 | property var allSections: []
25 | model: allSections
26 |
27 | property int buttonSize: {
28 | if (squareView) {
29 | return 70 * Screen.devicePixelRatio
30 | } else {
31 | return 36 * Screen.devicePixelRatio
32 | }
33 | }
34 |
35 | cellWidth: {
36 | if (squareView) {
37 | return buttonSize
38 | } else {
39 | return width
40 | }
41 | }
42 | cellHeight: buttonSize
43 |
44 | delegate: JumpToSectionButton {
45 | width: jumpToLetterView.cellWidth
46 | height: jumpToLetterView.cellHeight
47 |
48 | readonly property string section: modelData || ''
49 | readonly property bool isRecentApps: section == appsModel.recentAppsSectionKey
50 | readonly property var sectionIcon: appsModel.allAppsModel.sectionIcons[section] || null
51 |
52 | enabled: availableSections.indexOf(section) >= 0
53 |
54 | font.pixelSize: height * 0.6
55 |
56 | iconSource: {
57 | if (isRecentApps) {
58 | return 'view-history'
59 | } else if (jumpToLetterView.squareView) {
60 | return ''
61 | } else {
62 | return sectionIcon
63 | }
64 | }
65 | text: {
66 | if (isRecentApps) {
67 | if (jumpToLetterView.squareView) {
68 | return '' // Use '◷' icon
69 | } else {
70 | return appsModel.recentAppsSectionLabel
71 | }
72 | } else if (jumpToLetterView.squareView && section == '0-9') {
73 | return '#'
74 | } else {
75 | return section
76 | }
77 | }
78 |
79 | onClicked: {
80 | appsView.show() // appsView.show(stackView.zoomIn)
81 | appsView.jumpToSection(section)
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/package/contents/ui/KickerAppModel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.private.kicker as Kicker
3 |
4 | Kicker.FavoritesModel {
5 | // Kicker.FavoritesModel must be a child object of RootModel.
6 | // appEntry.actions() looks at the parent object for parent.appletInterface and will crash plasma if it can't find it.
7 | // https://github.com/KDE/plasma-desktop/blob/master/applets/kicker/plugin/appentry.cpp#L151
8 | id: kickerAppModel
9 |
10 | signal triggerIndex(int index)
11 | onTriggerIndex: {
12 | var closeRequested = kickerAppModel.trigger(index, "", null)
13 | if (closeRequested) {
14 | plasmoid.expanded = false
15 | }
16 | }
17 |
18 | signal triggerIndexAction(int index, string actionId, string actionArgument)
19 | onTriggerIndexAction: {
20 | var closeRequested = kickerAppModel.trigger(index, actionId, actionArgument)
21 | if (closeRequested) {
22 | plasmoid.expanded = false
23 | }
24 | }
25 |
26 | // https://invent.kde.org/plasma/plasma-workspace/-/blame/master/applets/kicker/plugin/actionlist.h#L18
27 | // DescriptionRole Qt.UserRole + 1
28 | // GroupRole Qt.UserRole + 2
29 | // FavoriteIdRole Qt.UserRole + 3
30 | // IsSeparatorRole Qt.UserRole + 4
31 | // IsDropPlaceholderRole Qt.UserRole + 5
32 | // IsParentRole Qt.UserRole + 6
33 | // HasChildrenRole Qt.UserRole + 7
34 | // HasActionListRole Qt.UserRole + 8
35 | // ActionListRole Qt.UserRole + 9
36 | // UrlRole Qt.UserRole + 10
37 | // DisabledRole Qt.UserRole + 11 @since: Plasma 5.20
38 | // IsMultilineTextRole Qt.UserRole + 12 @since: Plasma 5.24
39 | // DisplayWrappedRole Qt.UserRole + 13 @since: Plasma 6.0
40 | function getApp(url) {
41 | for (var i = 0; i < count; i++) {
42 | var modelIndex = kickerAppModel.index(i, 0)
43 | var favoriteId = kickerAppModel.data(modelIndex, Qt.UserRole + 3)
44 | if (favoriteId == url) {
45 | var app = {}
46 | app.indexInModel = i
47 | app.favoriteId = favoriteId
48 | app.display = kickerAppModel.data(modelIndex, Qt.DisplayRole)
49 | app.decoration = kickerAppModel.data(modelIndex, Qt.DecorationRole)
50 | app.description = kickerAppModel.data(modelIndex, Qt.UserRole + 1)
51 | app.group = kickerAppModel.data(modelIndex, Qt.UserRole + 2)
52 | app.url = kickerAppModel.data(modelIndex, Qt.UserRole + 10)
53 |
54 | // console.log(app, app.display, app.decoration, app.description, app.group, app.favoriteId)
55 |
56 | return app
57 | }
58 | }
59 | console.log('getApp', url, 'no index')
60 | return null
61 | }
62 | function runApp(url) {
63 | for (var i = 0; i < count; i++) {
64 | var modelIndex = kickerAppModel.index(i, 0)
65 | var favoriteId = kickerAppModel.data(modelIndex, Qt.UserRole + 3)
66 | if (favoriteId == url) {
67 | kickerAppModel.triggerIndex(i)
68 | return
69 | }
70 | }
71 | console.log('runApp', url, 'no index')
72 | }
73 |
74 | function indexHasActionList(i) {
75 | var modelIndex = kickerAppModel.index(i, 0)
76 | var hasActionList = kickerAppModel.data(modelIndex, Qt.UserRole + 8)
77 | return hasActionList
78 | }
79 |
80 | function getActionListAtIndex(i) {
81 | var modelIndex = kickerAppModel.index(i, 0)
82 | var actionList = kickerAppModel.data(modelIndex, Qt.UserRole + 9)
83 | return actionList
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/package/contents/ui/KickerListModel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | ListModel {
4 | id: listModel
5 |
6 | property var list: []
7 | property var sectionIcons: { return {} }
8 |
9 | signal refreshing()
10 | signal refreshed()
11 |
12 | onListChanged: {
13 | clear()
14 | for (var i = 0; i < list.length; i++) {
15 | append(list[i])
16 | }
17 | }
18 |
19 |
20 | function parseAppsModelItem(model, i) {
21 | // https://github.com/KDE/plasma-desktop/blob/master/applets/kicker/plugin/actionlist.h#L30
22 | var DescriptionRole = Qt.UserRole + 1
23 | var GroupRole = Qt.UserRole + 2
24 | var FavoriteIdRole = Qt.UserRole + 3
25 | var IsSeparatorRole = Qt.UserRole + 4
26 | var IsDropPlaceholderRole = Qt.UserRole + 5
27 | var IsParentRole = Qt.UserRole + 6
28 | var HasChildrenRole = Qt.UserRole + 7
29 | var HasActionListRole = Qt.UserRole + 8
30 | var ActionListRole = Qt.UserRole + 9
31 | var UrlRole = Qt.UserRole + 10
32 | var DisabledRole = Qt.UserRole + 11 // @since: Plasma 5.20
33 | var IsMultilineTextRole = Qt.UserRole + 12 // @since: Plasma 5.24
34 | var DisplayWrappedRole = Qt.UserRole + 13 // @since: Plasma 6.0
35 |
36 | var modelIndex = model.index(i, 0)
37 |
38 | var item = {
39 | parentModel: model,
40 | indexInParent: i,
41 | name: model.data(modelIndex, Qt.DisplayRole),
42 | description: model.data(modelIndex, DescriptionRole),
43 | favoriteId: model.data(modelIndex, FavoriteIdRole),
44 | disabled: false, // for SidebarContextMenu
45 | largeIcon: false, // for KickerListView
46 | }
47 |
48 | if (typeof model.name === 'string') {
49 | item.parentName = model.name
50 | }
51 |
52 | // ListView.append() doesn't like it when we have { key: [object] }.
53 | var url = model.data(modelIndex, UrlRole)
54 | if (typeof url === 'object') {
55 | url = url.toString()
56 | }
57 | if (typeof url === 'string') {
58 | item.url = url
59 | }
60 |
61 | var icon = model.data(modelIndex, Qt.DecorationRole)
62 | if (typeof icon === 'object') {
63 | item.icon = icon
64 | } else if (typeof icon === 'string') {
65 | item.iconName = icon
66 | }
67 |
68 | var isDisabled = model.data(modelIndex, DisabledRole)
69 | if (typeof isDisabled !== 'undefined') {
70 | item.disabled = isDisabled
71 | }
72 |
73 | return item
74 | }
75 |
76 | function parseModel(appList, model, path) {
77 | // console.log(path, model, model.description, model.count)
78 | for (var i = 0; i < model.count; i++) {
79 | var item = model.modelForRow(i)
80 | if (!item) {
81 | item = parseAppsModelItem(model, i)
82 | }
83 | var itemPath = (path || []).concat(i)
84 | if (item && item.hasChildren) {
85 | // console.log(item)
86 | parseModel(appList, item, itemPath)
87 | } else {
88 | // console.log(itemPath, item, item.description)
89 | appList.push(item)
90 | }
91 | }
92 | }
93 |
94 |
95 | function refresh() {
96 | refreshing()
97 |
98 | refreshed()
99 | }
100 |
101 | function log() {
102 | for (var i = 0; i < list.length; i++) {
103 | var item = list[i]
104 | console.log(JSON.stringify({
105 | name: item.name,
106 | description: item.description,
107 | }, null, '\t'))
108 | }
109 | }
110 |
111 | function triggerIndex(index) {
112 | var item = list[index]
113 | item.parentModel.trigger(item.indexInParent, "", null)
114 | itemTriggered()
115 | }
116 |
117 | signal itemTriggered()
118 |
119 | function hasActionList(index) {
120 | var DescriptionRole = Qt.UserRole + 1
121 | var HasActionListRole = Qt.UserRole + 8
122 |
123 | var item = list[index]
124 | var modelIndex = item.parentModel.index(item.indexInParent, 0)
125 | return item.parentModel.data(modelIndex, HasActionListRole)
126 | }
127 |
128 | function getActionList(index) {
129 | var DescriptionRole = Qt.UserRole + 1
130 | var ActionListRole = Qt.UserRole + 9
131 |
132 | var item = list[index]
133 | var modelIndex = item.parentModel.index(item.indexInParent, 0)
134 | return item.parentModel.data(modelIndex, ActionListRole)
135 | }
136 |
137 | function triggerIndexAction(index, actionId, actionArgument) {
138 | // kicker/code/tools.js triggerAction()
139 |
140 | var item = list[index]
141 | item.parentModel.trigger(item.indexInParent, actionId, actionArgument)
142 | itemTriggered()
143 | }
144 |
145 | function getByValue(key, value) {
146 | for (var i = 0; i < count; i++) {
147 | var item = get(i)
148 | if (item[key] == value) {
149 | return item
150 | }
151 | }
152 | return null
153 | }
154 |
155 | function hasApp(favoriteId) {
156 | for (var i = 0; i < count; i++) {
157 | var item = get(i)
158 | if (item.favoriteId == favoriteId) {
159 | return true
160 | }
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/package/contents/ui/KickerListView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.extras as PlasmaExtras
3 |
4 | ListView {
5 | id: listView
6 | clip: true
7 | cacheBuffer: 200 // Don't unload when scrolling (prevent stutter)
8 |
9 | // snapMode: ListView.SnapToItem
10 | keyNavigationWraps: true
11 | highlightMoveDuration: 0
12 | highlightResizeDuration: 0
13 |
14 | property bool showItemUrl: true
15 | property bool showDesktopFileUrl: false
16 | property int iconSize: 36 * Screen.devicePixelRatio
17 |
18 | section.delegate: KickerSectionHeader {}
19 |
20 | delegate: MenuListItem {}
21 |
22 | property var modelList: model ? model.list : []
23 |
24 | // currentIndex: 0
25 | // Connections {
26 | // target: appsModel.allAppsModel
27 | // function onRefreshing() {
28 | // console.log('appsList.onRefreshing')
29 | // appsList.model = []
30 | // // console.log('search.results.onRefreshed')
31 | // appsList.currentIndex = 0
32 | // }
33 | // function onRefreshed() {
34 | // console.log('appsList.onRefreshed')
35 | // // appsList.model = appsModel.allAppsList
36 | // appsList.model = appsModel.allAppsList
37 | // appsList.modelList = appsModel.allAppsList.list
38 | // appsList.currentIndex = 0
39 | // }
40 | // }
41 |
42 | highlight: PlasmaExtras.Highlight {
43 | visible: listView.currentItem && !listView.currentItem.isSeparator
44 | }
45 |
46 | // function triggerIndex(index) {
47 | // model.triggerIndex(index)
48 | // }
49 |
50 | function goUp() {
51 | if (verticalLayoutDirection == ListView.TopToBottom) {
52 | decrementCurrentIndex()
53 | } else { // ListView.BottomToTop
54 | incrementCurrentIndex()
55 | }
56 | }
57 |
58 | function goDown() {
59 | if (verticalLayoutDirection == ListView.TopToBottom) {
60 | incrementCurrentIndex()
61 | } else { // ListView.BottomToTop
62 | decrementCurrentIndex()
63 | }
64 | }
65 |
66 | function skipToMin() {
67 | currentIndex = Math.max(0, currentIndex - 10)
68 | }
69 |
70 | function skipToMax() {
71 | currentIndex = Math.min(currentIndex + 10, count-1)
72 | }
73 |
74 | function pageUp() {
75 | if (verticalLayoutDirection == ListView.TopToBottom) {
76 | skipToMin()
77 | } else { // ListView.BottomToTop
78 | skipToMax()
79 | }
80 | }
81 |
82 | function pageDown() {
83 | if (verticalLayoutDirection == ListView.TopToBottom) {
84 | skipToMax()
85 | } else { // ListView.BottomToTop
86 | skipToMin()
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/package/contents/ui/KickerSectionHeader.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.kirigami as Kirigami
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | MouseArea {
6 | id: sectionDelegate
7 |
8 | width: ListView.view.width
9 | // height: childrenRect.height
10 | implicitHeight: listView.iconSize
11 |
12 | property bool enableJumpToSection: false
13 |
14 | PlasmaComponents3.Label {
15 | id: sectionHeading
16 | anchors {
17 | left: parent.left
18 | leftMargin: Kirigami.Units.smallSpacing
19 | verticalCenter: parent.verticalCenter
20 | }
21 | text: {
22 | if (section == appsModel.recentAppsSectionKey) {
23 | return appsModel.recentAppsSectionLabel
24 | } else {
25 | return section
26 | }
27 | }
28 |
29 | // Add 4pt to font. Default 10pt => 14pt
30 | font.pointSize: Kirigami.Theme.defaultFont.pointSize + 4
31 |
32 | property bool centerOverIcon: sectionHeading.contentWidth <= listView.iconSize
33 | width: centerOverIcon ? listView.iconSize : parent.width
34 | horizontalAlignment: centerOverIcon ? Text.AlignHCenter : Text.AlignLeft
35 | }
36 |
37 | HoverOutlineEffect {
38 | id: hoverOutlineEffect
39 | anchors.fill: parent
40 | visible: enableJumpToSection && mouseArea.containsMouse
41 | hoverRadius: width/2
42 | pressedRadius: width
43 | mouseArea: sectionDelegate
44 | }
45 |
46 | hoverEnabled: true
47 | onClicked: {
48 | if (enableJumpToSection) {
49 | if (appsModel.order == "alphabetical") {
50 | jumpToLetterView.show()
51 | } else { // appsModel.order = "categories"
52 | jumpToLetterView.show()
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/package/contents/ui/LauncherIcon.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.core as PlasmaCore
4 | import org.kde.draganddrop as DragAndDrop
5 | import org.kde.kirigami as Kirigami
6 | import org.kde.plasma.plasmoid
7 |
8 | MouseArea {
9 | id: launcherIcon
10 |
11 | readonly property bool inPanel: (plasmoid.location == PlasmaCore.Types.TopEdge
12 | || plasmoid.location == PlasmaCore.Types.RightEdge
13 | || plasmoid.location == PlasmaCore.Types.BottomEdge
14 | || plasmoid.location == PlasmaCore.Types.LeftEdge)
15 |
16 | Layout.minimumWidth: {
17 | switch (plasmoid.formFactor) {
18 | case PlasmaCore.Types.Vertical:
19 | return 0
20 | case PlasmaCore.Types.Horizontal:
21 | return height
22 | default:
23 | return Kirigami.Units.gridUnit * 3
24 | }
25 | }
26 |
27 | Layout.minimumHeight: {
28 | switch (plasmoid.formFactor) {
29 | case PlasmaCore.Types.Vertical:
30 | return width
31 | case PlasmaCore.Types.Horizontal:
32 | return 0
33 | default:
34 | return Kirigami.Units.gridUnit * 3
35 | }
36 | }
37 |
38 | readonly property int maxSize: Math.max(width, height)
39 | property int size: {
40 | if (inPanel) {
41 | if (plasmoid.configuration.fixedPanelIcon) {
42 | // Was PlasmaCore.Units.iconSizeHints.panel in Plasma5
43 | // In Plasma6 https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/1390/diffs
44 | return 48 // Kickoff uses this hardcoded number
45 | } else {
46 | return maxSize
47 | }
48 | } else {
49 | return -1
50 | }
51 | }
52 | Layout.maximumWidth: size
53 | Layout.maximumHeight: size
54 |
55 |
56 | property int iconSize: Math.min(width, height)
57 | property alias iconSource: icon.source
58 |
59 | Kirigami.Icon {
60 | id: icon
61 | anchors.centerIn: parent
62 | source: "start-here-kde-symbolic"
63 | width: launcherIcon.iconSize
64 | height: launcherIcon.iconSize
65 | active: launcherIcon.containsMouse && !justOpenedTimer.running
66 | smooth: true
67 | }
68 |
69 | // Debugging
70 | // Rectangle { anchors.fill: parent; border.color: "#ff0"; color: "transparent"; border.width: 1; }
71 | // Rectangle { anchors.fill: icon; border.color: "#f00"; color: "transparent"; border.width: 1; }
72 |
73 | Accessible.name: Plasmoid.title
74 | Accessible.role: Accessible.Button
75 |
76 | hoverEnabled: true
77 | // cursorShape: Qt.PointingHandCursor
78 |
79 | property bool wasExpanded
80 | onPressed: wasExpanded = widget.expanded
81 | onClicked: widget.expanded = !wasExpanded
82 |
83 | property alias activateOnDrag: dropArea.enabled
84 | DragAndDrop.DropArea {
85 | id: dropArea
86 | anchors.fill: parent
87 | }
88 |
89 | onContainsMouseChanged: {
90 | if (!containsMouse) {
91 | dragHoverTimer.stop()
92 | }
93 | }
94 |
95 | Timer {
96 | id: dragHoverTimer
97 | interval: 250 // Same as taskmanager's activationTimer in MouseHandler.qml
98 | running: dropArea.containsDrag
99 | onTriggered: widget.expanded = true
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/package/contents/ui/MenuListItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.components as PlasmaComponents3
5 | import org.kde.draganddrop as DragAndDrop
6 |
7 | AppToolButton {
8 | id: itemDelegate
9 |
10 | width: ListView.view.width
11 | implicitHeight: row.implicitHeight
12 |
13 | property var parentModel: typeof modelList !== "undefined" && modelList[index] ? modelList[index].parentModel : undefined
14 | property string modelDescription: model.name == model.description ? '' : model.description // Ignore the Comment if it's the same as the Name.
15 | property string description: model.url ? modelDescription : '' //
16 | property bool isDesktopFile: !!(model.url && endsWith(model.url, '.desktop'))
17 | property bool showItemUrl: listView.showItemUrl && (!isDesktopFile || listView.showDesktopFileUrl)
18 | property string secondRowText: showItemUrl && model.url ? model.url : modelDescription
19 | property bool secondRowVisible: secondRowText
20 | property string launcherUrl: model.favoriteId || model.url
21 | property string iconName: model.iconName || ''
22 | property alias iconSource: itemIcon.source
23 | property int iconSize: model.largeIcon ? listView.iconSize * 2 : listView.iconSize
24 |
25 | function endsWith(s, substr) {
26 | return s.indexOf(substr) == s.length - substr.length
27 | }
28 |
29 | // We need to look at the js list since ListModel doesn't support item's with non primitive propeties (like an Image).
30 | property bool modelListPopulated: !!listView.model.list && listView.model.list.length - 1 >= index
31 | property var iconInstance: modelListPopulated && listView.model.list[index] ? listView.model.list[index].icon : ""
32 | Connections {
33 | target: listView.model
34 | function onRefreshed() {
35 | // We need to manually trigger an update when we update the model without replacing the list.
36 | // Otherwise the icon won't be in sync.
37 | itemDelegate.iconInstance = listView.model.list[index] ? listView.model.list[index].icon : ""
38 | }
39 | }
40 |
41 | // Drag (based on kicker)
42 | // https://github.com/KDE/plasma-desktop/blob/4aad3fdf16bc5fd25035d3d59bb6968e06f86ec6/applets/kicker/package/contents/ui/ItemListDelegate.qml#L96
43 | // https://github.com/KDE/plasma-desktop/blob/master/applets/kicker/plugin/draghelper.cpp
44 | property int pressX: -1
45 | property int pressY: -1
46 | property bool dragEnabled: launcherUrl
47 | function initDrag(mouse) {
48 | pressX = mouse.x
49 | pressY = mouse.y
50 | }
51 | function shouldStartDrag(mouse) {
52 | return dragEnabled
53 | && pressX != -1 // Drag initialized?
54 | && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y) // Mouse moved far enough?
55 | }
56 | function startDrag() {
57 | // Note that we fallback from url to favoriteId for "Most Used" apps.
58 | var dragIcon = iconInstance
59 | if (typeof dragIcon === "string") {
60 | // startDrag must use QIcon. See Issue #75.
61 | // dragIcon = dragHelper.defaultIcon
62 | dragIcon = null
63 | }
64 | // console.log('startDrag', widget, model.url, "favoriteId", model.favoriteId)
65 | // console.log(' iconInstance', iconInstance)
66 | // console.log(' dragIcon', dragIcon)
67 | if (dragIcon) {
68 | dragHelper.startDrag(widget, model.url || model.favoriteId, dragIcon, "favoriteId", model.favoriteId)
69 | }
70 |
71 | resetDragState()
72 | }
73 | function resetDragState() {
74 | pressX = -1
75 | pressY = -1
76 | }
77 | onPressed: function(mouse) {
78 | if (mouse.buttons & Qt.LeftButton) {
79 | initDrag(mouse)
80 | }
81 | }
82 | onContainsMouseChanged: function(containsMouse) {
83 | if (!containsMouse) {
84 | resetDragState()
85 | }
86 | }
87 | onPositionChanged: function(mouse) {
88 | if (shouldStartDrag(mouse)) {
89 | startDrag()
90 | }
91 | }
92 |
93 | RowLayout { // ItemListDelegate
94 | id: row
95 | anchors.left: parent.left
96 | anchors.leftMargin: Kirigami.Units.smallSpacing
97 | anchors.right: parent.right
98 | anchors.rightMargin: Kirigami.Units.smallSpacing
99 |
100 | Item {
101 | Layout.fillHeight: true
102 | implicitHeight: itemIcon.implicitHeight
103 | implicitWidth: itemIcon.implicitWidth
104 |
105 | Kirigami.Icon {
106 | id: itemIcon
107 | anchors.centerIn: parent
108 | implicitHeight: itemDelegate.iconSize
109 | implicitWidth: implicitHeight
110 |
111 | // visible: iconsEnabled
112 |
113 | animated: false
114 | // usesPlasmaTheme: false
115 | source: itemDelegate.iconName || itemDelegate.iconInstance
116 | }
117 | }
118 |
119 | ColumnLayout {
120 | Layout.fillWidth: true
121 | // Layout.fillHeight: true
122 | Layout.alignment: Qt.AlignVCenter
123 | spacing: 0
124 |
125 | RowLayout {
126 | Layout.fillWidth: true
127 | // height: itemLabel.height
128 |
129 | PlasmaComponents3.Label {
130 | id: itemLabel
131 | text: model.name
132 | maximumLineCount: 1
133 | // elide: Text.ElideMiddle
134 | height: implicitHeight
135 | }
136 |
137 | PlasmaComponents3.Label {
138 | Layout.fillWidth: true
139 | text: !itemDelegate.secondRowVisible ? itemDelegate.description : ''
140 | color: config.menuItemTextColor2
141 | maximumLineCount: 1
142 | elide: Text.ElideRight
143 | height: implicitHeight // ElideRight causes some top padding for some reason
144 | }
145 | }
146 |
147 | PlasmaComponents3.Label {
148 | visible: itemDelegate.secondRowVisible
149 | Layout.fillWidth: true
150 | // Layout.fillHeight: true
151 | text: itemDelegate.secondRowText
152 | color: config.menuItemTextColor2
153 | maximumLineCount: 1
154 | elide: Text.ElideMiddle
155 | height: implicitHeight
156 | }
157 | }
158 |
159 | }
160 |
161 | acceptedButtons: Qt.LeftButton | Qt.RightButton
162 | onClicked: function(mouse) {
163 | mouse.accepted = true
164 | resetDragState()
165 | logger.debug('MenuListItem.onClicked', mouse.button, Qt.LeftButton, Qt.RightButton)
166 | if (mouse.button == Qt.LeftButton) {
167 | trigger()
168 | } else if (mouse.button == Qt.RightButton) {
169 | contextMenu.open(mouse.x, mouse.y)
170 | }
171 | }
172 |
173 | function trigger() {
174 | listView.model.triggerIndex(index)
175 | }
176 |
177 | // property bool hasActionList: listView.model.hasActionList(index)
178 | // property var actionList: hasActionList ? listView.model.getActionList(index) : []
179 | AppContextMenu {
180 | id: contextMenu
181 | onPopulateMenu: function(menu) {
182 | if (launcherUrl && !plasmoid.configuration.tilesLocked) {
183 | menu.addPinToMenuAction(launcherUrl)
184 | }
185 | if (listView.model.hasActionList(index)) {
186 | var actionList = listView.model.getActionList(index)
187 | menu.addActionList(actionList, listView.model, index)
188 | }
189 | }
190 | }
191 |
192 | } // delegate: AppToolButton
193 |
--------------------------------------------------------------------------------
/package/contents/ui/Popup.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.core as PlasmaCore
5 |
6 | MouseArea {
7 | id: popup
8 | property alias searchView: searchView
9 | property alias appsView: searchView.appsView
10 | property alias tileEditorView: searchView.tileEditorView
11 | property alias tileEditorViewLoader: searchView.tileEditorViewLoader
12 | property alias tileGrid: tileGrid
13 |
14 | RowLayout {
15 | anchors.fill: parent
16 | spacing: 0
17 |
18 | Item {
19 | id: sidebarPlaceholder
20 | implicitWidth: config.sidebarWidth + config.sidebarRightMargin
21 | Layout.fillHeight: true
22 | }
23 |
24 | SearchView {
25 | id: searchView
26 | Layout.fillHeight: true
27 | }
28 |
29 | TileGrid {
30 | id: tileGrid
31 | Layout.fillWidth: true
32 | Layout.fillHeight: true
33 |
34 | cellSize: config.cellSize
35 | cellMargin: config.cellMargin
36 | cellPushedMargin: config.cellPushedMargin
37 |
38 | tileModel: config.tileModel.value
39 |
40 | onEditTile: function(tile) { tileEditorViewLoader.open(tile) }
41 |
42 | onTileModelChanged: saveTileModel.restart()
43 | Timer {
44 | id: saveTileModel
45 | interval: 2000
46 | onTriggered: config.tileModel.save()
47 | }
48 | }
49 |
50 | }
51 |
52 | SidebarView {
53 | id: sidebarView
54 | }
55 |
56 | MouseArea {
57 | visible: !plasmoid.configuration.tilesLocked && !(plasmoid.location == PlasmaCore.Types.TopEdge || plasmoid.location == PlasmaCore.Types.RightEdge)
58 | anchors.top: parent.top
59 | anchors.right: parent.right
60 | width: Kirigami.Units.largeSpacing
61 | height: Kirigami.Units.largeSpacing
62 | cursorShape: Qt.WhatsThisCursor
63 |
64 | PlasmaCore.ToolTipArea {
65 | anchors.fill: parent
66 | icon: "help-hint"
67 | mainText: i18n("Resize?")
68 | subText: i18n("Meta + Right Click to resize the menu.")
69 | }
70 | }
71 |
72 | MouseArea {
73 | visible: !plasmoid.configuration.tilesLocked && !(plasmoid.location == PlasmaCore.Types.BottomEdge || plasmoid.location == PlasmaCore.Types.RightEdge)
74 | anchors.bottom: parent.bottom
75 | anchors.right: parent.right
76 | width: Kirigami.Units.largeSpacing
77 | height: Kirigami.Units.largeSpacing
78 | cursorShape: Qt.WhatsThisCursor
79 |
80 | PlasmaCore.ToolTipArea {
81 | anchors.fill: parent
82 | icon: "help-hint"
83 | mainText: i18n("Resize?")
84 | subText: i18n("Meta + Right Click to resize the menu.")
85 | }
86 | }
87 |
88 | onClicked: searchView.searchField.forceActiveFocus()
89 | }
90 |
--------------------------------------------------------------------------------
/package/contents/ui/RoundShadow.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 by Daker Fernandes Pinheiro
3 | * Copyright (C) 2011 by Marco Martin
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 2, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public
16 | * License along with this program; if not, write to the
17 | * Free Software Foundation, Inc.,
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
19 | */
20 |
21 | /**Documented API
22 | Inherits:
23 | Item
24 |
25 | Imports:
26 | QtQuick 2.1
27 | org.kde.plasma.core
28 |
29 | Description:
30 | It is a simple Radio button which is using the plasma theme.
31 | TODO Do we need more info?
32 |
33 | Properties:
34 | TODO needs more info??
35 | **/
36 |
37 | import QtQuick
38 | import org.kde.kirigami as Kirigami
39 | import org.kde.ksvg as KSvg
40 |
41 | Item {
42 | id: main
43 | state: parent.state
44 | property alias imagePath: shadowSvg.imagePath
45 | property string hoverElement: "hover"
46 | property string focusElement: "focus"
47 | property alias shadowElement: shadow.elementId
48 |
49 | //used to tell apart this implementation with the touch components one
50 | property bool hasOverState: true
51 |
52 | KSvg.Svg {
53 | id: shadowSvg
54 | imagePath: "widgets/actionbutton"
55 | }
56 |
57 | KSvg.SvgItem {
58 | id: hover
59 | svg: shadowSvg
60 | elementId: "hover"
61 |
62 | anchors.fill: parent
63 |
64 | opacity: 0
65 | }
66 |
67 | KSvg.SvgItem {
68 | id: shadow
69 | svg: shadowSvg
70 | elementId: "shadow"
71 |
72 | anchors.fill: parent
73 | }
74 |
75 | states: [
76 | State {
77 | name: "shadow"
78 | PropertyChanges {
79 | target: shadow
80 | opacity: 1
81 | }
82 | PropertyChanges {
83 | target: hover
84 | opacity: 0
85 | elementId: hoverElement
86 | }
87 | },
88 | State {
89 | name: "hover"
90 | PropertyChanges {
91 | target: shadow
92 | opacity: 0
93 | }
94 | PropertyChanges {
95 | target: hover
96 | opacity: 1
97 | elementId: hoverElement
98 | }
99 | },
100 | State {
101 | name: "focus"
102 | PropertyChanges {
103 | target: shadow
104 | opacity: 0
105 | }
106 | PropertyChanges {
107 | target: hover
108 | opacity: 1
109 | elementId: focusElement
110 | }
111 | },
112 | State {
113 | name: "hidden"
114 | PropertyChanges {
115 | target: shadow
116 | opacity: 0
117 | }
118 | PropertyChanges {
119 | target: hover
120 | opacity: 0
121 | elementId: hoverElement
122 | }
123 | }
124 | ]
125 |
126 | transitions: [
127 | Transition {
128 | PropertyAnimation {
129 | properties: "opacity"
130 | duration: Kirigami.Units.longDuration
131 | easing.type: Easing.OutQuad
132 | }
133 | }
134 | ]
135 | }
136 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchField.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | // import QtQuick.Controls as QQC2
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.components as PlasmaComponents3
5 |
6 | // import QtQuick.Controls.Styles.Plasma 2.0 as PlasmaStyles
7 |
8 | // QQC2.TextField {
9 | PlasmaComponents3.TextField {
10 | id: searchField
11 | placeholderText: {
12 | if (search.isDefaultFilter) {
13 | return i18n("Search")
14 | } else if (search.isAppsFilter) {
15 | return i18n("Search Apps")
16 | } else if (search.isFileFilter) {
17 | return i18n("Search Files")
18 | } else if (search.isBookmarksFilter) {
19 | return i18n("Search Bookmarks")
20 | } else {
21 | return i18nc("Search [krunnerName, krunnerName, ...], ", "Search %1", search.filters.toString())
22 | }
23 | }
24 | property int topMargin: 0
25 | property int bottomMargin: 0
26 | property int defaultFontSize: 16 * Screen.devicePixelRatio // Not the same as pointSize=16
27 | property int styleMaxFontSize: height - topMargin - bottomMargin
28 | font.pixelSize: Math.min(defaultFontSize, styleMaxFontSize)
29 |
30 | // style: plasmoid.configuration.searchFieldFollowsTheme ? plasmaStyle : redmondStyle
31 | // Component {
32 | // id: plasmaStyle
33 | // // Creates the following warning when not in use:
34 | // // file:///usr/lib/x86_64-linux-gnu/qt5/qml/QtQuick/Controls/Styles/Plasma/TextFieldStyle.qml:74: ReferenceError: textField is not defined
35 | // // Caused by:
36 | // // var actionIconSize = Math.max(textField.height * 0.8, Kirigami.Units.iconSizes.small);
37 | // PlasmaStyles.TextFieldStyle {
38 | // id: style
39 | // Component.onCompleted: {
40 | // searchField.topMargin = Qt.binding(function() {
41 | // return style.padding.top
42 | // })
43 | // searchField.bottomMargin = Qt.binding(function() {
44 | // return style.padding.bottom
45 | // })
46 | // }
47 | // }
48 | // }
49 | // Component {
50 | // id: redmondStyle
51 |
52 | // // https://github.com/qt/qtquickcontrols/blob/dev/src/controls/Styles/Base/TextFieldStyle.qml
53 | // // https://github.com/qt/qtquickcontrols/blob/dev/src/controls/Styles/Desktop/TextFieldStyle.qml
54 | // QtStyles.TextFieldStyle {
55 | // id: style
56 |
57 | // background: Rectangle {
58 | // color: "#eee"
59 | // }
60 | // textColor: "#111"
61 | // placeholderTextColor: "#777"
62 |
63 | // Component.onCompleted: {
64 | // searchField.topMargin = Qt.binding(function() {
65 | // return style.padding.top
66 | // })
67 | // searchField.bottomMargin = Qt.binding(function() {
68 | // return style.padding.bottom
69 | // })
70 | // }
71 | // }
72 | // }
73 |
74 | onTextChanged: {
75 | search.query = text
76 | }
77 | Connections {
78 | target: search
79 | function onQueryChanged() {
80 | searchField.text = search.query
81 | }
82 | }
83 |
84 | property var listView: searchResultsView.listView
85 | Keys.onPressed: function(event) {
86 | if (event.key == Qt.Key_Up) {
87 | event.accepted = true; listView.goUp()
88 | } else if (event.key == Qt.Key_Down) {
89 | event.accepted = true; listView.goDown()
90 | } else if (event.key == Qt.Key_PageUp) {
91 | event.accepted = true; listView.pageUp()
92 | } else if (event.key == Qt.Key_PageDown) {
93 | event.accepted = true; listView.pageDown()
94 | } else if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) {
95 | event.accepted = true; listView.currentItem.trigger()
96 | } else if (event.modifiers & Qt.MetaModifier && event.key == Qt.Key_R) {
97 | event.accepted = true; search.filters = ['shell']
98 | } else if (event.key == Qt.Key_Escape) {
99 | plasmoid.expanded = false
100 | }
101 | }
102 |
103 | Component.onCompleted: {
104 | forceActiveFocus()
105 | }
106 | }
--------------------------------------------------------------------------------
/package/contents/ui/SearchFiltersView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 |
4 | ColumnLayout {
5 | id: searchFiltersView
6 | // width: parent.width
7 | // Layout.fillHeight: true
8 |
9 | SearchFiltersViewItem {
10 | visible: false
11 | runnerId: ""
12 | indentLevel: 0
13 | iconSource: "applications-other"
14 | text: i18n("All") + ' (Not working)'
15 | subText: i18n("Search with all KRunner plugins")
16 | checkBox.visible: false
17 | onApplyButtonClicked: search.filters = []
18 | enabled: false
19 | }
20 |
21 | SearchFiltersViewItem {
22 | runnerId: ""
23 | indentLevel: 0
24 | iconSource: "applications-other"
25 | text: i18n("Default")
26 | subText: i18n("Search with user selected defaults")
27 | checkBox.visible: false
28 | onApplyButtonClicked: search.applyDefaultFilters()
29 | }
30 |
31 | // Installed runners are listed at: /usr/share/kservices5/plasma-runner-*.desktop
32 |
33 | SearchFiltersViewItem {
34 | runnerId: "services"
35 | indentLevel: 1
36 | iconSource: "window"
37 | text: i18n("Applications")
38 | }
39 |
40 | SearchFiltersViewItem {
41 | runnerId: "baloosearch"
42 | indentLevel: 1
43 | iconSource: "document-new"
44 | text: i18n("Files")
45 | }
46 |
47 | //--- baloosearch filters
48 | // https://github.com/KDE/baloo/blob/master/docs/user/searching.md#advanced-searches
49 | // Use `type:Audio` or `type:Document` to filter specific filetypes.
50 | SearchFiltersViewItem {
51 | runnerId: "baloosearch"
52 | indentLevel: 2
53 | iconSource: "folder-music-symbolic"
54 | text: i18n("Music")
55 | checkBox.visible: false
56 | onApplyButtonClicked: search.setQueryPrefix('type:Audio ')
57 | }
58 | SearchFiltersViewItem {
59 | runnerId: "baloosearch"
60 | indentLevel: 2
61 | iconSource: "folder-videos-symbolic"
62 | text: i18n("Videos")
63 | checkBox.visible: false
64 | onApplyButtonClicked: search.setQueryPrefix('type:Video ')
65 | }
66 | //--- end baloosearch filters
67 |
68 | SearchFiltersViewItem {
69 | runnerId: "krunner_systemsettings"
70 | indentLevel: 1
71 | iconSource: "preferences-system"
72 | text: i18n("System Settings")
73 | }
74 |
75 | SearchFiltersViewItem {
76 | runnerId: "bookmarks"
77 | indentLevel: 1
78 | iconSource: "globe"
79 | text: i18n("Bookmarks")
80 | }
81 |
82 | SearchFiltersViewItem {
83 | runnerId: "locations"
84 | indentLevel: 1
85 | iconSource: "system-file-manager"
86 | text: i18n("Locations")
87 | }
88 |
89 | SearchFiltersViewItem {
90 | runnerId: "Dictionary"
91 | indentLevel: 1
92 | iconSource: "accessories-dictionary"
93 | text: i18n("Dictionary")
94 | onApplyButtonClicked: search.setQueryPrefix('define ')
95 | }
96 |
97 | SearchFiltersViewItem {
98 | runnerId: "shell"
99 | indentLevel: 1
100 | iconSource: "system-run"
101 | text: i18n("Shell")
102 | }
103 |
104 | SearchFiltersViewItem {
105 | runnerId: "calculator"
106 | indentLevel: 1
107 | iconSource: "accessories-calculator"
108 | text: i18n("Calculator")
109 | }
110 |
111 | SearchFiltersViewItem {
112 | runnerId: "org.kde.windowedwidgets"
113 | indentLevel: 1
114 | iconSource: "plasma"
115 | text: i18n("Windowed Widgets")
116 | }
117 |
118 | SearchFiltersViewItem {
119 | runnerId: "org.kde.datetime"
120 | indentLevel: 1
121 | iconSource: "clock"
122 | text: i18n("Date/Time")
123 | }
124 |
125 | SearchFiltersViewItem {
126 | runnerId: "unitconverter"
127 | indentLevel: 1
128 | iconSource: "accessories-calculator"
129 | text: i18n("Unit Converter")
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchFiltersViewItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import QtQuick.Window
4 | import org.kde.kirigami as Kirigami
5 | import org.kde.plasma.components as PlasmaComponents3
6 | import org.kde.ksvg as KSvg
7 |
8 | // import QtQuick.Controls.Styles 1.1 as QtQuickControlStyle
9 | // import QtQuick.Controls.Styles.Plasma 2.0 as PlasmaStyles
10 |
11 | RowLayout {
12 | id: searchFiltersViewItem
13 | Layout.fillWidth: true
14 | spacing: 0
15 |
16 | property string runnerId: ''
17 | property int indentLevel: 0
18 |
19 | property alias iconSource: applyFilterButton.icon.source
20 | property alias text: applyFilterButton.text
21 | property alias subText: applyFilterButton.subText
22 |
23 | property alias checkBox: isDefaultFilter
24 | property alias applyButton: applyFilterButton
25 |
26 | signal applyButtonClicked()
27 |
28 | property var surfaceNormal: KSvg.FrameSvgItem {
29 | anchors.fill: parent
30 | imagePath: "widgets/button"
31 | prefix: "normal"
32 | // prefix: style.flat ? ["toolbutton-hover", "normal"] : "normal"
33 | }
34 |
35 | Item { // Align CheckBoxes buttons to "All"
36 | Layout.minimumWidth: surfaceNormal.margins.left + (config.flatButtonIconSize + surfaceNormal.margins.left) * searchFiltersViewItem.indentLevel
37 | Layout.maximumWidth: Layout.minimumWidth
38 | Layout.fillHeight: true
39 | }
40 |
41 | PlasmaComponents3.ToolButton {
42 | id: applyFilterButton
43 | Layout.fillWidth: true
44 | property string subText: ""
45 |
46 | // style: PlasmaStyles.ToolButtonStyle {
47 | // id: style
48 | // readonly property bool smallIcon: !control.subText
49 |
50 | // label: RowLayout {
51 | // Kirigami.Icon {
52 | // source: control.iconSource
53 | // Layout.preferredHeight: style.smallIcon ? config.flatButtonIconSize : -1
54 | // Layout.preferredWidth: style.smallIcon ? config.flatButtonIconSize : -1
55 | // }
56 | // ColumnLayout {
57 | // id: textColumn
58 | // Layout.fillWidth: true
59 | // Layout.fillHeight: true
60 | // spacing: 0
61 | // PlasmaComponents3.Label {
62 | // Layout.fillWidth: true
63 | // text: control.text
64 | // horizontalAlignment: Text.AlignLeft
65 | // maximumLineCount: 1
66 | // elide: Text.ElideRight
67 | // }
68 | // PlasmaComponents3.Label {
69 | // Layout.fillWidth: true
70 | // text: control.subText
71 | // horizontalAlignment: Text.AlignLeft
72 | // visible: control.subText
73 | // color: config.menuItemTextColor2
74 | // maximumLineCount: 1
75 | // elide: Text.ElideRight
76 | // }
77 | // }
78 | // }
79 | // }
80 | onClicked: {
81 | if (searchFiltersViewItem.runnerId) {
82 | search.filters = [searchFiltersViewItem.runnerId]
83 | }
84 | searchFiltersViewItem.applyButtonClicked()
85 | searchResultsView.filterViewOpen = false
86 | }
87 | }
88 |
89 | PlasmaComponents3.CheckBox {
90 | id: isDefaultFilter
91 | checked: search.defaultFiltersContains(searchFiltersViewItem.runnerId)
92 | onCheckedChanged: {
93 | if (checked) {
94 | search.addDefaultFilter(searchFiltersViewItem.runnerId)
95 | } else {
96 | search.removeDefaultFilter(searchFiltersViewItem.runnerId)
97 | }
98 | }
99 | Layout.fillHeight: true
100 | text: i18n("Default")
101 | }
102 |
103 | Item { // Align CheckBoxes buttons to "All"
104 | Layout.minimumWidth: surfaceNormal.margins.right
105 | Layout.maximumWidth: Layout.minimumWidth
106 | Layout.fillHeight: true
107 | // visible: isDefaultFilter.visible && searchFiltersViewItem.indentLevel > 0
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchModel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.private.kicker as Kicker
3 |
4 | Item {
5 | id: search
6 | property alias results: resultModel
7 | property alias runnerModel: runnerModel
8 |
9 | property string query: ""
10 | property bool isSearching: query.length > 0
11 | onQueryChanged: {
12 | runnerModel.query = search.query
13 | }
14 |
15 | // KRunner runners are defined in /usr/share/kservices5/plasma-runner-*.desktop
16 | // To list the runner ids, use:
17 | // find /usr/share/kservices5/ -iname "plasma-runner-*.desktop" -print0 | xargs -0 grep "PluginInfo-Name" | sort
18 | property var filters: []
19 | onFiltersChanged: {
20 | // runnerModel.deleteWhenEmpty = !runnerModel.deleteWhenEmpty // runnerModel.clear()
21 | // runnerModel.runners = filters
22 | clearQueryPrefix()
23 | runnerModel.query = search.query
24 | }
25 |
26 | Kicker.RunnerModel {
27 | id: runnerModel
28 |
29 | appletInterface: plasmoid
30 | favoritesModel: rootModel.favoritesModel
31 | mergeResults: config.searchResultsMerged
32 |
33 | // runners: [] // Empty = All runners.
34 |
35 | // deleteWhenEmpty: isDash
36 | // deleteWhenEmpty: false
37 |
38 | onRunnersChanged: debouncedRefresh.restart()
39 | onDataChanged: debouncedRefresh.restart()
40 | onCountChanged: debouncedRefresh.restart()
41 | }
42 |
43 | Timer {
44 | id: debouncedRefresh
45 | interval: 100
46 | onTriggered: resultModel.refresh()
47 |
48 | function logAndRestart() {
49 | // console.log('debouncedRefresh')
50 | restart()
51 | }
52 | }
53 |
54 | SearchResultsModel {
55 | id: resultModel
56 | }
57 |
58 | readonly property var defaultFilters: plasmoid.configuration.searchDefaultFilters
59 | function defaultFiltersContains(runnerId) {
60 | return defaultFilters.indexOf(runnerId) != -1
61 | }
62 | function addDefaultFilter(runnerId) {
63 | if (!defaultFiltersContains(runnerId)) {
64 | var l = plasmoid.configuration.searchDefaultFilters
65 | l.push(runnerId)
66 | plasmoid.configuration.searchDefaultFilters = l
67 | }
68 | }
69 | function removeDefaultFilter(runnerId) {
70 | console.log(JSON.stringify(plasmoid.configuration.searchDefaultFilters))
71 | var i = defaultFilters.indexOf(runnerId)
72 | if (i >= 0) {
73 | var l = plasmoid.configuration.searchDefaultFilters
74 | l.splice(i, 1) // Remove 1 item at index
75 | plasmoid.configuration.searchDefaultFilters = l
76 | }
77 | }
78 |
79 | function isFilter(runnerId) {
80 | return filters.length == 1 && filters[0] == runnerId
81 | }
82 | property bool isDefaultFilter: filters == defaultFilters
83 | property bool isAppsFilter: isFilter('services')
84 | property bool isFileFilter: isFilter('baloosearch')
85 | property bool isBookmarksFilter: isFilter('bookmarks')
86 |
87 | function hasFilter(runnerId) {
88 | return filters.indexOf(runnerId) >= 0
89 | }
90 |
91 | function applyDefaultFilters() {
92 | filters = defaultFilters
93 | }
94 |
95 | function setQueryPrefix(prefix) {
96 | // First check to see if there's already a prefix we need to replace.
97 | var firstSpaceIndex = query.indexOf(' ')
98 | if (firstSpaceIndex > 0) {
99 | var firstToken = query.substring(0, firstSpaceIndex)
100 |
101 | if (/^type:\w+$/.exec(firstToken) // baloosearch
102 | || /^define$/.exec(firstToken) // Dictionary
103 | ) {
104 | // replace existing prefix
105 | query = prefix + query.substring(firstSpaceIndex + 1, query.length)
106 | return
107 | }
108 | }
109 |
110 | // If not, just prepend the prefix
111 | var newQuery = prefix + query
112 | if (newQuery != query) {
113 | query = prefix + query
114 | }
115 | }
116 |
117 | function clearQueryPrefix() {
118 | setQueryPrefix('')
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchResultsList.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | KickerListView { // RunnerResultsList
4 | id: searchResultsList
5 |
6 | model: []
7 | delegate: MenuListItem {
8 | property var runner: search.runnerModel.modelForRow(model.runnerIndex)
9 | iconSource: runner && runner.data(runner.index(model.runnerItemIndex, 0), Qt.DecorationRole)
10 | }
11 |
12 | section.property: 'runnerName'
13 | section.criteria: ViewSection.FullString
14 | // verticalLayoutDirection: config.searchResultsDirection
15 |
16 | Connections {
17 | target: search.results
18 | function onRefreshing() {
19 | searchResultsList.model = []
20 | // console.log('search.results.onRefreshed')
21 | searchResultsList.currentIndex = 0
22 | }
23 | function onRefreshed() {
24 | // console.log('search.results.onRefreshed')
25 | searchResultsList.model = search.results
26 | // if (searchResultsList.verticalLayoutDirection == Qt.BottomToTop) {
27 | if (plasmoid.configuration.searchResultsReversed) {
28 | searchResultsList.currentIndex = searchResultsList.model.count - 1
29 | } else { // TopToBottom (normal)
30 | searchResultsList.currentIndex = 0
31 | }
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchResultsView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 | import QtQuick.Layouts
4 | import org.kde.plasma.components as PlasmaComponents3
5 | import org.kde.kirigami as Kirigami
6 |
7 | GridLayout {
8 | id: searchResultsView
9 | rowSpacing: 0
10 | property alias listView: searchResultsList
11 | property bool filterViewOpen: false
12 |
13 | RowLayout {
14 | id: searchFiltersRow
15 | Layout.row: searchView.searchOnTop ? 2 : 0
16 | Layout.preferredHeight: config.searchFilterRowHeight - 1 // -1px is for the underline seperator
17 | Layout.fillWidth: true
18 |
19 | FlatButton {
20 | icon.name: "system-search-symbolic"
21 | Layout.preferredHeight: parent.Layout.preferredHeight
22 | Layout.preferredWidth: parent.Layout.preferredHeight
23 | onClicked: search.applyDefaultFilters()
24 | checked: search.isDefaultFilter
25 | checkedEdge: searchView.searchOnTop ? Qt.TopEdge : Qt.BottomEdge
26 | }
27 | FlatButton {
28 | icon.name: "window"
29 | Layout.preferredHeight: parent.Layout.preferredHeight
30 | Layout.preferredWidth: parent.Layout.preferredHeight
31 | onClicked: search.filters = ['services']
32 | checked: search.isAppsFilter
33 | checkedEdge: searchView.searchOnTop ? Qt.TopEdge : Qt.BottomEdge
34 | }
35 | FlatButton {
36 | icon.name: "document-new"
37 | Layout.preferredHeight: parent.Layout.preferredHeight
38 | Layout.preferredWidth: parent.Layout.preferredHeight
39 | onClicked: search.filters = ['baloosearch']
40 | checked: search.isFileFilter
41 | checkedEdge: searchView.searchOnTop ? Qt.TopEdge : Qt.BottomEdge
42 | }
43 | // FlatButton {
44 | // icon.name: "globe"
45 | // Layout.preferredHeight: parent.Layout.preferredHeight
46 | // Layout.preferredWidth: parent.Layout.preferredHeight
47 | // onClicked: search.filters = ['bookmarks']
48 | // checked: search.isBookmarksFilter
49 | // checkedEdge: searchView.searchOnTop ? Qt.TopEdge : Qt.BottomEdge
50 | // }
51 |
52 | Item { Layout.fillWidth: true }
53 |
54 | FlatButton {
55 | id: moreFiltersButton
56 | Layout.preferredHeight: parent.Layout.preferredHeight
57 | Layout.preferredWidth: moreFiltersButtonRow.implicitWidth + padding*2
58 | // property int padding: (config.searchFilterRowHeight - config.flatButtonIconSize) / 2
59 | padding: (config.searchFilterRowHeight - config.flatButtonIconSize) / 2
60 | // enabled: false
61 |
62 | RowLayout {
63 | id: moreFiltersButtonRow
64 | anchors.centerIn: parent
65 | anchors.margins: parent.padding
66 |
67 | PlasmaComponents3.Label {
68 | id: moreFiltersButtonLabel
69 | text: i18n("Filters")
70 | }
71 | Kirigami.Icon {
72 | source: "usermenu-down"
73 | rotation: searchResultsView.filterViewOpen ? 180 : 0
74 | Layout.preferredHeight: config.flatButtonIconSize
75 | Layout.preferredWidth: config.flatButtonIconSize
76 |
77 | Behavior on rotation {
78 | NumberAnimation { duration: Kirigami.Units.longDuration }
79 | }
80 | }
81 | }
82 |
83 | onClicked: searchResultsView.filterViewOpen = !searchResultsView.filterViewOpen
84 | }
85 | }
86 |
87 | Rectangle {
88 | color: "#111"
89 | height: 1
90 | width: parent.width
91 | // anchors.bottom: searchFiltersRow.bottom - 1
92 | }
93 |
94 | QQC2.StackView {
95 | id: searchResultsViewStackView
96 | Layout.row: searchView.searchOnTop ? 0 : 2
97 | Layout.fillWidth: true
98 | Layout.fillHeight: true
99 | clip: true
100 | initialItem: searchResultsListScrollView
101 |
102 | Connections {
103 | target: searchResultsView
104 | function onFilterViewOpenChanged() {
105 | if (searchResultsView.filterViewOpen) {
106 | searchResultsViewStackView.push(searchFiltersViewScrollView)
107 | } else {
108 | searchResultsViewStackView.pop()
109 | }
110 | }
111 | }
112 |
113 | QQC2.ScrollView {
114 | id: searchResultsListScrollView
115 | visible: false
116 |
117 | SearchResultsList {
118 | id: searchResultsList
119 | }
120 | }
121 |
122 | QQC2.ScrollView {
123 | id: searchFiltersViewScrollView
124 | visible: false
125 |
126 | SearchFiltersView {
127 | id: searchFiltersView
128 | }
129 | }
130 |
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchStackView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 |
4 | QQC2.StackView {
5 | id: stackView
6 | clip: true
7 |
8 | // delegate: panUp
9 |
10 | property int zoomDuration: 250
11 | property int zoomDelta: 100
12 | property real zoomedInRatio: Math.max(1, stackView.width + zoomDelta * Screen.devicePixelRatio) / stackView.width
13 | property real zoomedOutRatio: Math.max(1, stackView.width - zoomDelta * Screen.devicePixelRatio) / stackView.width
14 |
15 | // readonly property var noTransition: StackViewDelegate {}
16 |
17 | // readonly property var panUp: StackViewDelegate {
18 | // pushTransition: StackViewTransition {
19 | // PropertyAnimation {
20 | // target: enterItem
21 | // property: "y"
22 | // from: stackView.height * (searchView.searchOnTop ? -1 : 1)
23 | // to: 0
24 | // }
25 | // PropertyAnimation {
26 | // target: exitItem
27 | // property: "opacity"
28 | // from: 1
29 | // to: 0
30 | // }
31 | // }
32 |
33 | // function transitionFinished(properties) {
34 | // properties.exitItem.opacity = 1
35 | // }
36 | // }
37 | // readonly property var zoomOut: StackViewDelegate {
38 | // function transitionFinished(properties) {
39 | // properties.exitItem.opacity = 1
40 | // properties.exitItem.scale = 1
41 | // }
42 |
43 | // pushTransition: StackViewTransition {
44 | // PropertyAnimation {
45 | // target: enterItem
46 | // property: "opacity"
47 | // easing.type: Easing.InQuad
48 | // from: 0
49 | // to: 1
50 | // duration: stackView.zoomDuration
51 | // }
52 | // PropertyAnimation {
53 | // target: exitItem
54 | // property: "opacity"
55 | // easing.type: Easing.InQuad
56 | // from: 1
57 | // to: 0
58 | // duration: stackView.zoomDuration
59 | // }
60 | // PropertyAnimation {
61 | // target: enterItem
62 | // property: "scale"
63 | // easing.type: Easing.Linear
64 | // from: stackView.zoomedInRatio
65 | // to: 1
66 | // duration: stackView.zoomDuration
67 | // }
68 | // PropertyAnimation {
69 | // target: exitItem
70 | // property: "scale"
71 | // easing.type: Easing.Linear
72 | // from: 1
73 | // to: stackView.zoomedOutRatio
74 | // duration: stackView.zoomDuration
75 | // }
76 | // }
77 | // }
78 | // readonly property var zoomIn: StackViewDelegate {
79 | // function transitionFinished(properties) {
80 | // properties.exitItem.opacity = 1
81 | // properties.exitItem.scale = 1
82 | // }
83 |
84 | // pushTransition: StackViewTransition {
85 | // PropertyAnimation {
86 | // target: enterItem
87 | // property: "opacity"
88 | // easing.type: Easing.InQuad
89 | // from: 0
90 | // to: 1
91 | // duration: stackView.zoomDuration
92 | // }
93 | // PropertyAnimation {
94 | // target: exitItem
95 | // property: "opacity"
96 | // easing.type: Easing.InQuad
97 | // from: 1
98 | // to: 0
99 | // duration: stackView.zoomDuration
100 | // }
101 | // PropertyAnimation {
102 | // target: enterItem
103 | // property: "scale"
104 | // easing.type: Easing.Linear
105 | // from: stackView.zoomedOutRatio
106 | // to: 1
107 | // duration: stackView.zoomDuration
108 | // }
109 | // PropertyAnimation {
110 | // target: exitItem
111 | // property: "scale"
112 | // easing.type: Easing.Linear
113 | // from: 1
114 | // to: stackView.zoomedInRatio
115 | // duration: stackView.zoomDuration
116 | // }
117 | // }
118 | // popTransition: pushTransition
119 | // }
120 | }
121 |
--------------------------------------------------------------------------------
/package/contents/ui/SearchView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | Item {
4 | id: searchView
5 | implicitWidth: config.appAreaWidth
6 | // Behavior on implicitWidth {
7 | // NumberAnimation { duration: 400 }
8 | // }
9 |
10 | visible: opacity > 0
11 | opacity: config.showSearch ? 1 : 0
12 | // Behavior on opacity {
13 | // NumberAnimation { duration: 400 }
14 | // }
15 |
16 | Connections {
17 | target: search
18 | function onIsSearchingChanged() {
19 | if (search.isSearching) {
20 | searchView.showSearchView()
21 | }
22 | }
23 | }
24 | clip: true
25 |
26 | property alias searchResultsView: searchResultsView
27 | property alias appsView: appsView
28 | property alias tileEditorView: tileEditorViewLoader.item
29 | property alias tileEditorViewLoader: tileEditorViewLoader
30 | property alias searchField: searchField
31 | property alias jumpToLetterView: jumpToLetterView
32 |
33 | readonly property bool showingOnlyTiles: !config.showSearch
34 | readonly property bool showingAppList: stackView.currentItem == appsView || stackView.currentItem == jumpToLetterView
35 | readonly property bool showingAppsAlphabetically: config.showSearch && appsModel.order == "alphabetical" && showingAppList
36 | readonly property bool showingAppsCategorically: config.showSearch && appsModel.order == "categories" && showingAppList
37 | readonly property bool showSearchField: config.hideSearchField ? !!searchField.text : true
38 |
39 | property bool searchOnTop: false
40 |
41 | function showDefaultView() {
42 | var defView = plasmoid.configuration.defaultAppListView
43 | if (defView == 'Alphabetical') {
44 | appsView.showAppsAlphabetically()
45 | config.showSearch = true
46 | } else if (defView == 'Categories') {
47 | appsView.showAppsCategorically()
48 | config.showSearch = true
49 | } else if (defView == 'JumpToLetter') {
50 | jumpToLetterView.showLetters()
51 | config.showSearch = true
52 | } else if (defView == 'JumpToCategory') {
53 | jumpToLetterView.showCategories()
54 | config.showSearch = true
55 | } else if (defView == 'TilesOnly') {
56 | searchView.showTilesOnly()
57 | }
58 | }
59 |
60 | function showTilesOnly() {
61 | if (!showingAppList) {
62 | // appsView.show(stackView.noTransition)
63 | appsView.show()
64 | }
65 | config.showSearch = false
66 | }
67 |
68 | function showSearchView() {
69 | config.showSearch = true
70 | }
71 |
72 | states: [
73 | State {
74 | name: "searchOnTop"
75 | when: searchOnTop
76 | PropertyChanges {
77 | target: stackViewContainer
78 | anchors.topMargin: searchField.visible ? searchField.height : 0
79 | }
80 | PropertyChanges {
81 | target: searchField
82 | anchors.top: searchField.parent.top
83 | }
84 | },
85 | State {
86 | name: "searchOnBottom"
87 | when: !searchOnTop
88 | PropertyChanges {
89 | target: stackViewContainer
90 | anchors.bottomMargin: searchField.visible ? searchField.height : 0
91 | }
92 | PropertyChanges {
93 | target: searchField
94 | anchors.bottom: searchField.parent.bottom
95 | }
96 | }
97 | ]
98 |
99 |
100 | Item {
101 | id: stackViewContainer
102 | anchors.fill: parent
103 |
104 | SearchResultsView {
105 | id: searchResultsView
106 | visible: false
107 |
108 | Connections {
109 | target: search
110 | function onQueryChanged() {
111 | if (search.query.length > 0 && stackView.currentItem != searchResultsView) {
112 | stackView.replace(searchResultsView)
113 | }
114 | searchResultsView.filterViewOpen = false
115 | }
116 | }
117 |
118 | onVisibleChanged: {
119 | if (!visible) { // !stackView.currentItem
120 | search.query = ""
121 | }
122 | }
123 |
124 | function showDefaultSearch() {
125 | if (stackView.currentItem != searchResultsView) {
126 | stackView.replace(searchResultsView)
127 | }
128 | search.applyDefaultFilters()
129 | }
130 | }
131 |
132 | AppsView {
133 | id: appsView
134 | visible: false
135 |
136 | function showAppsAlphabetically() {
137 | appsModel.order = "alphabetical"
138 | show()
139 | }
140 |
141 | function showAppsCategorically() {
142 | appsModel.order = "categories"
143 | show()
144 | }
145 |
146 | function show(animation) {
147 | config.showSearch = true
148 | if (stackView.currentItem != appsView) {
149 | // stackView.delegate = animation || stackView.panUp
150 | stackView.replace(appsView)
151 | }
152 | appsView.scrollToTop()
153 | }
154 | }
155 |
156 | JumpToLetterView {
157 | id: jumpToLetterView
158 | visible: false
159 |
160 | function showLetters() {
161 | appsModel.order = "alphabetical"
162 | show()
163 | }
164 |
165 | function showCategories() {
166 | appsModel.order = "categories"
167 | show()
168 | }
169 |
170 | function show() {
171 | config.showSearch = true
172 | if (stackView.currentItem != jumpToLetterView) {
173 | // stackView.delegate = stackView.zoomOut
174 | stackView.replace(jumpToLetterView)
175 | }
176 | }
177 | }
178 |
179 | Loader {
180 | id: tileEditorViewLoader
181 | source: "TileEditorView.qml"
182 | visible: false
183 | active: false
184 | // asynchronous: true
185 | function open(tile) {
186 | config.showSearch = true
187 | active = true
188 | item.open(tile)
189 | }
190 | readonly property bool isCurrentView: stackView.currentItem == tileEditorView
191 | onIsCurrentViewChanged: {
192 | config.isEditingTile = isCurrentView
193 | }
194 | }
195 |
196 | SearchStackView {
197 | id: stackView
198 | anchors.fill: parent
199 | initialItem: appsView
200 | }
201 | }
202 |
203 |
204 | SearchField {
205 | id: searchField
206 | visible: !config.isEditingTile && searchView.showSearchField
207 | height: config.searchFieldHeight
208 | anchors.left: parent.left
209 | anchors.right: parent.right
210 |
211 | listView: stackView.currentItem && stackView.currentItem.listView ? stackView.currentItem.listView : []
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarContextMenu.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQml.Models as QtModels
3 | import org.kde.plasma.extras as PlasmaExtras
4 |
5 | // https://invent.kde.org/plasma/plasma-framework/-/blame/master/src/declarativeimports/plasmaextracomponents/qmenu.h
6 | PlasmaExtras.Menu {
7 | id: kickerContextMenu
8 | required property var model
9 |
10 | function toggleOpen() {
11 | if (kickerContextMenu.status == PlasmaExtras.Menu.Open) {
12 | kickerContextMenu.close()
13 | } else if (kickerContextMenu.status == PlasmaExtras.Menu.Closed) {
14 | kickerContextMenu.openRelative()
15 | }
16 | }
17 |
18 | // https://invent.kde.org/plasma/plasma-desktop/-/blame/master/applets/kickoff/package/contents/ui/LeaveButtons.qml
19 | // https://invent.kde.org/plasma/plasma-desktop/-/blame/master/applets/kickoff/package/contents/ui/ActionMenu.qml
20 | // https://doc.qt.io/qt-6/qml-qtqml-models-instantiator.html
21 | property Instantiator _instantiator: QtModels.Instantiator {
22 | model: kickerContextMenu.model
23 | delegate: PlasmaExtras.MenuItem {
24 | icon: model.iconName || model.decoration
25 | text: model.name || model.display
26 | visible: !model.disabled
27 | onClicked: {
28 | kickerContextMenu.model.triggerIndex(index)
29 | }
30 | }
31 | onObjectAdded: (index, object) => kickerContextMenu.addMenuItem(object)
32 | onObjectRemoved: (index, object) => kickerContextMenu.removeMenuItem(object)
33 | }
34 | placement: {
35 | if (searchView.searchOnTop) {
36 | return PlasmaExtras.Menu.BottomPosedRightAlignedPopup
37 | } else {
38 | return PlasmaExtras.Menu.TopPosedRightAlignedPopup
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarFavouritesView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import "./lib/" as Lib
3 |
4 | Repeater {
5 | id: repeater
6 | property int maxHeight: 1000000
7 | property int numAvailable: maxHeight / config.flatButtonSize
8 | property int minVisibleIndex: count - numAvailable // Hide items with an index smaller than this
9 |
10 | property QtObject xdgUserDir: Lib.XdgUserDir {}
11 |
12 | delegate: SidebarItem {
13 | icon.name: symbolicIconName || model.iconName || model.decoration
14 | text: xdgDisplayName || model.name || model.display
15 | sidebarMenu: repeater.parent.parent // SidebarContextMenu { Column { Repeater{} } }
16 | onClicked: {
17 | repeater.parent.parent.open = false // SidebarContextMenu { Column { Repeater{} } }
18 | var xdgFolder = isLocalizedFolder()
19 | if (xdgFolder === 'DOCUMENTS') {
20 | Qt.openUrlExternally(xdgUserDir.documents)
21 | } else if (xdgFolder === 'DOWNLOAD') {
22 | Qt.openUrlExternally(xdgUserDir.download)
23 | } else if (xdgFolder === 'MUSIC') {
24 | Qt.openUrlExternally(xdgUserDir.music)
25 | } else if (xdgFolder === 'PICTURES') {
26 | Qt.openUrlExternally(xdgUserDir.pictures)
27 | } else if (xdgFolder === 'VIDEOS') {
28 | Qt.openUrlExternally(xdgUserDir.videos)
29 | } else {
30 | repeater.model.triggerIndex(index)
31 | }
32 | }
33 | visible: index >= minVisibleIndex
34 |
35 | // These files are localize, so open them via commandline
36 | // since Qt 5.7 doesn't expose the localized paths anywhere.
37 | function isLocalizedFolder() {
38 | var s = model.url.toString()
39 | if (startsWith(s, 'xdg:')) {
40 | s = s.substring('xdg:'.length, s.length)
41 | if (s == 'DOCUMENTS'
42 | || s == 'DOWNLOAD'
43 | || s == 'MUSIC'
44 | || s == 'PICTURES'
45 | || s == 'VIDEOS'
46 | ) {
47 | return s
48 | }
49 | }
50 | return ''
51 | }
52 |
53 | function startsWith(s, sub) {
54 | return s.indexOf(sub) === 0
55 | }
56 | function endsWith(s, sub) {
57 | return s.indexOf(sub) === s.length - sub.length
58 | }
59 |
60 | property string xdgDisplayName: {
61 | var xdgFolder = isLocalizedFolder()
62 | if (xdgFolder) {
63 | // https://translationproject.org/domain/xdg-user-dirs.html
64 | // https://translationproject.org/PO-files/fr/xdg-user-dirs-0.17.fr.po
65 | if (xdgFolder === 'DOCUMENTS') {
66 | return i18nd("xdg-user-dirs", "Documents")
67 | } else if (xdgFolder === 'DOWNLOAD') {
68 | return i18nd("xdg-user-dirs", "Download")
69 | } else if (xdgFolder === 'MUSIC') {
70 | return i18nd("xdg-user-dirs", "Music")
71 | } else if (xdgFolder === 'PICTURES') {
72 | return i18nd("xdg-user-dirs", "Pictures")
73 | } else if (xdgFolder === 'VIDEOS') {
74 | return i18nd("xdg-user-dirs", "Videos")
75 | } else {
76 | return ''
77 | }
78 | } else {
79 | return ''
80 | }
81 | }
82 | property string symbolicIconName: {
83 | if (model.url) {
84 | var s = model.url.toString()
85 | if (endsWith(s, '.desktop')) {
86 | if (endsWith(s, '/org.kde.dolphin.desktop')) {
87 | return 'folder-open-symbolic'
88 | } else if (endsWith(s, '/systemsettings.desktop')) {
89 | return 'configure'
90 | }
91 | } else if (startsWith(s, 'file:///home/')) {
92 | s = s.substring('file:///home/'.length, s.length)
93 | // console.log(model.url, s)
94 |
95 | var trimIndex = s.indexOf('/')
96 | if (trimIndex == -1) { // file:///home/username
97 | s = ''
98 | } else {
99 | s = s.substring(trimIndex, s.length)
100 | }
101 | // console.log(model.url, s)
102 |
103 | if (s === '') { // Home Directory
104 | return 'user-home-symbolic'
105 | }
106 | } else if (startsWith(s, 'xdg:')) {
107 | s = s.substring('xdg:'.length, s.length)
108 | if (s === 'DOCUMENTS') {
109 | return 'folder-documents-symbolic'
110 | } else if (s === 'DOWNLOAD') {
111 | return 'folder-download-symbolic'
112 | } else if (s === 'MUSIC') {
113 | return 'folder-music-symbolic'
114 | } else if (s === 'PICTURES') {
115 | return 'folder-pictures-symbolic'
116 | } else if (s === 'VIDEOS') {
117 | return 'folder-videos-symbolic'
118 | }
119 | }
120 | }
121 | return ""
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 | import QtQuick.Layouts
4 |
5 | FlatButton {
6 | id: sidebarItem
7 | Layout.fillWidth: true
8 | Layout.minimumWidth: expanded ? config.sidebarMinOpenWidth : implicitWidth
9 | property var sidebarMenu: parent.parent // Column.SidebarMenu
10 | expanded: sidebarMenu ? sidebarMenu.open : false
11 | labelVisible: expanded
12 | property bool closeOnClick: true
13 |
14 | QQC2.ToolTip {
15 | id: control
16 | visible: sidebarItem.hovered && !sidebarItem.expanded
17 | text: sidebarItem.text
18 | delay: 0
19 | x: parent.width + rightPadding
20 | y: (parent.height - height) / 2
21 | }
22 |
23 | Loader {
24 | id: hoverOutlineEffectLoader
25 | anchors.fill: parent
26 | source: "HoverOutlineButtonEffect.qml"
27 | asynchronous: true
28 | property var mouseArea: sidebarItem.__behavior
29 | active: !!mouseArea && mouseArea.containsMouse
30 | visible: active
31 | property var __mouseArea: mouseArea
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarItemRepeater.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | Repeater {
4 | id: repeater
5 | property int buttonHeight: config.flatButtonSize
6 | property int iconSize: config.flatButtonIconSize
7 |
8 | delegate: SidebarItem {
9 | buttonHeight: repeater.buttonHeight
10 | iconSize: repeater.iconSize
11 | icon.name: model.iconName || model.decoration
12 | text: model.name || model.display
13 | sidebarMenu: repeater.parent.parent // SidebarContextMenu { Column { Repeater{} } }
14 | onClicked: {
15 | repeater.parent.parent.open = false // SidebarContextMenu { Column { Repeater{} } }
16 | repeater.model.triggerIndex(index)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarMenu.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.kirigami as Kirigami
3 | import org.kde.ksvg as KSvg
4 |
5 | MouseArea {
6 | id: sidebarMenu
7 | hoverEnabled: true
8 | z: 1
9 | // clip: true
10 | implicitWidth: config.sidebarWidth
11 | property bool open: false
12 |
13 | onOpenChanged: {
14 | if (open) {
15 | forceActiveFocus()
16 | } else {
17 | searchView.searchField.forceActiveFocus()
18 | }
19 | }
20 |
21 | Rectangle {
22 | anchors.fill: parent
23 | visible: !plasmoid.configuration.sidebarFollowsTheme
24 | color: config.sidebarBackgroundColor
25 | opacity: parent.open ? 1 : 0
26 | }
27 |
28 | Rectangle {
29 | anchors.fill: parent
30 | visible: plasmoid.configuration.sidebarFollowsTheme
31 | color: Kirigami.Theme.backgroundColor
32 | opacity: parent.open ? 1 : 0
33 | }
34 | KSvg.FrameSvgItem {
35 | anchors.fill: parent
36 | visible: plasmoid.configuration.sidebarFollowsTheme
37 | imagePath: "widgets/frame"
38 | prefix: "raised"
39 | }
40 |
41 | property alias showDropShadow: sidebarMenuShadows.visible
42 | SidebarMenuShadows {
43 | id: sidebarMenuShadows
44 | anchors.fill: parent
45 | visible: !plasmoid.configuration.sidebarFollowsTheme && sidebarMenu.open
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarMenuShadows.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | Item {
4 | property int dropShadowSize: 4 * Screen.devicePixelRatio
5 | property int roundShadowHack: dropShadowSize/2 // "dropShadowSize/2" draws enough to fool the eye.
6 | Rectangle {
7 | id: topShadow
8 | anchors.left: parent.left
9 | anchors.bottom: parent.top
10 | width: parent.width + roundShadowHack
11 | height: dropShadowSize
12 | gradient: Gradient {
13 | GradientStop { position: 0.0; color: "#00000000" }
14 | GradientStop { position: 1.0; color: "#60000000" }
15 | }
16 | }
17 | Rectangle {
18 | id: rightShadow
19 | anchors.bottom: parent.top
20 | anchors.left: parent.right
21 | height: dropShadowSize
22 | width: parent.height
23 |
24 | transformOrigin: Item.BottomLeft
25 | rotation: 90
26 |
27 | gradient: Gradient {
28 | GradientStop { position: 0.0; color: "#00000000" }
29 | GradientStop { position: 1.0; color: "#60000000" }
30 | }
31 | }
32 | Rectangle {
33 | id: bottomShadow
34 | anchors.left: parent.left
35 | anchors.top: parent.bottom
36 | width: parent.width + roundShadowHack
37 | height: dropShadowSize
38 | gradient: Gradient {
39 | GradientStop { position: 0.0; color: "#90000000" }
40 | GradientStop { position: 1.0; color: "#00000000" }
41 | }
42 | }
43 | Rectangle {
44 | id: leftShadow
45 | anchors.bottom: parent.top
46 | anchors.left: parent.left
47 | height: dropShadowSize
48 | width: parent.height
49 |
50 | transformOrigin: Item.BottomLeft
51 | rotation: 90
52 |
53 | gradient: Gradient {
54 | GradientStop { position: 0.0; color: "#00000000" }
55 | GradientStop { position: 1.0; color: "#20000000" }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.core as PlasmaCore
4 | import org.kde.plasma.extras as PlasmaExtras
5 | import org.kde.config as KConfig
6 | import org.kde.draganddrop as DragAndDrop
7 | import org.kde.kcmutils as KCM // KCMLauncher
8 | import "Utils.js" as Utils
9 |
10 | Item {
11 | id: sidebarView
12 | anchors.left: parent.left
13 | anchors.top: parent.top
14 | anchors.bottom: parent.bottom
15 | z: 1
16 |
17 | width: sidebarMenu.width
18 | Behavior on width { NumberAnimation { duration: 100 } }
19 |
20 | DragAndDrop.DropArea {
21 | anchors.fill: sidebarMenu
22 |
23 | onDrop: {
24 | if (event && event.mimeData && event.mimeData.url) {
25 | var url = event.mimeData.url.toString()
26 | url = Utils.parseDropUrl(url)
27 | appsModel.sidebarModel.addFavorite(url, 0)
28 | }
29 | }
30 | }
31 |
32 | SidebarMenu {
33 | id: sidebarMenu
34 | anchors.left: parent.left
35 | anchors.top: parent.top
36 | anchors.bottom: parent.bottom
37 |
38 |
39 | ColumnLayout {
40 | id: sidebarMenuTop
41 | spacing: 0
42 |
43 | // SidebarItem {
44 | // icon.name: 'open-menu-symbolic'
45 | // text: i18n("Menu")
46 | // closeOnClick: false
47 | // onClicked: sidebarMenu.open = !sidebarMenu.open
48 | // zoomOnPush: expanded
49 | // }
50 |
51 | SidebarViewButton {
52 | appletIconName: "view-tilesonly"
53 | text: i18n("Tiles Only")
54 | onClicked: searchView.showTilesOnly()
55 | checked: searchView.showingOnlyTiles
56 | visible: checked || plasmoid.configuration.defaultAppListView == 'TilesOnly'
57 | }
58 | SidebarViewButton {
59 | appletIconName: "view-list-alphabetically"
60 | text: i18n("Alphabetical")
61 | onClicked: appsView.showAppsAlphabetically()
62 | checked: searchView.showingAppsAlphabetically
63 | }
64 | SidebarViewButton {
65 | appletIconName: 'view-list-categorically'
66 | text: i18n("Categories")
67 | onClicked: appsView.showAppsCategorically()
68 | checked: searchView.showingAppsCategorically
69 | }
70 | // SidebarItem {
71 | // icon.name: 'system-search-symbolic'
72 | // text: i18n("Search")
73 | // onClicked: searchResultsView.showDefaultSearch()
74 | // // checked: stackView.currentItem == searchResultsView
75 | // // checkedEdge: Qt.RightEdge
76 | // // checkedEdgeWidth: 4 * Screen.devicePixelRatio // Twice as thick as normal
77 | // }
78 | }
79 | ColumnLayout {
80 | anchors.bottom: parent.bottom
81 | spacing: 0
82 |
83 | SidebarItem {
84 | id: userMenuButton
85 | icon.name: kuser.hasFaceIcon ? kuser.faceIconUrl : 'user-identity'
86 | text: kuser.fullName
87 | onClicked: {
88 | userMenu.toggleOpen()
89 | }
90 | SidebarContextMenu {
91 | id: userMenu
92 | visualParent: userMenuButton
93 | model: appsModel.sessionActionsModel
94 |
95 | PlasmaExtras.MenuItem {
96 | icon: 'system-users'
97 | text: i18n("User Manager")
98 | onClicked: KCM.KCMLauncher.open('kcm_users')
99 | visible: KConfig.KAuthorized.authorizeControlModule('kcm_users')
100 | }
101 |
102 | // ... appsModel.sessionActionsModel
103 | }
104 | }
105 |
106 | SidebarFavouritesView {
107 | model: appsModel.sidebarModel
108 | maxHeight: sidebarMenu.height - sidebarMenuTop.height - 2 * config.flatButtonSize
109 | }
110 |
111 | SidebarItem {
112 | id: powerMenuButton
113 | icon.name: 'system-shutdown-symbolic'
114 | text: i18n("Power")
115 | onClicked: {
116 | powerMenu.toggleOpen()
117 | }
118 | SidebarContextMenu {
119 | id: powerMenu
120 | visualParent: powerMenuButton
121 | model: appsModel.powerActionsModel
122 | }
123 | }
124 | }
125 |
126 | onFocusChanged: {
127 | logger.debug('searchView.onFocusChanged', focus)
128 | if (!focus) {
129 | open = false
130 | }
131 | }
132 | }
133 |
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/package/contents/ui/SidebarViewButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.kirigami as Kirigami
3 | import org.kde.ksvg as KSvg
4 |
5 | SidebarItem {
6 | id: control
7 |
8 | implicitWidth: config.flatButtonSize
9 |
10 | property string appletIconName: ""
11 | readonly property string appletIconFilename: appletIconName ? Qt.resolvedUrl("../icons/" + appletIconName + ".svg") : ""
12 |
13 | checkedEdge: Qt.LeftEdge
14 | checkedEdgeWidth: 4 * Screen.devicePixelRatio // Twice as thick as normal
15 |
16 | Kirigami.Icon {
17 | id: icon
18 | source: control.appletIconFilename
19 |
20 | isMask: true // Force color
21 | color: control.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
22 |
23 | // From FlatButton.qml, modifed so icon is also 16px
24 | property int iconSize: Kirigami.Units.iconSizes.roundedIconSize(config.flatButtonIconSize)
25 | width: iconSize
26 | height: iconSize
27 | anchors.centerIn: parent
28 |
29 | // Note: Disabled this seems it seems to create a blurry icon after release.
30 | // From FlatButton.qml
31 | // scale: control.zoomOnPush && control.pressed ? (control.height-5) / control.height : 1
32 | // Behavior on scale { NumberAnimation { duration: 200 } }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorColorField.qml:
--------------------------------------------------------------------------------
1 | // Based on LibConfig.ColorField v8
2 | // QQC2.TextField => PlasmaComponents3.TextField
3 |
4 | import QtQuick
5 | import QtQuick.Controls as QQC2
6 | import QtQuick.Dialogs as QtDialogs
7 | import QtQuick.Window
8 | import org.kde.kirigami as Kirigami
9 |
10 | // https://doc.qt.io/qt-6/qtgraphicaleffects5-index.html
11 | import Qt5Compat.GraphicalEffects as QtGraphicalEffects // TODO Deprecated in Qt6
12 |
13 | import org.kde.plasma.components as PlasmaComponents3
14 |
15 | PlasmaComponents3.TextField {
16 | id: colorField
17 | font.family: "monospace"
18 | readonly property string defaultText: "#AARRGGBB"
19 | placeholderText: defaultColor ? defaultColor : defaultText
20 |
21 | onTextChanged: {
22 | // Make sure the text is:
23 | // Empty (use default)
24 | // or #123 or #112233 or #11223344 before applying the color.
25 | if (text.length === 0
26 | || (text.indexOf('#') === 0 && (text.length == 4 || text.length == 7 || text.length == 9))
27 | ) {
28 | colorField.value = text
29 | }
30 | }
31 |
32 | property bool showAlphaChannel: true
33 | property bool showPreviewBg: true
34 |
35 | property string configKey: ''
36 | property string defaultColor: ''
37 | property string value: {
38 | if (configKey) {
39 | return plasmoid.configuration[configKey]
40 | } else {
41 | return "#000"
42 | }
43 | }
44 |
45 | readonly property color defaultColorValue: defaultColor
46 | readonly property color valueColor: {
47 | if (value == '' && defaultColor) {
48 | return defaultColor
49 | } else {
50 | return value
51 | }
52 | }
53 |
54 | onValueChanged: {
55 | if (!activeFocus) {
56 | text = colorField.value
57 | }
58 | if (configKey) {
59 | if (value == defaultColorValue) {
60 | plasmoid.configuration[configKey] = ""
61 | } else {
62 | plasmoid.configuration[configKey] = value
63 | }
64 | }
65 | }
66 |
67 | leftPadding: rightPadding + mouseArea.height + rightPadding
68 |
69 | FontMetrics {
70 | id: fontMetrics
71 | font.family: colorField.font.family
72 | font.italic: colorField.font.italic
73 | font.pointSize: colorField.font.pointSize
74 | font.pixelSize: colorField.font.pixelSize
75 | font.weight: colorField.font.weight
76 | }
77 | readonly property int defaultWidth: Math.ceil(fontMetrics.advanceWidth(defaultText))
78 | implicitWidth: rightPadding + Math.max(defaultWidth, contentWidth) + leftPadding
79 |
80 | MouseArea {
81 | id: mouseArea
82 | anchors.leftMargin: parent.rightPadding
83 | anchors.topMargin: parent.topPadding
84 | anchors.bottomMargin: parent.bottomPadding
85 | anchors.left: parent.left
86 | anchors.top: parent.top
87 | anchors.bottom: parent.bottom
88 | width: height
89 | hoverEnabled: true
90 | cursorShape: Qt.PointingHandCursor
91 |
92 | onClicked: dialogLoader.active = true
93 |
94 | // Color Preview Circle
95 | Rectangle {
96 | id: previewBgMask
97 | visible: false
98 | anchors.fill: parent
99 | border.width: 1 * Screen.devicePixelRatio
100 | border.color: "transparent"
101 | radius: width / 2
102 | }
103 | QtGraphicalEffects.ConicalGradient {
104 | id: previewBgGradient
105 | visible: colorField.showPreviewBg
106 | anchors.fill: parent
107 | angle: 0.0
108 | gradient: Gradient {
109 | GradientStop { position: 0.00; color: "white" }
110 | GradientStop { position: 0.24; color: "white" }
111 | GradientStop { position: 0.25; color: "#cccccc" }
112 | GradientStop { position: 0.49; color: "#cccccc" }
113 | GradientStop { position: 0.50; color: "white" }
114 | GradientStop { position: 0.74; color: "white" }
115 | GradientStop { position: 0.75; color: "#cccccc" }
116 | GradientStop { position: 1.00; color: "#cccccc" }
117 | }
118 | source: previewBgMask
119 | }
120 | Rectangle {
121 | id: previewFill
122 | anchors.fill: parent
123 | color: colorField.valueColor
124 | border.width: 1 * Kirigami.Units.devicePixelRatio
125 | border.color: Kirigami.ColorUtils.linearInterpolation(color, Kirigami.Theme.textColor, 0.5)
126 | radius: width / 2
127 | }
128 | }
129 |
130 | Loader {
131 | id: dialogLoader
132 | active: false
133 | sourceComponent: QtDialogs.ColorDialog {
134 | id: dialog
135 | visible: true
136 | modality: Qt.WindowModal
137 | options: colorField.showAlphaChannel ? QtDialogs.ColorDialog.ShowAlphaChannel : 0
138 | selectedColor: colorField.valueColor
139 | onSelectedColorChanged: {
140 | if (visible) {
141 | colorField.text = selectedColor
142 | }
143 | }
144 | onAccepted: {
145 | colorField.text = selectedColor
146 | dialogLoader.active = false
147 | }
148 | onRejected: {
149 | // This event is also triggered when the user clicks outside the popup modal.
150 | // TODO Find a way to only trigger when Cancel is clicked.
151 | colorField.text = initColor
152 | dialogLoader.active = false
153 | }
154 |
155 | property color initColor
156 | Component.onCompleted: {
157 | initColor = colorField.valueColor
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorColorGroup.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 |
4 | TileEditorGroupBox {
5 | id: tileEditorColorField
6 | title: "Label"
7 | implicitWidth: parent.implicitWidth
8 | Layout.fillWidth: true
9 | property alias placeholderText: colorField.placeholderText
10 | property alias enabled: colorField.enabled
11 | property string key: ''
12 |
13 | TileEditorColorField {
14 | id: colorField
15 | showPreviewBg: false
16 | anchors.left: parent.left
17 | anchors.right: parent.right
18 | text: key && appObj.tile && appObj.tile[key] ? appObj.tile[key] : ''
19 | property bool updateOnChange: false
20 | onTextChanged: {
21 | if (key && updateOnChange) {
22 | if (text) {
23 | appObj.tile[key] = text
24 | } else {
25 | delete appObj.tile[key]
26 | }
27 | appObj.tileChanged()
28 | tileGrid.tileModelChanged()
29 | }
30 | }
31 | }
32 |
33 | Connections {
34 | target: appObj
35 |
36 | function onTileChanged() {
37 | if (key && tile) {
38 | colorField.updateOnChange = false
39 | colorField.text = appObj.tile[key] || ''
40 | colorField.updateOnChange = true
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorField.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | TileEditorGroupBox {
6 | id: tileEditorField
7 | title: "Label"
8 | Layout.fillWidth: true
9 | property alias text: textField.text
10 | property alias placeholderText: textField.placeholderText
11 | property alias enabled: textField.enabled
12 | property string key: ''
13 | property string checkedKey: ''
14 | checkable: checkedKey
15 | property bool checkedDefault: true
16 |
17 | property bool updateOnChange: false
18 | onCheckedChanged: {
19 | if (checkedKey && tileEditorField.updateOnChange) {
20 | appObj.tile[checkedKey] = checked
21 | appObj.tileChanged()
22 | tileGrid.tileModelChanged()
23 | }
24 | }
25 |
26 | default property alias _contentChildren: content.data
27 |
28 | Connections {
29 | target: appObj
30 |
31 | function onTileChanged() {
32 | if (checkedKey && tile) {
33 | tileEditorField.updateOnChange = false
34 | tileEditorField.checked = typeof appObj.tile[checkedKey] !== "undefined" ? appObj.tile[checkedKey] : checkedDefault
35 | tileEditorField.updateOnChange = true
36 | }
37 | }
38 | }
39 |
40 | RowLayout {
41 | id: content
42 | anchors.left: parent.left
43 | anchors.right: parent.right
44 |
45 | PlasmaComponents3.TextField {
46 | id: textField
47 | Layout.fillWidth: true
48 | text: key && appObj.tile && appObj.tile[key] ? appObj.tile[key] : ''
49 | property bool updateOnChange: false
50 | onTextChanged: {
51 | if (key && textField.updateOnChange) {
52 | appObj.tile[key] = text
53 | appObj.tileChanged()
54 | tileGrid.tileModelChanged()
55 | }
56 | }
57 |
58 | Connections {
59 | target: appObj
60 |
61 | function onTileChanged() {
62 | if (key && tile) {
63 | textField.updateOnChange = false
64 | textField.text = appObj.tile[key] || ''
65 | textField.updateOnChange = true
66 | }
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorFileField.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Dialogs as QtDialogs
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | TileEditorField {
6 | id: fileField
7 | property string dialogTitle: ""
8 | signal dialogOpen(var dialog)
9 |
10 | PlasmaComponents3.Button {
11 | icon.name: 'document-open'
12 | onClicked: dialogLoader.active = true
13 |
14 | Loader {
15 | id: dialogLoader
16 | active: false
17 | sourceComponent: QtDialogs.FileDialog {
18 | id: dialog
19 | visible: false
20 | modality: Qt.WindowModal
21 | onAccepted: {
22 | fileField.text = selectedFile
23 | dialogLoader.active = false // visible=false is called before onAccepted
24 | }
25 | onRejected: {
26 | dialogLoader.active = false // visible=false is called before onRejected
27 | }
28 |
29 | // nameFilters must be set before opening the dialog.
30 | // If we create the dialog with visible=true, the nameFilters
31 | // will not be set before it opens.
32 | Component.onCompleted: {
33 | fileField.dialogOpen(dialog)
34 | visible = true
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorGroupBox.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | // https://invent.kde.org/frameworks/plasma-framework/-/blame/master/src/declarativeimports/plasmacomponents3/GroupBox.qml
6 | PlasmaComponents3.GroupBox {
7 | id: control
8 | property bool checkable: false
9 | property bool checked: false
10 |
11 | label: RowLayout {
12 | x: control.leftPadding
13 | y: control.topInset
14 | width: control.availableWidth
15 |
16 | Loader {
17 | id: checkBoxLoader
18 | active: control.checkable
19 | sourceComponent: PlasmaComponents3.CheckBox {
20 | id: checkBox
21 | Layout.fillWidth: true
22 | enabled: control.enabled
23 | text: control.title
24 | checked: control.checked
25 | onCheckedChanged: control.checked = checked
26 | }
27 | }
28 | PlasmaComponents3.Label {
29 | Layout.fillWidth: true
30 | enabled: control.enabled
31 | visible: !control.checkable
32 |
33 | text: control.title
34 | font: control.font
35 | // color: SystemPaletteSingleton.text(control.enabled) // TODO: Fix Label color upstream
36 | elide: Text.ElideRight
37 | horizontalAlignment: Text.AlignLeft
38 | verticalAlignment: Text.AlignVCenter
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorPresetTileButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 |
4 | Item {
5 | id: presetTileButton
6 | Layout.fillWidth: parent.width
7 | Layout.preferredHeight: image.paintedHeight
8 |
9 | visible: source
10 | property alias source: image.source
11 | property string filename: 'temp.jpg'
12 | property int w: 0
13 | property int h: 0
14 |
15 | Image {
16 | id: image
17 | anchors.centerIn: parent
18 | width: Math.min(parent.width, sourceSize.width)
19 |
20 | fillMode: Image.PreserveAspectFit
21 | }
22 |
23 | HoverOutlineEffect {
24 | id: hoverOutlineEffect
25 | anchors.fill: image
26 | hoverRadius: Math.min(width, height)
27 | property alias control: mouseArea
28 | }
29 |
30 | MouseArea {
31 | id: mouseArea
32 | anchors.fill: image
33 | hoverEnabled: true
34 | acceptedButtons: Qt.LeftButton
35 | cursorShape: Qt.ArrowCursor
36 |
37 | onClicked: presetTileButton.select()
38 | }
39 |
40 | function getDownloadDir() {
41 | // plasmoid.downloadPath() will create this folder.
42 | // ~/Downloads/Plasma/com.github.zren.tiledmenu/
43 | // I litters the Downloads folder... which isn't ideal.
44 | return plasmoid.downloadPath()
45 |
46 | // TODO: Download to ~/.local/share since it's hidden.
47 | // Note, this folder does not exist! So we need to create it somehow.
48 | // Maybe we could run `mkdir -p /path/to/folder` using the exec dataengine.
49 |
50 | // Requires: import Qt.labs.platform 1.0
51 | // ~/.local/share/
52 | // var localDownloadDir = StandardPaths.writableLocation(StandardPaths.GenericDataLocation)
53 | // console.log('localDownloadDir', localDownloadDir)
54 |
55 | // Remove file:// URL scheme
56 | // localFilepath = localDownloadDir.substr('file://'.length)
57 |
58 | // ~/.local/share/plasma_com.github.zren.tiledmenu
59 | // var tiledMenuDir = localDownloadDir + '/' + 'plasma_' + plasmoid.pluginName
60 | // console.log('tiledMenuDir', tiledMenuDir)
61 | // return tiledMenuDir
62 | }
63 |
64 | function resizeTile() {
65 | var sizeChanged = false
66 | if (presetTileButton.w > 0) {
67 | appObj.tile.w = presetTileButton.w
68 | sizeChanged = true
69 | }
70 | if (presetTileButton.h > 0) {
71 | appObj.tile.h = presetTileButton.h
72 | sizeChanged = true
73 | }
74 | if (sizeChanged) {
75 | appObj.tileChanged()
76 | tileGrid.tileModelChanged()
77 | }
78 | }
79 |
80 | function setTileBackgroundImage(filepath) {
81 | backgroundImageField.text = filepath
82 | labelField.checked = false
83 | iconField.checked = false
84 | }
85 |
86 | function select() {
87 | logger.debug('select', source)
88 |
89 | var sourceFilepath = '' + source // cast to string
90 | var isLocalFilepath = sourceFilepath.indexOf('file://') == 0 || sourceFilepath.indexOf('/') == 0
91 | if (isLocalFilepath) {
92 | presetTileButton.setTileBackgroundImage(source)
93 | presetTileButton.resizeTile()
94 | } else {
95 | var tiledMenuDir = getDownloadDir()
96 | var localFilepath = tiledMenuDir + filename
97 | logger.debug('localFilepath', localFilepath)
98 |
99 | // Save tile image to file
100 | logger.debug('grabToImage.start')
101 | image.grabToImage(function(result){
102 | logger.debug('grabToImage.done', result, result.url)
103 | result.saveToFile(localFilepath)
104 | presetTileButton.setTileBackgroundImage(localFilepath)
105 | presetTileButton.resizeTile()
106 | }, image.sourceSize)
107 | }
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorPresetTiles.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 |
4 | import "lib/Requests.js" as Requests
5 |
6 | // Note: This references a global KCoreAddons.KUser { id: kuser }
7 |
8 | TileEditorGroupBox {
9 | id: tileEditorPresetTiles
10 | title: "Label"
11 | Layout.fillWidth: true
12 |
13 | visible: false
14 | function checkForPreset() {
15 | var visiblePresets = 0
16 | for (var i = 0; i < content.children.length; i++) {
17 | var item = content.children[i]
18 | var hasImageUrl = item.source && item.source.toString()
19 | if (hasImageUrl) {
20 | visiblePresets += 1
21 | }
22 | }
23 | visible = visiblePresets > 0
24 | }
25 | Component.onCompleted: {
26 | checkIfRecognizedLauncher()
27 | }
28 |
29 | readonly property bool isDesktopFile: endsWith(appObj.appUrl, '.desktop')
30 | property string steamGameId: ''
31 | readonly property bool isSteamGameLauncher: !!steamGameId
32 | property string lutrisGameSlug: ''
33 | readonly property bool isLutrisGameLauncher: !!lutrisGameSlug
34 |
35 | function endsWith(s, substr) {
36 | return s.indexOf(substr) == s.length - substr.length
37 | }
38 |
39 | function resetRecognizedLaunchers() {
40 | tileEditorPresetTiles.steamGameId = ''
41 | tileEditorPresetTiles.lutrisGameSlug = ''
42 | }
43 |
44 | function checkIfRecognizedLauncher() {
45 | // console.log('checkIfRecognizedLauncher', appObj.appUrl)
46 |
47 | resetRecognizedLaunchers()
48 | checkForPreset()
49 |
50 | if (!appObj.appUrl) {
51 | return
52 | }
53 |
54 | if (!isDesktopFile) {
55 | return
56 | }
57 |
58 | // Qt 5.15+ warns that XHR on local file will be removed.
59 | // Requests.getFile(appObj.appUrl, function(err, data) {
60 | // if (err) {
61 | // console.log('[tiledmenu] checkIfRecognizedLauncher.err', err)
62 | // return
63 | // }
64 |
65 | // var desktopFile = Requests.parseMetadata(data)
66 | // checkIfSteamLauncher(desktopFile)
67 | // checkIfLutrisLauncher(desktopFile)
68 |
69 | // tileEditorPresetTiles.checkForPreset()
70 | // })
71 |
72 | checkIfSteamIcon(appObj.iconSource)
73 | appObj.iconSourceChanged.connect(function(){
74 | tileEditorPresetTiles.checkIfSteamIcon(appObj.iconSource)
75 | tileEditorPresetTiles.checkIfLutrisIcon(appObj.iconSource)
76 | tileEditorPresetTiles.checkForPreset()
77 | })
78 |
79 | // Lutris does not use game id in icon name. Eg: lutris_overwatch instead of lutris_game_1
80 | }
81 |
82 | function checkIfSteamIcon(iconSource) {
83 | var steamIconRegex = /steam_icon_(\d+)/
84 | var m = steamIconRegex.exec(iconSource)
85 | if (m) {
86 | tileEditorPresetTiles.steamGameId = m[1]
87 | } else {
88 | tileEditorPresetTiles.steamGameId = '' // Reset
89 | }
90 | }
91 |
92 | function checkIfLutrisIcon(iconSource) {
93 | var lutrisIconRegex = /lutris_([\w\-]+)/
94 | var m = lutrisIconRegex.exec(iconSource)
95 | if (m) {
96 | tileEditorPresetTiles.lutrisGameSlug = m[1]
97 | } else {
98 | tileEditorPresetTiles.lutrisGameSlug = '' // Reset
99 | }
100 | }
101 |
102 | function checkIfSteamLauncher(desktopFile) {
103 | var steamCommandRegex = /steam steam:\/\/rungameid\/(\d+)/
104 | var m = steamCommandRegex.exec(desktopFile['Exec'])
105 | if (m) {
106 | tileEditorPresetTiles.steamGameId = m[1]
107 | } else {
108 | tileEditorPresetTiles.steamGameId = '' // Reset
109 | }
110 | }
111 |
112 | function checkIfLutrisLauncher(desktopFile) {
113 | var lutrisCommandRegex = /lutris lutris:rungameid\/(\d+)/
114 | var m1 = lutrisCommandRegex.exec(desktopFile['Exec'])
115 | var lutrisIconRegex = /^lutris_(.+)$/
116 | var m2 = lutrisIconRegex.exec(desktopFile['Icon'])
117 | if (m1 && m2) {
118 | tileEditorPresetTiles.lutrisGameSlug = m2[1]
119 | } else {
120 | tileEditorPresetTiles.lutrisGameSlug = '' // Reset
121 | }
122 | }
123 |
124 | Connections {
125 | target: appObj
126 |
127 | function onAppUrlChanged() {
128 | logger.debug('onAppUrlChanged', appObj.appUrl)
129 | tileEditorPresetTiles.checkIfRecognizedLauncher()
130 | }
131 | }
132 |
133 | GridLayout {
134 | id: content
135 | anchors.left: parent.left
136 | anchors.right: parent.right
137 | columns: 2
138 |
139 | //--- Steam
140 | // 4x2
141 | TileEditorPresetTileButton {
142 | filename: 'steam_' + steamGameId + '_4x2.jpg'
143 | property string tileImageUrl: 'https://steamcdn-a.akamaihd.net/steam/apps/' + steamGameId + '/header.jpg'
144 | source: isSteamGameLauncher ? tileImageUrl : ''
145 | w: 4
146 | h: 2
147 | }
148 |
149 | // 3x1
150 | TileEditorPresetTileButton {
151 | filename: 'steam_' + steamGameId + '_3x1.jpg'
152 | property string tileImageUrl: 'https://steamcdn-a.akamaihd.net/steam/apps/' + steamGameId + '/capsule_184x69.jpg'
153 | source: isSteamGameLauncher ? tileImageUrl : ''
154 | w: 3
155 | h: 1
156 | }
157 |
158 | // 5x3 or 3x2
159 | TileEditorPresetTileButton {
160 | filename: 'steam_' + steamGameId + '_5x3.jpg'
161 | property string tileImageUrl: 'https://steamcdn-a.akamaihd.net/steam/apps/' + steamGameId + '/capsule_616x353.jpg'
162 | source: isSteamGameLauncher ? tileImageUrl : ''
163 | w: 3
164 | h: 2
165 | }
166 |
167 | // 5x2 or 2x1
168 | TileEditorPresetTileButton {
169 | filename: 'lutris_' + lutrisGameSlug + '_2x1.jpg'
170 | // property string tileImageUrl: '/home/' + kuser.loginName + '/.local/share/lutris/banners/' + lutrisGameSlug + '.jpg'
171 | // source: (isLutrisGameLauncher && kuser.loginName) ? tileImageUrl : ''
172 | property string tileImageUrl: 'https://lutris.net/games/banner/' + lutrisGameSlug + '.jpg'
173 | source: (isLutrisGameLauncher) ? tileImageUrl : ''
174 | w: 2
175 | h: 1
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorRectField.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | TileEditorGroupBox {
6 | id: tileEditorRectField
7 | title: "Label"
8 | implicitWidth: parent.implicitWidth
9 | Layout.fillWidth: true
10 |
11 | // readonly property int xLeft: tileGrid.columns - (appObj.tileX + appObj.tileW)
12 |
13 | RowLayout {
14 | anchors.fill: parent
15 |
16 | GridLayout {
17 | columns: 2
18 | Layout.fillWidth: true
19 |
20 | PlasmaComponents3.Label { text: "x:" }
21 | TileEditorSpinBox {
22 | key: 'x'
23 | from: 0
24 | // to: tileGrid.columns - (appObj.tile && appObj.tile.w-1 || 0)
25 | // to: appObj.tileX + tileEditorRectField.xLeft
26 | }
27 | PlasmaComponents3.Label { text: "y:" }
28 | TileEditorSpinBox {
29 | key: 'y'
30 | from: 0
31 | }
32 | PlasmaComponents3.Label { text: "w:" }
33 | TileEditorSpinBox {
34 | key: 'w'
35 | from: 1
36 | // to: tileGrid.columns - (appObj.tile && appObj.tile.x || 0)
37 | // to: appObj.tileW + tileEditorRectField.xLeft
38 | }
39 | PlasmaComponents3.Label { text: "h:" }
40 | TileEditorSpinBox {
41 | key: 'h'
42 | from: 1
43 | }
44 | }
45 |
46 | GridLayout {
47 | id: resizeGrid
48 | Layout.fillWidth: true
49 | rows: 4
50 | columns: 4
51 |
52 | Repeater {
53 | model: resizeGrid.rows * resizeGrid.columns
54 |
55 | PlasmaComponents3.Button {
56 | Layout.fillWidth: true
57 | implicitWidth: 20
58 | property int w: (modelData % resizeGrid.columns) + 1
59 | property int h: Math.floor(modelData / resizeGrid.columns) + 1
60 | text: '' + w + 'x' + h
61 | checked: w <= appObj.tileW && h <= appObj.tileH
62 | // enabled: w - appObj.tileW <= tileEditorRectField.xLeft
63 | onClicked: {
64 | appObj.tile.w = w
65 | appObj.tile.h = h
66 | appObj.tileChanged()
67 | tileGrid.tileModelChanged()
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorSpinBox.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.plasma.components as PlasmaComponents3
4 |
5 | PlasmaComponents3.SpinBox {
6 | id: spinBox
7 | property string key: ''
8 | Layout.fillWidth: true
9 | implicitWidth: 20
10 | value: appObj.tile && appObj.tile[key] || 0
11 | property bool updateOnChange: false
12 | onValueChanged: {
13 | if (key && updateOnChange) {
14 | appObj.tile[key] = value
15 | appObj.tileChanged()
16 | tileGrid.tileModelChanged()
17 | }
18 | }
19 |
20 | Connections {
21 | target: appObj
22 |
23 | function onTileChanged() {
24 | if (key && tile) {
25 | spinBox.updateOnChange = false
26 | spinBox.value = appObj.tile[key] || 0
27 | spinBox.updateOnChange = true
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/package/contents/ui/TileEditorView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import QtQuick.Dialogs as QtDialogs
4 | import org.kde.plasma.components as PlasmaComponents3
5 | import org.kde.plasma.extras as PlasmaExtras
6 | import org.kde.iconthemes as KIconThemes // IconDialog
7 |
8 | ColumnLayout {
9 | id: tileEditorView
10 | Layout.alignment: Qt.AlignTop
11 |
12 | AppObject {
13 | id: appObj
14 | }
15 | property alias tile: appObj.tile
16 |
17 | function resetView() {
18 | tile = null
19 | }
20 |
21 | function resetTile() {
22 | delete appObj.tile.showIcon
23 | delete appObj.tile.showLabel
24 | delete appObj.tile.label
25 | delete appObj.tile.icon
26 | delete appObj.tile.backgroundColor
27 | delete appObj.tile.backgroundImage
28 | appObj.tileChanged()
29 | tileGrid.tileModelChanged()
30 | }
31 |
32 |
33 | RowLayout {
34 | PlasmaExtras.Heading {
35 | Layout.fillWidth: true
36 | level: 2
37 | text: i18n("Edit Tile")
38 | }
39 |
40 | PlasmaComponents3.Button {
41 | text: i18n("Reset Tile")
42 | onClicked: resetTile()
43 | }
44 |
45 | PlasmaComponents3.Button {
46 | text: i18n("Close")
47 | onClicked: {
48 | tileEditorView.close()
49 | }
50 | }
51 | }
52 |
53 |
54 | PlasmaComponents3.ScrollView {
55 | id: scrollView
56 | Layout.fillHeight: true
57 | Layout.fillWidth: true
58 |
59 | ColumnLayout {
60 | id: scrollContent
61 | Layout.fillWidth: true
62 | width: scrollView.availableWidth
63 |
64 | TileEditorField {
65 | // visible: appObj.isLauncher
66 | title: i18n("Url")
67 | key: 'url'
68 | }
69 |
70 | TileEditorField {
71 | id: labelField
72 | title: i18n("Label")
73 | placeholderText: appObj.appLabel
74 | key: 'label'
75 | checkedKey: 'showLabel'
76 | }
77 |
78 | TileEditorField {
79 | id: iconField
80 | title: i18n("Icon")
81 | // placeholderText: appObj.appIcon ? appObj.appIcon.toString() : ''
82 | key: 'icon'
83 | checkedKey: 'showIcon'
84 | checkedDefault: appObj.defaultShowIcon
85 |
86 | PlasmaComponents3.Button {
87 | icon.name: "document-open"
88 | onClicked: iconDialog.open()
89 |
90 | KIconThemes.IconDialog {
91 | id: iconDialog
92 | onIconNameChanged: iconField.text = iconName
93 | }
94 | }
95 | }
96 |
97 | TileEditorFileField {
98 | id: backgroundImageField
99 | title: i18n("Background Image")
100 | key: 'backgroundImage'
101 | onTextChanged: {
102 | if (text) {
103 | labelField.checked = false
104 | iconField.checked = false
105 | }
106 | }
107 | onDialogOpen: function(dialog) {
108 | dialog.title = i18n("Choose an image")
109 | dialog.nameFilters.unshift(i18n("Image Files (*.png *.jpg *.jpeg *.bmp *.svg *.svgz)"))
110 | }
111 | }
112 |
113 | TileEditorPresetTiles {
114 | title: i18n("Preset Tiles")
115 | }
116 |
117 | TileEditorColorGroup {
118 | title: i18n("Background Color")
119 | placeholderText: config.defaultTileColor
120 | key: 'backgroundColor'
121 | }
122 |
123 | TileEditorRectField {
124 | title: i18n("Position / Size")
125 | }
126 |
127 | Item { // Consume the extra space below
128 | Layout.fillHeight: true
129 | }
130 | }
131 | }
132 |
133 | function show() {
134 | if (stackView.currentItem != tileEditorView) {
135 | stackView.replace(tileEditorView)
136 | }
137 | }
138 |
139 | function open(tile) {
140 | resetView()
141 | tileEditorView.tile = tile
142 | show()
143 | }
144 |
145 | function close() {
146 | searchView.showDefaultView()
147 | }
148 |
149 |
150 | Connections {
151 | target: stackView
152 |
153 | function onCurrentItemChanged() {
154 | if (stackView.currentItem != tileEditorView) {
155 | tileEditorView.resetView()
156 | }
157 | }
158 | }
159 |
160 |
161 | Connections {
162 | target: config.tileModel
163 |
164 | function onLoaded() {
165 | // Base64JsonString.save() will create a new JavaScript array [],
166 | // and our current tile {} reference will be incorrect, which breaks the tile editor.
167 | // We could keep a reference to the tile's index in the array, and make sure
168 | // the tile's url did not change, but there's no guarantee we won't overwrite data
169 | // during an Import, so just close the view.
170 | tileEditorView.close()
171 | }
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/package/contents/ui/TileGridPresets.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.extras as PlasmaExtras
3 |
4 | PlasmaExtras.MenuItem {
5 | id: presetMenuItem
6 | icon: "list-add-symbolic"
7 | text: i18n("Add Preset")
8 |
9 | //---
10 | function addDefault() {
11 | var pos = tileGrid.findOpenPos(6, 6)
12 | addProductivity(pos.x, pos.y)
13 | addExplore(pos.x, pos.y + 3)
14 | }
15 |
16 | function isAppInstalled(appId) {
17 | return appsModel.allAppsModel.hasApp(appId)
18 | }
19 |
20 | function addTilePreset(x, y, tileData) {
21 | var appId = tileData.url
22 | if (isAppInstalled(appId)) {
23 | return tileGrid.addTile(x, y, tileData)
24 | } else {
25 | return null
26 | }
27 | }
28 |
29 | function addGroupPreset(x, y, groupData, tileFnList) {
30 | var group = tileGrid.addGroup(x, y, groupData)
31 | var tileX = group.x
32 | var tileY = y + group.h
33 | for (var i = 0; i < tileFnList.length; i++) {
34 | var tileFn = tileFnList[i]
35 | var tile = tileFn(tileX, tileY)
36 | if (tile) {
37 | // TODO: Support Wrap
38 | tileX += tile.w
39 | }
40 | }
41 | }
42 |
43 | function addProductivity(x, y) {
44 | addGroupPreset(x, y, {
45 | label: i18n("Productivity"),
46 | }, [
47 | addWriter,
48 | addCalc,
49 | addMail,
50 | ])
51 | }
52 |
53 | function addExplore(x, y) {
54 | addGroupPreset(x, y, {
55 | label: i18n("Explore"),
56 | }, [
57 | addAppCenter,
58 | addWebBrowser,
59 | addSteam,
60 | ])
61 | }
62 |
63 |
64 | //---
65 | function addWriter(x, y) {
66 | return addTilePreset(x, y, {
67 | url: 'libreoffice-writer.desktop',
68 | backgroundColor: '#802584b7',
69 | })
70 | }
71 | function addCalc(x, y) {
72 | return addTilePreset(x, y, {
73 | url: 'libreoffice-calc.desktop',
74 | backgroundColor: '#80289769',
75 | })
76 | }
77 | function addMail(x, y) {
78 | var tile = addKMail(x, y)
79 | if (!tile) {
80 | tile = addGmail(x, y)
81 | }
82 | return tile
83 | }
84 | function addKMail(x, y) {
85 | return addTilePreset(x, y, {
86 | url: 'org.kde.kmail2.desktop',
87 | })
88 | }
89 | function addGmail(x, y) {
90 | return tileGrid.addTile(x, y, {
91 | url: 'https://mail.google.com/mail/u/0/#inbox',
92 | label: i18n("Gmail"),
93 | icon: 'mail-message',
94 | backgroundColor: '#80a73325',
95 | })
96 | }
97 |
98 | function addAppCenter(x, y) {
99 | if (isAppInstalled('octopi.desktop')) {
100 | return tileGrid.addTile(x, y, {
101 | url: 'octopi.desktop',
102 | label: i18n("Software Center"),
103 | })
104 | } else if (isAppInstalled('org.manjaro.pamac.manager.desktop')) {
105 | return tileGrid.addTile(x, y, {
106 | url: 'org.manjaro.pamac.manager.desktop',
107 | // default label is 'Add/Remove Software'
108 | })
109 | } else if (isAppInstalled('org.opensuse.yast.Packager.desktop')) {
110 | return tileGrid.addTile(x, y, {
111 | url: 'org.opensuse.yast.Packager.desktop',
112 | label: i18n("Software Center"),
113 | })
114 | } else if (isAppInstalled('org.kde.discover')) {
115 | return tileGrid.addTile(x, y, {
116 | url: 'org.kde.discover',
117 | label: i18n("Software Center"),
118 | })
119 | } else {
120 | return null
121 | }
122 | }
123 |
124 | function addWebBrowser(x, y) {
125 | return tileGrid.addTile(x, y, {
126 | url: 'preferred://browser',
127 | })
128 | }
129 |
130 | function addSteam(x, y) {
131 | return addTilePreset(x, y, {
132 | url: 'steam.desktop',
133 | })
134 | }
135 |
136 |
137 | //---
138 | readonly property var presetSubMenu: PlasmaExtras.Menu {
139 | visualParent: presetMenuItem.action
140 |
141 | PlasmaExtras.MenuItem {
142 | icon: "libreoffice-startcenter"
143 | text: i18n("Productivity")
144 | onClicked: {
145 | var pos = tileGrid.findOpenPos(6, 3)
146 | presetMenuItem.addProductivity(pos.x, pos.y)
147 | }
148 | }
149 |
150 | PlasmaExtras.MenuItem {
151 | icon: "internet-web-browser"
152 | text: i18n("Explore")
153 | onClicked: {
154 | var pos = tileGrid.findOpenPos(6, 3)
155 | presetMenuItem.addExplore(pos.x, pos.y)
156 | }
157 | }
158 |
159 | PlasmaExtras.MenuItem {
160 | icon: "mail-message"
161 | text: i18n("Gmail")
162 | onClicked: {
163 | var tile = presetMenuItem.addGmail(cellContextMenu.cellX, cellContextMenu.cellY)
164 | tileGrid.editTile(tile)
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/package/contents/ui/TileGridSplash.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 | import org.kde.kirigami as Kirigami
4 | import org.kde.plasma.components as PlasmaComponents3
5 | import org.kde.plasma.extras as PlasmaExtras
6 |
7 | ColumnLayout {
8 | // inherits tileGridPresets from Loader
9 | // inherits maxWidth from Loader
10 |
11 | spacing: Kirigami.Units.largeSpacing
12 |
13 | PlasmaExtras.Heading {
14 | Layout.alignment: Qt.AlignHCenter
15 | Layout.maximumWidth: maxWidth
16 | wrapMode: Text.Wrap
17 | horizontalAlignment: Text.AlignHCenter
18 | text: i18n("Getting Started")
19 | }
20 | PlasmaComponents3.Label {
21 | Layout.alignment: Qt.AlignHCenter
22 | Layout.maximumWidth: maxWidth
23 | wrapMode: Text.Wrap
24 |
25 | text: {
26 | var tips = [
27 | i18n("Drag apps onto the grid."),
28 | i18n("Drag folders from the file manager here."),
29 | i18n("Meta + Right Click to resize the menu."),
30 | ]
31 | var str = ''
32 | for (var i = 0; i < tips.length; i++) {
33 | var tip = tips[i]
34 | str += '- ' + tip + '
'
35 | }
36 | str += '
'
37 | return str
38 | }
39 | }
40 |
41 | PlasmaComponents3.Button {
42 | Layout.alignment: Qt.AlignHCenter
43 | text: i18n("Use Default Tile Layout")
44 | onClicked: tileGridPresets.addDefault()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/package/contents/ui/TileItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 | import org.kde.plasma.core as PlasmaCore
4 |
5 | Item {
6 | id: tileItem
7 | x: modelData.x * cellBoxSize
8 | y: modelData.y * cellBoxSize
9 | width: modelData.w * cellBoxSize
10 | height: modelData.h * cellBoxSize
11 |
12 | function fixCoordinateBindings() {
13 | x = Qt.binding(function(){ return modelData.x * cellBoxSize })
14 | y = Qt.binding(function(){ return modelData.y * cellBoxSize })
15 | z = 0
16 | }
17 |
18 | AppObject {
19 | id: appObj
20 | tile: modelData
21 | }
22 | readonly property alias app: appObj.app
23 |
24 | readonly property bool faded: tileGrid.editing || tileMouseArea.isLeftPressed
25 | readonly property int fadedWidth: width - cellPushedMargin
26 | opacity: faded ? 0.75 : 1
27 | scale: faded ? fadedWidth / width : 1
28 | Behavior on opacity { NumberAnimation { duration: 200 } }
29 | Behavior on scale { NumberAnimation { duration: 200 } }
30 |
31 | //--- View Start
32 | TileItemView {
33 | id: tileItemView
34 | anchors.fill: parent
35 | anchors.margins: cellMargin
36 | width: modelData.w * cellBoxSize
37 | height: modelData.h * cellBoxSize
38 | readonly property int minSize: Math.min(width, height)
39 | readonly property int maxSize: Math.max(width, height)
40 | hovered: tileMouseArea.containsMouse
41 | }
42 |
43 | HoverOutlineEffect {
44 | id: hoverOutlineEffect
45 | anchors.fill: parent
46 | anchors.margins: cellMargin
47 | hoverRadius: {
48 | if (appObj.isGroup) {
49 | return tileItemView.maxSize
50 | } else {
51 | return tileItemView.minSize
52 | }
53 | }
54 | hoverOutlineSize: tileGrid.hoverOutlineSize
55 | mouseArea: tileMouseArea
56 | }
57 | //--- View End
58 |
59 | MouseArea {
60 | id: tileMouseArea
61 | anchors.fill: parent
62 | hoverEnabled: true
63 | acceptedButtons: Qt.LeftButton | Qt.RightButton
64 | cursorShape: editing ? Qt.ClosedHandCursor : Qt.ArrowCursor
65 | readonly property bool isLeftPressed: pressedButtons & Qt.LeftButton
66 |
67 | property int pressX: -1
68 | property int pressY: -1
69 | onPressed: function(mouse) {
70 | pressX = mouse.x
71 | pressY = mouse.y
72 | }
73 |
74 | drag.target: plasmoid.configuration.tilesLocked ? undefined : tileItem
75 | // drag.onActiveChanged: console.log('drag.active', drag.active)
76 |
77 | // This MouseArea will spam "QQuickItem::ungrabMouse(): Item is not the mouse grabber."
78 | // but there's no other way of having a clickable drag area.
79 | onClicked: function(mouse) {
80 | mouse.accepted = true
81 | tileGrid.resetDrag()
82 | if (mouse.button == Qt.LeftButton) {
83 | if (tileEditorView && tileEditorView.tile) {
84 | openTileEditor()
85 | } else if (modelData.url) {
86 | appsModel.tileGridModel.runApp(modelData.url)
87 | }
88 | } else if (mouse.button == Qt.RightButton) {
89 | contextMenu.open(mouse.x, mouse.y)
90 | }
91 | }
92 | }
93 |
94 | Drag.dragType: Drag.Automatic
95 | Drag.proposedAction: Qt.MoveAction
96 |
97 | // We use this drag pattern to use the internal drag with events.
98 | // https://stackoverflow.com/a/24729837/947742
99 | readonly property bool dragActive: tileMouseArea.drag.active
100 | onDragActiveChanged: function(dragActive) {
101 | if (dragActive) {
102 | // console.log("drag started")
103 | // console.log('onDragStarted', JSON.stringify(modelData), index, tileModel.length)
104 | tileGrid.startDrag(index)
105 | // tileGrid.dropOffsetX = 0
106 | // tileGrid.dropOffsetY = 0
107 | tileItem.z = 1
108 | Drag.start()
109 | } else {
110 | // console.log("drag finished")
111 | // console.log('DragArea.onDrop', draggedItem)
112 | Qt.callLater(tileGrid.resetDrag)
113 | Qt.callLater(tileItem.fixCoordinateBindings)
114 | Drag.drop() // Breaks QML context.
115 | // We need to use callLater to call functions after Drag.drop().
116 | }
117 | }
118 |
119 | QQC2.ToolTip {
120 | id: control
121 | visible: tileItemView.hovered && !(dragActive || contextMenu.opened) && appObj.tile.w == 1 && appObj.tile.h == 1
122 | text: appObj.labelText
123 | delay: 0
124 | x: parent.width + rightPadding
125 | y: (parent.height - height) / 2
126 | }
127 |
128 | Loader {
129 | id: groupEffectLoader
130 | visible: tileMouseArea.containsMouse
131 | active: appObj.isGroup && visible
132 | sourceComponent: Rectangle {
133 | id: groupOutline
134 | color: "transparent"
135 | border.width: Math.max(1, Math.round(1 * Screen.devicePixelRatio))
136 | border.color: "#80ffffff"
137 | y: modelData.h * cellBoxSize
138 | z: 100
139 | width: appObj.groupRect.w * cellBoxSize
140 | height: appObj.groupRect.h * cellBoxSize
141 | }
142 | }
143 |
144 | AppContextMenu {
145 | id: contextMenu
146 | tileIndex: index
147 | onPopulateMenu: function(menu) {
148 | if (!plasmoid.configuration.tilesLocked) {
149 | menu.addPinToMenuAction(modelData.url)
150 | }
151 |
152 | appObj.addActionList(menu)
153 |
154 | if (!plasmoid.configuration.tilesLocked) {
155 | if (modelData.tileType == "group") {
156 | var menuItem = menu.newMenuItem()
157 | menuItem.text = i18n("Sort Tiles")
158 | menuItem.icon = 'sort-name'
159 | menuItem.onClicked.connect(function(){
160 | tileGrid.sortGroupTiles(modelData)
161 | })
162 | }
163 | var menuItem = menu.newMenuItem()
164 | menuItem.text = i18n("Edit Tile")
165 | menuItem.icon = 'rectangle-shape'
166 | menuItem.onClicked.connect(function(){
167 | tileItem.openTileEditor()
168 | })
169 | }
170 | }
171 | }
172 |
173 | function openTileEditor() {
174 | tileGrid.editTile(tileGrid.tileModel[index])
175 | }
176 | function closeTileEditor() {
177 |
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/package/contents/ui/TileItemView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import org.kde.plasma.components as PlasmaComponents3
3 | import org.kde.kirigami as Kirigami
4 |
5 | Rectangle {
6 | id: tileItemView
7 | color: appObj.backgroundColor
8 | property color gradientBottomColor: Qt.darker(appObj.backgroundColor, 2.0)
9 |
10 | Component {
11 | id: tileGradient
12 | Gradient {
13 | GradientStop { position: 0.0; color: appObj.backgroundColor }
14 | GradientStop { position: 1.0; color: tileItemView.gradientBottomColor }
15 | }
16 | }
17 | gradient: appObj.backgroundGradient ? tileGradient.createObject(tileItemView) : null
18 |
19 | readonly property int tilePadding: 4 * Screen.devicePixelRatio
20 | readonly property int smallIconSize: 32 * Screen.devicePixelRatio
21 | readonly property int mediumIconSize: 72 * Screen.devicePixelRatio
22 | readonly property int largeIconSize: 96 * Screen.devicePixelRatio
23 |
24 | readonly property int labelAlignment: appObj.isGroup ? config.groupLabelAlignment : config.tileLabelAlignment
25 |
26 | property bool hovered: false
27 |
28 | states: [
29 | State {
30 | when: modelData.w == 1 && modelData.h >= 1
31 | PropertyChanges { target: icon; size: smallIconSize }
32 | PropertyChanges { target: label; visible: false }
33 | },
34 | State {
35 | when: modelData.w >= 2 && modelData.h == 1
36 | AnchorChanges { target: icon
37 | anchors.horizontalCenter: undefined
38 | anchors.left: tileItemView.left
39 | }
40 | PropertyChanges { target: icon; anchors.leftMargin: tilePadding }
41 | PropertyChanges { target: label
42 | verticalAlignment: Text.AlignVCenter
43 | }
44 | AnchorChanges { target: label
45 | anchors.left: icon.right
46 | }
47 | },
48 | State {
49 | when: (modelData.w >= 2 && modelData.h == 2) || (modelData.w == 2 && modelData.h >= 2)
50 | PropertyChanges { target: icon; size: mediumIconSize }
51 | },
52 | State {
53 | when: modelData.w >= 3 && modelData.h >= 3
54 | PropertyChanges { target: icon; size: largeIconSize }
55 | }
56 | ]
57 |
58 | Image {
59 | id: backgroundImage
60 | anchors.fill: parent
61 | visible: appObj.backgroundImage
62 | source: appObj.backgroundImage
63 | fillMode: Image.PreserveAspectCrop
64 | asynchronous: true
65 | }
66 |
67 | Kirigami.Icon {
68 | id: icon
69 | visible: appObj.showIcon
70 | source: appObj.iconSource
71 | anchors.verticalCenter: parent.verticalCenter
72 | anchors.horizontalCenter: parent.horizontalCenter
73 | // property int size: 72 // Just a default, overriden in State change
74 | property int size: Math.min(parent.width, parent.height) / 2
75 | width: appObj.showIcon ? size : 0
76 | height: appObj.showIcon ? size : 0
77 | anchors.fill: appObj.iconFill ? parent : null
78 | smooth: appObj.iconFill
79 | }
80 |
81 | PlasmaComponents3.Label {
82 | id: label
83 | visible: appObj.showLabel
84 | text: appObj.labelText
85 | anchors.top: parent.top
86 | anchors.bottom: parent.bottom
87 | anchors.leftMargin: tilePadding
88 | anchors.rightMargin: tilePadding
89 | anchors.left: parent.left
90 | anchors.right: parent.right
91 | wrapMode: Text.Wrap
92 | horizontalAlignment: labelAlignment
93 | verticalAlignment: Text.AlignBottom
94 | width: parent.width
95 | renderType: Text.QtRendering // Fix pixelation when scaling. Plasma.Label uses NativeRendering.
96 | style: Text.Outline
97 | styleColor: appObj.backgroundGradient ? tileItemView.gradientBottomColor : appObj.backgroundColor
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/package/contents/ui/Utils.js:
--------------------------------------------------------------------------------
1 | .pragma library
2 |
3 | function parseDropUrl(url) {
4 | var startsWithAppsScheme = url.indexOf('applications:') === 0 // Search Results add this prefix
5 | if (startsWithAppsScheme) {
6 | // console.log('parseDropUrl', 'startsWithAppsScheme', url)
7 | url = url.substr('applications:'.length)
8 | }
9 |
10 | var workingDir = Qt.resolvedUrl('.')
11 | var endsWithDesktop = url.indexOf('.desktop') === url.length - '.desktop'.length
12 | var isRelativeDesktopUrl = endsWithDesktop && (
13 | url.indexOf(workingDir) === 0
14 | // || url.indexOf('file:///usr/share/applications/') === 0
15 | // || url.indexOf('/.local/share/applications/') >= 0
16 | || url.indexOf('/share/applications/') >= 0 // 99% certain this desktop file should be accessed relatively.
17 | )
18 | // console.log('parseDropUrl', workingDir, endsWithDesktop, isRelativeDesktopUrl)
19 | // console.log('onUrlDropped', 'url', url)
20 | if (isRelativeDesktopUrl) {
21 | // Remove the path because .favoriteId is just the file name.
22 | // However passing the favoriteId in mimeData.url will prefix the current QML path because it's a QUrl.
23 | var tokens = url.toString().split('/')
24 | var favoriteId = tokens[tokens.length-1]
25 | // console.log('isRelativeDesktopUrl', tokens, favoriteId)
26 | return favoriteId
27 | } else {
28 | return url
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package/contents/ui/config/ConfigExportLayout.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls
3 | import QtQuick.Layouts
4 | import org.kde.plasma.core as PlasmaCore
5 |
6 | import ".." as TiledMenu
7 |
8 | ColumnLayout {
9 | id: page
10 |
11 | TextAreaBase64JsonString {
12 | id: exportData
13 | Layout.fillHeight: true
14 |
15 | TiledMenu.Base64JsonString {
16 | id: configTileModel
17 | configKey: 'tileModel'
18 | writing: exportData.base64JsonString.writing
19 | defaultValue: []
20 | }
21 |
22 | property var ignoredKeys: [
23 | 'tileScale',
24 | 'searchResultsReversed',
25 | 'searchResultsCustomSort',
26 | ]
27 |
28 | defaultValue: {
29 | var data = {}
30 | var configKeyList = plasmoid.configuration.keys()
31 | for (var i = 0; i < configKeyList.length; i++) {
32 | var configKey = configKeyList[i]
33 | var configValue = plasmoid.configuration[configKey]
34 | if (typeof configValue === "undefined") {
35 | continue
36 | }
37 | if (ignoredKeys.indexOf(configKey) >= 0) {
38 | continue
39 | }
40 | // Filter KF5 5.78 default keys https://invent.kde.org/frameworks/kdeclarative/-/merge_requests/38
41 | if (configKey.endsWith('Default')) {
42 | var key2 = configKey.substr(0, configKey.length - 'Default'.length)
43 | if (typeof plasmoid.configuration[key2] !== 'undefined') {
44 | continue
45 | }
46 | }
47 | if (configKey == 'tileModel') {
48 | data.tileModel = configTileModel.value
49 | } else {
50 | data[configKey] = configValue
51 | }
52 | }
53 | return data
54 | }
55 |
56 | function serialize() {
57 | var newValue = parseText(textArea.text)
58 | var configKeyList = plasmoid.configuration.keys()
59 | for (var i = 0; i < configKeyList.length; i++) {
60 | var configKey = configKeyList[i]
61 | var propValue = newValue[configKey]
62 | if (typeof propValue === "undefined") {
63 | continue
64 | }
65 | if (ignoredKeys.indexOf(configKey) >= 0) {
66 | continue
67 | }
68 | if (configKey == 'tileModel') {
69 | configTileModel.set(propValue)
70 | } else {
71 | if (plasmoid.configuration[configKey] != propValue) {
72 | plasmoid.configuration[configKey] = propValue
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/package/contents/ui/config/ConfigurationShortcuts.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Controls as QQC2
3 | import QtQuick.Layouts
4 | import org.kde.kquickcontrols as KQuickControls
5 | import org.kde.kirigami as Kirigami
6 | import org.kde.plasma.plasmoid
7 | import org.kde.kcmutils as KCM
8 |
9 | // Based on:
10 | // https://invent.kde.org/plasma/plasma-desktop/blob/master/desktoppackage/contents/configuration/ConfigurationShortcuts.qml
11 | KCM.SimpleKCM {
12 | id: page
13 |
14 | title: i18nd("plasma_shell_org.kde.plasma.desktop", "Shortcuts")
15 |
16 | signal configurationChanged()
17 | function saveConfig() {
18 | Plasmoid.globalShortcut = keySequenceItem.keySequence
19 | }
20 |
21 | ColumnLayout {
22 | QQC2.Label {
23 | Layout.fillWidth: true
24 | text: i18nd("plasma_shell_org.kde.plasma.desktop", "This shortcut will activate the applet as though it had been clicked.")
25 | wrapMode: Text.WordWrap
26 | }
27 |
28 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrols/KeySequenceItem.qml
29 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrols/private/keysequencehelper.h
30 | KQuickControls.KeySequenceItem {
31 | id: keySequenceItem
32 | keySequence: Plasmoid.globalShortcut
33 | modifierOnlyAllowed: true
34 | onCaptureFinished: {
35 | if (keySequence !== Plasmoid.globalShortcut) {
36 | page.configurationChanged()
37 | }
38 | }
39 |
40 | // Unfortunately, keySequence does not exposed the isEmpty function to QML.
41 | // There's no way to detect if the shortcut is not set.
42 | // readonly property bool isEmpty: keySequence.isEmpty()
43 |
44 | // Luckily, the PlasmaQuick::ConfigView exposes the global shortcut as a String.
45 | // Unfortunately, appletGlobalShortcut only updates when the config tab is loaded.
46 | // It does not even change when we hit apply. It's limited use makes it useless
47 | // for notifying the user when the Meta shortcut is active.
48 | // https://github.com/KDE/plasma-framework/blob/master/src/plasmaquick/configview.cpp#L174
49 | // https://github.com/KDE/plasma-framework/blob/master/src/plasmaquick/configview.h
50 | // readonly property bool isEmpty: configDialog.appletGlobalShortcut == ""
51 | }
52 |
53 | Item {
54 | implicitHeight: Kirigami.Units.largeSpacing
55 | }
56 |
57 | QQC2.Label {
58 | Layout.fillWidth: true
59 | text: i18n("When this widget has a global shortcut set, like 'Alt+F1', Plasma will open this menu with just the ⊞ Windows / Meta key.")
60 | wrapMode: Text.WordWrap
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/package/contents/ui/config/TextAreaBase64JsonString.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 | import QtQuick.Layouts
3 |
4 | import ".." as TiledMenu
5 | import "../libconfig" as LibConfig
6 |
7 | LibConfig.TextArea {
8 | id: textArea
9 | Layout.fillWidth: true
10 |
11 | property var base64JsonString: TiledMenu.Base64JsonString {
12 | id: base64JsonString
13 | }
14 |
15 | property alias jsonKey: base64JsonString.configKey
16 | property alias defaultValue: base64JsonString.defaultValue
17 |
18 | property alias enabled: textArea.enabled
19 |
20 | readonly property var configValue: configKey ? plasmoid.configuration[configKey] : ""
21 | onConfigValueChanged: deserialize()
22 | readonly property var value: base64JsonString.value
23 |
24 | property alias textArea: textArea
25 | property alias textAreaText: textArea.text
26 |
27 | property string indent: ' '
28 |
29 | function parseValue(value) {
30 | return JSON.stringify(value, null, indent)
31 | }
32 | function parseText(text) {
33 | return JSON.parse(text)
34 | }
35 |
36 | function setValue(val) {
37 | var newText = parseValue(val)
38 | if (textArea.text != newText) {
39 | textArea.text = newText
40 | }
41 | }
42 |
43 | function deserialize() {
44 | if (!textArea.focus) {
45 | setValue(value)
46 | }
47 | }
48 | function serialize() {
49 | var newValue = parseText(textArea.text)
50 | base64JsonString.set(newValue)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/package/contents/ui/lib/Logger.qml:
--------------------------------------------------------------------------------
1 | import QtQuick
2 |
3 | Item {
4 | id: logger
5 | property string name: 'logger'
6 | property bool showDebug: false
7 |
8 | function prettifyArguments(rawArgs) {
9 | var args = Array.apply(null, rawArgs)
10 | for (var i = 0; i < args.length; i++) {
11 | if (typeof args[i] === "object" || args[i] instanceof Array) {
12 | args[i] = JSON.stringify(args[i], null, '\t')
13 | }
14 | }
15 | return args
16 | }
17 |
18 | function debug() {
19 | if (showDebug) {
20 | var args = Array.apply(null, arguments)
21 | args.unshift('[' + name + ':debug]')
22 | console.log.apply(console, args)
23 | }
24 | }
25 |
26 | function debugJSON() {
27 | if (showDebug) {
28 | var args = prettifyArguments(arguments)
29 | args.unshift('[' + name + ':debug]')
30 | console.log.apply(console, args)
31 | }
32 | }
33 |
34 | function log() {
35 | var args = Array.apply(null, arguments)
36 | args.unshift('[' + name + ']')
37 | console.log.apply(console, args)
38 | }
39 |
40 | function logJSON() {
41 | if (showDebug) {
42 | var args = prettifyArguments(arguments)
43 | args.unshift('[' + name + ']')
44 | console.log.apply(console, args)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/package/contents/ui/lib/Requests.js:
--------------------------------------------------------------------------------
1 | .pragma library
2 | // Version 6
3 |
4 | function request(opt, callback) {
5 | if (typeof opt === 'string') {
6 | opt = { url: opt }
7 | }
8 | var req = new XMLHttpRequest()
9 | req.onerror = function(e) {
10 | console.log('XMLHttpRequest.onerror', e)
11 | if (e) {
12 | console.log('\t', e.status, e.statusText, e.message)
13 | callback(e.message)
14 | } else {
15 | callback('XMLHttpRequest.onerror(undefined)')
16 | }
17 | }
18 | req.onreadystatechange = function() {
19 | if (req.readyState === XMLHttpRequest.DONE) { // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-done
20 | if (200 <= req.status && req.status < 400) {
21 | callback(null, req.responseText, req)
22 | } else {
23 | if (req.status === 0) {
24 | console.log('HTTP 0 Headers: \n' + req.getAllResponseHeaders())
25 | }
26 | var msg = "HTTP Error " + req.status + ": " + req.statusText
27 | callback(msg, req.responseText, req)
28 | }
29 | }
30 | }
31 | req.open(opt.method || "GET", opt.url, true)
32 | if (opt.headers) {
33 | for (var key in opt.headers) {
34 | req.setRequestHeader(key, opt.headers[key])
35 | }
36 | }
37 | req.send(opt.data)
38 | }
39 |
40 | function encodeFormData(opt) {
41 | opt.headers = opt.headers || {}
42 | opt.headers['Content-Type'] = 'application/x-www-form-urlencoded'
43 | if (opt.data) {
44 | var s = ''
45 | var i = 0
46 | for (var key in opt.data) {
47 | if (i > 0) {
48 | s += '&'
49 | }
50 | var value = opt.data[key]
51 | if (typeof value === "object") {
52 | // TODO: Flatten obj={list: [1, 2]} as
53 | // obj[list][0]=1
54 | // obj[list][1]=2
55 | }
56 | s += encodeURIComponent(key) + '=' + encodeURIComponent(value)
57 | i += 1
58 | }
59 | opt.data = s
60 | }
61 | return opt
62 | }
63 |
64 | function post(opt, callback) {
65 | if (typeof opt === 'string') {
66 | opt = { url: opt }
67 | }
68 | opt.method = 'POST'
69 | encodeFormData(opt)
70 | request(opt, callback)
71 | }
72 |
73 |
74 | function getJSON(opt, callback) {
75 | if (typeof opt === 'string') {
76 | opt = { url: opt }
77 | }
78 | opt.headers = opt.headers || {}
79 | opt.headers['Accept'] = 'application/json'
80 | request(opt, function(err, data, req) {
81 | if (!err && data) {
82 | data = JSON.parse(data)
83 | }
84 | callback(err, data, req)
85 | })
86 | }
87 |
88 |
89 | function postJSON(opt, callback) {
90 | if (typeof opt === 'string') {
91 | opt = { url: opt }
92 | }
93 | opt.method = opt.method || 'POST'
94 | opt.headers = opt.headers || {}
95 | opt.headers['Content-Type'] = 'application/json'
96 | if (opt.data) {
97 | opt.data = JSON.stringify(opt.data)
98 | }
99 | getJSON(opt, callback)
100 | }
101 |
102 | function getFile(url, callback) {
103 | var req = new XMLHttpRequest()
104 | req.onerror = function(e) {
105 | console.log('XMLHttpRequest.onerror', e)
106 | if (e) {
107 | console.log('\t', e.status, e.statusText, e.message)
108 | callback(e.message)
109 | } else {
110 | callback('XMLHttpRequest.onerror(undefined)')
111 | }
112 | }
113 | req.onreadystatechange = function() {
114 | if (req.readyState === 4) {
115 | // Since the file is local, it will have HTTP 0 Unsent.
116 | callback(null, req.responseText, req)
117 | }
118 | }
119 | req.open("GET", url, true)
120 | req.send()
121 | }
122 |
123 | function parseMetadata(data) {
124 | var lines = data.split('\n')
125 | var d = {}
126 | for (var i = 0; i < lines.length; i++) {
127 | var line = lines[i]
128 | var delimeterIndex = line.indexOf('=')
129 | if (delimeterIndex >= 0) {
130 | var key = line.substr(0, delimeterIndex)
131 | var value = line.substr(delimeterIndex + 1)
132 | d[key] = value
133 | }
134 | }
135 | return d
136 | }
137 |
138 | function getAppletMetadata(callback) {
139 | var url = Qt.resolvedUrl('.')
140 |
141 | var s = '/share/plasma/plasmoids/'
142 | var index = url.indexOf(s)
143 | if (index >= 0) {
144 | var a = index + s.length
145 | var b = url.indexOf('/', a)
146 | // var packageName = url.substr(a, b-a)
147 | var metadataUrl = url.substr(0, b) + '/metadata.desktop'
148 | Requests.getFile(metadataUrl, function(err, data) {
149 | if (err) {
150 | return callback(err)
151 | }
152 |
153 | var metadata = parseMetadata(data)
154 | callback(null, metadata)
155 | })
156 | } else {
157 | return callback('Could not parse version.')
158 | }
159 | }
160 |
161 | function getAppletVersion(callback) {
162 | getAppletMetadata(function(err, metadata) {
163 | if (err) return callback(err)
164 |
165 | callback(err, metadata['X-KDE-PluginInfo-Version'])
166 | })
167 | }
168 |
--------------------------------------------------------------------------------
/package/contents/ui/lib/XdgUserDir.qml:
--------------------------------------------------------------------------------
1 | // Version 1
2 |
3 | import QtQml
4 | import QtCore as QtCore
5 |
6 | QtObject {
7 | id: xdgUserDir
8 | readonly property url home: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.HomeLocation)
9 | readonly property url desktop: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.DesktopLocation)
10 | readonly property url documents: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.DocumentsLocation)
11 | readonly property url download: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.DownloadLocation)
12 | readonly property url music: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.MusicLocation)
13 | readonly property url pictures: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.PicturesLocation)
14 | readonly property url videos: QtCore.StandardPaths.writableLocation(QtCore.StandardPaths.MoviesLocation)
15 | }
16 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/CheckBox.qml:
--------------------------------------------------------------------------------
1 | // Version 5
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 |
6 | QQC2.CheckBox {
7 | id: configCheckBox
8 |
9 | property string configKey: ''
10 | checked: plasmoid.configuration[configKey]
11 | onClicked: plasmoid.configuration[configKey] = !plasmoid.configuration[configKey]
12 | }
13 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/ColorField.qml:
--------------------------------------------------------------------------------
1 | // Version 8
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Dialogs as QtDialogs
6 | import QtQuick.Window
7 | import org.kde.kirigami as Kirigami
8 |
9 | // https://doc.qt.io/qt-6/qtgraphicaleffects5-index.html
10 | import Qt5Compat.GraphicalEffects as QtGraphicalEffects // TODO Deprecated in Qt6
11 |
12 |
13 | QQC2.TextField {
14 | id: colorField
15 | font.family: "monospace"
16 | readonly property string defaultText: "#AARRGGBB"
17 | placeholderText: defaultColor ? defaultColor : defaultText
18 |
19 | onTextChanged: {
20 | // Make sure the text is:
21 | // Empty (use default)
22 | // or #123 or #112233 or #11223344 before applying the color.
23 | if (text.length === 0
24 | || (text.indexOf('#') === 0 && (text.length == 4 || text.length == 7 || text.length == 9))
25 | ) {
26 | colorField.value = text
27 | }
28 | }
29 |
30 | property bool showAlphaChannel: true
31 | property bool showPreviewBg: true
32 |
33 | property string configKey: ''
34 | property string defaultColor: ''
35 | property string value: {
36 | if (configKey) {
37 | return plasmoid.configuration[configKey]
38 | } else {
39 | return "#000"
40 | }
41 | }
42 |
43 | readonly property color defaultColorValue: defaultColor
44 | readonly property color valueColor: {
45 | if (value == '' && defaultColor) {
46 | return defaultColor
47 | } else {
48 | return value
49 | }
50 | }
51 |
52 | onValueChanged: {
53 | if (!activeFocus) {
54 | text = colorField.value
55 | }
56 | if (configKey) {
57 | if (value == defaultColorValue) {
58 | plasmoid.configuration[configKey] = ""
59 | } else {
60 | plasmoid.configuration[configKey] = value
61 | }
62 | }
63 | }
64 |
65 | leftPadding: rightPadding + mouseArea.height + rightPadding
66 |
67 | FontMetrics {
68 | id: fontMetrics
69 | font.family: colorField.font.family
70 | font.italic: colorField.font.italic
71 | font.pointSize: colorField.font.pointSize
72 | font.pixelSize: colorField.font.pixelSize
73 | font.weight: colorField.font.weight
74 | }
75 | readonly property int defaultWidth: Math.ceil(fontMetrics.advanceWidth(defaultText))
76 | implicitWidth: rightPadding + Math.max(defaultWidth, contentWidth) + leftPadding
77 |
78 | MouseArea {
79 | id: mouseArea
80 | anchors.leftMargin: parent.rightPadding
81 | anchors.topMargin: parent.topPadding
82 | anchors.bottomMargin: parent.bottomPadding
83 | anchors.left: parent.left
84 | anchors.top: parent.top
85 | anchors.bottom: parent.bottom
86 | width: height
87 | hoverEnabled: true
88 | cursorShape: Qt.PointingHandCursor
89 |
90 | onClicked: dialogLoader.active = true
91 |
92 | // Color Preview Circle
93 | Rectangle {
94 | id: previewBgMask
95 | visible: false
96 | anchors.fill: parent
97 | border.width: 1 * Screen.devicePixelRatio
98 | border.color: "transparent"
99 | radius: width / 2
100 | }
101 | QtGraphicalEffects.ConicalGradient {
102 | id: previewBgGradient
103 | visible: colorField.showPreviewBg
104 | anchors.fill: parent
105 | angle: 0.0
106 | gradient: Gradient {
107 | GradientStop { position: 0.00; color: "white" }
108 | GradientStop { position: 0.24; color: "white" }
109 | GradientStop { position: 0.25; color: "#cccccc" }
110 | GradientStop { position: 0.49; color: "#cccccc" }
111 | GradientStop { position: 0.50; color: "white" }
112 | GradientStop { position: 0.74; color: "white" }
113 | GradientStop { position: 0.75; color: "#cccccc" }
114 | GradientStop { position: 1.00; color: "#cccccc" }
115 | }
116 | source: previewBgMask
117 | }
118 | Rectangle {
119 | id: previewFill
120 | anchors.fill: parent
121 | color: colorField.valueColor
122 | border.width: 1 * Screen.devicePixelRatio
123 | border.color: Kirigami.ColorUtils.linearInterpolation(color, Kirigami.Theme.textColor, 0.5)
124 | radius: width / 2
125 | }
126 | }
127 |
128 | Loader {
129 | id: dialogLoader
130 | active: false
131 | sourceComponent: QtDialogs.ColorDialog {
132 | id: dialog
133 | visible: true
134 | modality: Qt.WindowModal
135 | options: colorField.showAlphaChannel ? QtDialogs.ColorDialog.ShowAlphaChannel : 0
136 | selectedColor: colorField.valueColor
137 | onSelectedColorChanged: {
138 | if (visible) {
139 | colorField.text = selectedColor
140 | }
141 | }
142 | onAccepted: {
143 | colorField.text = selectedColor
144 | dialogLoader.active = false
145 | }
146 | onRejected: {
147 | // This event is also triggered when the user clicks outside the popup modal.
148 | // TODO Find a way to only trigger when Cancel is clicked.
149 | colorField.text = initColor
150 | dialogLoader.active = false
151 | }
152 |
153 | property color initColor
154 | Component.onCompleted: {
155 | initColor = colorField.valueColor
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/ComboBox.qml:
--------------------------------------------------------------------------------
1 | // Version 8
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 |
6 | /*
7 | ** Example:
8 | **
9 | import './libconfig' as LibConfig
10 | LibConfig.ComboBox {
11 | configKey: "appDescription"
12 | model: [
13 | { value: "a", text: i18n("A") },
14 | { value: "b", text: i18n("B") },
15 | { value: "c", text: i18n("C") },
16 | ]
17 | }
18 | LibConfig.ComboBox {
19 | configKey: "appDescription"
20 | populated: false
21 | onPopulate: {
22 | model = [
23 | { value: "a", text: i18n("A") },
24 | { value: "b", text: i18n("B") },
25 | { value: "c", text: i18n("C") },
26 | ]
27 | populated = true
28 | }
29 | }
30 | */
31 | QQC2.ComboBox {
32 | id: configComboBox
33 |
34 | property string configKey: ''
35 | readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
36 | onConfigValueChanged: {
37 | if (!focus && value != configValue) {
38 | selectValue(configValue)
39 | }
40 | }
41 |
42 | readonly property var currentItem: currentIndex >= 0 ? model[currentIndex] : null
43 |
44 | textRole: "text" // Doesn't autodeduce from model if we manually populate it
45 |
46 | // Note that ComboBox.valueRole and ComboBox.currentValue was introduced in Qt 5.14.
47 | // Ubuntu 20.04 only has Qt 5.12. We cannot define a currentValue property or it will
48 | // break when users upgrade to Qt 5.14.
49 | property string _valueRole: "value"
50 | readonly property var _currentValue: _valueRole && currentIndex >= 0 ? model[currentIndex][_valueRole] : null
51 | readonly property alias value: configComboBox._currentValue
52 |
53 | model: []
54 |
55 | signal populate()
56 | property bool populated: true
57 |
58 | Component.onCompleted: {
59 | populate()
60 | selectValue(configValue)
61 | }
62 |
63 | onCurrentIndexChanged: {
64 | if (typeof model !== 'number' && 0 <= currentIndex && currentIndex < count) {
65 | var item = model[currentIndex]
66 | if (typeof item !== "undefined") {
67 | var val = item[_valueRole]
68 | if (configKey && (typeof val !== "undefined") && populated) {
69 | plasmoid.configuration[configKey] = val
70 | }
71 | }
72 | }
73 | }
74 |
75 | function size() {
76 | if (typeof model === "number") {
77 | return model
78 | } else if (typeof model.count === "number") {
79 | return model.count
80 | } else if (typeof model.length === "number") {
81 | return model.length
82 | } else {
83 | return 0
84 | }
85 | }
86 |
87 | function findValue(val) {
88 | for (var i = 0; i < size(); i++) {
89 | if (model[i][_valueRole] == val) {
90 | return i
91 | }
92 | }
93 | return -1
94 | }
95 |
96 | function selectValue(val) {
97 | var index = findValue(val)
98 | if (index >= 0) {
99 | currentIndex = index
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/FormKCM.qml:
--------------------------------------------------------------------------------
1 | // Version 3
2 |
3 | import QtQuick
4 | import QtQuick.Window
5 | import org.kde.kirigami as Kirigami
6 | import org.kde.kcmutils as KCM
7 |
8 | KCM.SimpleKCM {
9 | id: simpleKCM
10 | default property alias _formChildren: formLayout.data
11 |
12 | Kirigami.FormLayout {
13 | id: formLayout
14 | }
15 |
16 | // https://invent.kde.org/plasma/plasma-desktop/-/blame/master/desktoppackage/contents/configuration/AppletConfiguration.qml
17 | // AppletConfiguration.implicitWidth: Kirigami.Units.gridUnit * 40 = 720
18 | // AppletConfiguration.Layout.minimumWidth: Kirigami.Units.gridUnit * 30 = 540
19 | // In practice, Window.width = 744px is a typical FormLayout.wideMode switchWidth
20 | // A rough guess is 128+24+180+10+360+24+20 = 746px
21 | // TabSidebar=128x, Padding=24px, Labels=180px, Spacing=10px, Controls=360px, Padding=24px, Scrollbar=20px
22 | // However the default is only 720px. So we'll set it to a 800px minimum to avoid wideMode=false
23 | property int wideModeMinWidth: 800 * Screen.devicePixelRatio
24 | Window.onWindowChanged: {
25 | if (Window.window) {
26 | Window.window.visibleChanged.connect(function(){
27 | if (Window.window && Window.window.visible && Window.window.width < wideModeMinWidth) {
28 | Window.window.width = wideModeMinWidth
29 | }
30 | })
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/Heading.qml:
--------------------------------------------------------------------------------
1 | // Version 6
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Layouts
6 | import org.kde.kirigami as Kirigami
7 |
8 | /*
9 | ** Example:
10 | **
11 | import './libconfig' as LibConfig
12 | LibConfig.Heading {
13 | text: i18n("SpinBox (Double)")
14 | }
15 | */
16 |
17 | // While the following Kirigami is very simple:
18 | // Kirigami.Separator {
19 | // Kirigami.FormData.label: "Heading"
20 | // Kirigami.FormData.isSection: true
21 | // }
22 | //
23 | // I want to be able to adjust the label size and make it bold.
24 | // Kirigami's buddy Heading is level=3, which does not stand out
25 | // very well. I also want to center the heading.
26 | // Since we can't access the Heading in the buddy component, we
27 | // need to make sure the Heading has no text, and draw our own.
28 | ColumnLayout {
29 | id: heading
30 | spacing: 0
31 |
32 | property string text: ""
33 | property alias separator: separator
34 | property alias label: label
35 | property bool useThickTopMargin: true
36 |
37 | property Item __formLayout: {
38 | if (parent && typeof parent.wideMode === 'boolean') {
39 | return parent
40 | } else if (typeof formLayout !== 'undefined' && typeof formLayout.wideMode === 'boolean') {
41 | return formLayout
42 | } else if (typeof page !== 'undefined' && typeof page.wideMode === 'boolean') {
43 | return page
44 | } else {
45 | return null
46 | }
47 | }
48 |
49 | Layout.fillWidth: true
50 | // Kirigami.FormData.isSection: true
51 | Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.FormLabel
52 |
53 | Kirigami.Separator {
54 | id: separator
55 | visible: false
56 | Layout.fillWidth: true
57 | Layout.topMargin: Kirigami.Units.largeSpacing
58 | Layout.bottomMargin: Kirigami.Units.largeSpacing
59 | }
60 |
61 | Kirigami.Heading {
62 | id: label
63 | Layout.topMargin: useThickTopMargin ? Kirigami.Units.largeSpacing * 3 : Kirigami.Units.largeSpacing
64 | Layout.bottomMargin: Kirigami.Units.smallSpacing
65 | Layout.fillWidth: true
66 | text: heading.text
67 | level: 1
68 | font.weight: Font.Bold
69 | // horizontalAlignment: (!__formLayout || __formLayout.wideMode) ? Text.AlignHCenter : Text.AlignLeft
70 | verticalAlignment: (!__formLayout || __formLayout.wideMode) ? Text.AlignVCenter : Text.AlignBottom
71 | }
72 | }
73 |
74 | //--- Test Default Kirigami Heading
75 | // Kirigami.Separator {
76 | // property string text: ""
77 | // Kirigami.FormData.label: text
78 | // Kirigami.FormData.isSection: true
79 | // property alias separator: separator
80 | // Item {
81 | // id: separator
82 | // }
83 | // }
84 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/IconField.qml:
--------------------------------------------------------------------------------
1 | // Version 11
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Layouts
6 | import org.kde.kirigami as Kirigami
7 | import org.kde.ksvg as KSvg
8 | import org.kde.plasma.core as PlasmaCore
9 | import org.kde.iconthemes as KIconThemes // IconDialog
10 |
11 | RowLayout {
12 | id: iconField
13 |
14 | default property alias _contentChildren: content.data
15 |
16 | property string configKey: ''
17 | property alias value: textField.text
18 | readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
19 | onConfigValueChanged: {
20 | if (!textField.focus && value != configValue) {
21 | value = configValue
22 | }
23 | }
24 | property int previewIconSize: Kirigami.Units.iconSizes.medium
25 | property string defaultValue: ""
26 | property alias placeholderValue: textField.placeholderText
27 | property var presetValues: []
28 | property bool showPresetLabel: true
29 |
30 | // Based on org.kde.plasma.kickoff
31 | QQC2.Button {
32 | id: iconButton
33 | padding: Kirigami.Units.smallSpacing
34 | Layout.alignment: Qt.AlignTop
35 |
36 | // KDE QQC2 sets implicitSize to background.implicitSize ignoring padding/inset properties.
37 | implicitWidth: leftPadding + contentItem.implicitWidth + rightPadding
38 | implicitHeight: topPadding + contentItem.implicitHeight + bottomPadding
39 |
40 | onPressed: iconMenu.opened ? iconMenu.close() : iconMenu.open()
41 |
42 | contentItem: KSvg.FrameSvgItem {
43 | id: previewFrame
44 | imagePath: plasmoid.location === PlasmaCore.Types.Vertical || plasmoid.location === PlasmaCore.Types.Horizontal
45 | ? "widgets/panel-background" : "widgets/background"
46 | implicitWidth: fixedMargins.left + previewIconSize + fixedMargins.right
47 | implicitHeight: fixedMargins.top + previewIconSize + fixedMargins.bottom
48 |
49 | Kirigami.Icon {
50 | anchors.fill: parent
51 | anchors.leftMargin: previewFrame.fixedMargins.left
52 | anchors.topMargin: previewFrame.fixedMargins.top
53 | anchors.rightMargin: previewFrame.fixedMargins.right
54 | anchors.bottomMargin: previewFrame.fixedMargins.bottom
55 | source: iconField.value || iconField.placeholderValue
56 | active: iconButton.hovered
57 | }
58 | }
59 |
60 | QQC2.Menu {
61 | id: iconMenu
62 |
63 | // Appear below the button
64 | y: +parent.height
65 |
66 | QQC2.MenuItem {
67 | text: i18ndc("plasma_applet_org.kde.plasma.kickoff", "@item:inmenu Open icon chooser dialog", "Choose...")
68 | icon.name: "document-open"
69 | onClicked: dialogLoader.active = true
70 | }
71 | QQC2.MenuItem {
72 | text: i18ndc("plasma_applet_org.kde.plasma.kickoff", "@item:inmenu Reset icon to default", "Clear Icon")
73 | icon.name: "edit-clear"
74 | onClicked: iconField.value = iconField.defaultValue
75 | }
76 | }
77 | }
78 |
79 | ColumnLayout {
80 | id: content
81 | Layout.fillWidth: true
82 |
83 | RowLayout {
84 | QQC2.TextField {
85 | id: textField
86 | Layout.fillWidth: true
87 |
88 | text: iconField.configValue
89 | onTextChanged: serializeTimer.restart()
90 |
91 | rightPadding: clearButton.width + Kirigami.Units.smallSpacing
92 |
93 | QQC2.ToolButton {
94 | id: clearButton
95 | visible: iconField.configValue != iconField.defaultValue
96 | icon.name: iconField.defaultValue === "" ? "edit-clear" : "edit-undo"
97 | onClicked: iconField.value = iconField.defaultValue
98 |
99 | anchors.top: parent.top
100 | anchors.right: parent.right
101 | anchors.bottom: parent.bottom
102 |
103 | width: height
104 | }
105 | }
106 |
107 | QQC2.Button {
108 | id: browseButton
109 | icon.name: "document-open"
110 | onClicked: dialogLoader.active = true
111 | }
112 | }
113 |
114 | Flow {
115 | Layout.fillWidth: true
116 | Layout.maximumWidth: Kirigami.Units.gridUnit * 30
117 | Repeater {
118 | model: presetValues
119 | QQC2.Button {
120 | icon.name: modelData
121 | text: iconField.showPresetLabel ? modelData : ''
122 | onClicked: iconField.value = modelData
123 | QQC2.ToolTip.text: modelData
124 | QQC2.ToolTip.visible: !iconField.showPresetLabel && hovered
125 | }
126 | }
127 | }
128 | }
129 |
130 | Loader {
131 | id: dialogLoader
132 | active: false
133 | sourceComponent: KIconThemes.IconDialog {
134 | id: dialog
135 | visible: true
136 | modality: Qt.WindowModal
137 | onIconNameChanged: {
138 | iconField.value = iconName
139 | }
140 | onVisibleChanged: {
141 | if (!visible) {
142 | dialogLoader.active = false
143 | }
144 | }
145 | }
146 | }
147 |
148 | Timer { // throttle
149 | id: serializeTimer
150 | interval: 300
151 | onTriggered: {
152 | if (configKey) {
153 | plasmoid.configuration[configKey] = iconField.value
154 | }
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/RadioButtonGroup.qml:
--------------------------------------------------------------------------------
1 | // Version 7
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Layouts
6 | import org.kde.kirigami as Kirigami
7 |
8 | /*
9 | ** Example:
10 | **
11 | import './libconfig' as LibConfig
12 | LibConfig.RadioButtonGroup {
13 | configKey: "priority"
14 | model: [
15 | { value: "a", text: i18n("A") },
16 | { value: "b", text: i18n("B") },
17 | { value: "c", text: i18n("C") },
18 | ]
19 | }
20 | */
21 | ColumnLayout {
22 | id: radioButtonGroup
23 |
24 | property string configKey: ''
25 | readonly property var configValue: configKey ? plasmoid.configuration[configKey] : ""
26 |
27 | Kirigami.FormData.labelAlignment: Qt.AlignTop
28 |
29 | property alias group: group
30 | QQC2.ButtonGroup {
31 | id: group
32 | }
33 |
34 | property alias model: buttonRepeater.model
35 |
36 | // The main reason we put all the RadioButtons in
37 | // a ColumnLayout is to shrink the spacing between the buttons.
38 | spacing: Kirigami.Units.smallSpacing
39 |
40 | // Assign buddyFor to the first RadioButton so that the Kirigami label aligns with it.
41 | // Repeater is also a visibleChild, so avoid it.
42 | Kirigami.FormData.buddyFor: {
43 | for (var i = 0; i < visibleChildren.length; i++) {
44 | if (!(visibleChildren[i] instanceof Repeater)) {
45 | return visibleChildren[i]
46 | }
47 | }
48 | return null
49 | }
50 |
51 | Repeater {
52 | id: buttonRepeater
53 | QQC2.RadioButton {
54 | visible: typeof modelData.visible !== "undefined" ? modelData.visible : true
55 | enabled: typeof modelData.enabled !== "undefined" ? modelData.enabled : true
56 | text: modelData.text
57 | checked: modelData.value === configValue
58 | QQC2.ButtonGroup.group: radioButtonGroup.group
59 | onClicked: {
60 | focus = true
61 | if (configKey) {
62 | plasmoid.configuration[configKey] = modelData.value
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/TextArea.qml:
--------------------------------------------------------------------------------
1 | // Version 7
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Layouts
6 | import org.kde.kirigami as Kirigami
7 |
8 | QQC2.TextArea {
9 | id: textArea
10 | property string configKey: ''
11 | readonly property var configValue: configKey ? plasmoid.configuration[configKey] : ""
12 | onConfigValueChanged: deserialize()
13 |
14 | onTextChanged: serializeTimer.restart()
15 |
16 | wrapMode: TextArea.Wrap
17 |
18 | Kirigami.FormData.labelAlignment: Qt.AlignTop
19 |
20 | // An empty TextArea adjust to it's empty contents.
21 | // So we need the TextArea to be wide enough.
22 | Layout.fillWidth: true
23 |
24 | // Since QQC2 defaults to implicitWidth=contentWidth, a really long
25 | // line in TextArea will cause a binding loop on FormLayout.width
26 | // when we only set fillWidth=true.
27 | // Setting an implicitWidth fixes this and allows the text to wrap.
28 | implicitWidth: Kirigami.Units.gridUnit * 20
29 |
30 | // Load
31 | function deserialize() {
32 | if (configKey) {
33 | var newText = valueToText(configValue)
34 | setText(newText)
35 | }
36 | }
37 | function valueToText(value) {
38 | return value
39 | }
40 | function setText(newText) {
41 | if (textArea.text != newText) {
42 | if (textArea.focus) {
43 | // TODO: Find cursor in newText and replace text before + after cursor.
44 | } else {
45 | textArea.text = newText
46 | }
47 | }
48 | }
49 |
50 | // Save
51 | function serialize() {
52 | var newValue = textToValue(textArea.text)
53 | setConfigValue(newValue)
54 | }
55 | function textToValue(text) {
56 | return text
57 | }
58 | function setConfigValue(newValue) {
59 | if (configKey) {
60 | var oldValue = plasmoid.configuration[configKey]
61 | if (oldValue != newValue) {
62 | plasmoid.configuration[configKey] = newValue
63 | }
64 | }
65 | }
66 |
67 | Timer { // throttle
68 | id: serializeTimer
69 | interval: 300
70 | onTriggered: serialize()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/package/contents/ui/libconfig/TextAreaStringList.qml:
--------------------------------------------------------------------------------
1 | // Version 6
2 |
3 | import QtQuick
4 | import QtQuick.Controls as QQC2
5 | import QtQuick.Layouts
6 |
7 | import "." as LibConfig
8 |
9 | LibConfig.TextArea {
10 | id: textArea
11 |
12 | // Load
13 | function valueToText(value) {
14 | if (value) {
15 | return value.join("\n")
16 | } else {
17 | return ""
18 | }
19 | }
20 |
21 | // Save
22 | function textToValue(text) {
23 | if (text) {
24 | return text.split("\n")
25 | } else {
26 | return []
27 | }
28 | }
29 |
30 | // Modify
31 | function prepend(str) {
32 | textArea.focus = true
33 | textArea.select(0, 0) // Make sure the text area has focus or we'll enter a loop.
34 | textArea.text = str + '\n' + textArea.text
35 | }
36 |
37 | function append(str) {
38 | textArea.focus = true
39 | textArea.select(0, 0) // Make sure the text area has focus or we'll enter a loop.
40 | textArea.text += '\n' + str
41 | }
42 |
43 | function hasItem(str) {
44 | var list = textToValue(textArea.text)
45 | for (var i = 0; i < list.length; i++) {
46 | if (list[i].trim() == str) {
47 | return true
48 | }
49 | }
50 | return false
51 | }
52 |
53 | function selectItem(str) {
54 | var start = textArea.text.indexOf(str)
55 | if (start >= 0) {
56 | textArea.select(start, start + str.length)
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/package/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "KPackageStructure": "Plasma/Applet",
3 | "KPlugin": {
4 | "Authors": [
5 | {
6 | "Email": "zrenfire@gmail.com",
7 | "Name": "Chris Holland"
8 | }
9 | ],
10 | "BugReportUrl": "https://github.com/Zren/plasma-applet-tiledmenu/issues",
11 | "Category": "Application Launchers",
12 | "Description": "A menu based on Windows 10's Start Menu.",
13 | "Description[es]": "Menú basado en el menú de inicio de Windows 10",
14 | "Description[fa]": "یک منو بر پایه ویندوز ۱۰",
15 | "Description[fi]": "Vailikko perustuu Windows 10: n käynnistä-valikkoon.",
16 | "Description[fr]": "Un menu basé sur le menu de démarrage de Windows 10.",
17 | "Description[he]": "תפריט שמבוסס על מראה תפריט ההתחלה של Windows 10.",
18 | "Description[ja]": "Windows 10 のスタートメニューに基づいたメニュー。",
19 | "Description[ko]": "Windows 10 시작메뉴를 기반으로 한 실행기",
20 | "Description[nl]": "Een menu a la het Windows 10-startmenu.",
21 | "Description[pt_BR]": "Um menu baseado no Start Menu do Windows 10.",
22 | "Description[ru]": "Меню, основанное на меню Пуск Windows 10.",
23 | "Description[sl]": "Meni, narejen po izgledu zaganjalnika v sistemu Windows 10",
24 | "Description[tr]": "Windows 10'un Başlat Menüsü'nü temel alan bir menü.",
25 | "Description[zh_TW]": "一個基於 Windows 10 開始功能表的選單。",
26 | "Icon": "start-here-kde",
27 | "Id": "com.github.zren.tiledmenu",
28 | "License": "GPL-2.0+",
29 | "Name": "Tiled Menu",
30 | "Name[es]": "Menú con Baldosas",
31 | "Name[fa]": "منو طرح کاشی",
32 | "Name[fi]": "Tiled Menu",
33 | "Name[fr]": "Tiled Menu",
34 | "Name[he]": "תפריט אריחים",
35 | "Name[ja]": "タイルメニュー",
36 | "Name[ko]": "타일 메뉴",
37 | "Name[nl]": "Tegelmenu",
38 | "Name[pt_BR]": "Menu em Ladrilhos",
39 | "Name[ru]": "Плиточное Меню",
40 | "Name[sl]": "Meni s ploščicami",
41 | "Name[tr]": "Döşeli Menü",
42 | "Name[zh_TW]": "方塊磚選單",
43 | "Version": "46",
44 | "Website": "https://github.com/Zren/plasma-applet-tiledmenu"
45 | },
46 | "X-Plasma-API-Minimum-Version": "6.0",
47 | "X-Plasma-Provides": [
48 | "org.kde.plasma.launchermenu"
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/package/translate/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Translate
2 |
3 | ## Status
4 |
5 | | Locale | Lines | % Done|
6 | |----------|---------|-------|
7 | | Template | 117 | |
8 | | de | 110/117 | 94% |
9 | | es | 113/117 | 96% |
10 | | fa | 117/117 | 100% |
11 | | fi | 113/117 | 96% |
12 | | fr | 116/117 | 99% |
13 | | he | 116/117 | 99% |
14 | | hr | 91/117 | 77% |
15 | | id | 99/117 | 84% |
16 | | ja | 117/117 | 100% |
17 | | ko | 114/117 | 97% |
18 | | nl | 117/117 | 100% |
19 | | pl | 101/117 | 86% |
20 | | pt | 96/117 | 82% |
21 | | pt_BR | 112/117 | 95% |
22 | | ro | 101/117 | 86% |
23 | | ru | 113/117 | 96% |
24 | | sl | 112/117 | 95% |
25 | | tr | 112/117 | 95% |
26 | | zh_CN | 88/117 | 75% |
27 | | zh_TW | 117/117 | 100% |
28 |
29 |
30 | ## New Translations
31 |
32 | * Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/Zren/plasma-applet-tiledmenu/issues/new), name the file `spanish.txt`, attach the txt file to the issue (drag and drop).
33 |
34 | Or if you know how to make a pull request
35 |
36 | * Copy the `template.pot` file and name it your locale's code (Eg: `en`/`de`/`fr`) with the extension `.po`. Then fill out all the `msgstr ""`.
37 | * Your region's locale code can be found at: https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes/28357857#28357857
38 |
39 | ## Scripts
40 |
41 | Zren's `kpac` script can easily run the `gettext` commands for you, parsing the `metadata.json` and filling out any placeholders for you. `kpac` can be [downloaded here](https://github.com/Zren/plasma-applet-lib/blob/master/kpac) and should be placed at `~/Code/plasmoid-widgetname/kpac` to edit translations at `~/Code/plasmoid-widgetname/package/translate/`.
42 |
43 |
44 | * `python3 ./kpac i18n` will parse the `i18n()` calls in the `*.qml` files and write it to the `template.pot` file. Then it will merge any changes into the `*.po` language files. Then it converts the `*.po` files to it's binary `*.mo` version and move it to `contents/locale/...` which will bundle the translations in the `*.plasmoid` without needing the user to manually install them.
45 | * `python3 ./kpac localetest` will convert the `.po` to the `*.mo` files then run `plasmoidviewer` (part of `plasma-sdk`).
46 |
47 | ## How it works
48 |
49 | Since KDE Frameworks v5.37, translations can be bundled with the zipped `*.plasmoid` file downloaded from the store.
50 |
51 | * `xgettext` extracts the messages from the source code into a `template.pot`.
52 | * Translators copy the `template.pot` to `fr.po` to translate the French language.
53 | * When the source code is updated, we use `msgmerge` to update the `fr.po` based on the updated `template.pot`.
54 | * When testing or releasing the widget, we convert the `.po` files to their binary `.mo` form with `msgfmt`.
55 |
56 | The binary `.mo` translation files are placed in `package/contents/locale/` so you may want to add `*.mo` to your `.gitignore`.
57 |
58 | ```
59 | package/contents/locale/fr/LC_MESSAGES/plasma_applet_com.github.zren.tiledmenu.mo
60 | ```
61 |
62 | ## Links
63 |
64 | * https://develop.kde.org/docs/plasma/widget/translations-i18n/
65 | * https://l10n.kde.org/stats/gui/trunk-kf5/team/fr/plasma-desktop/
66 | * https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems
67 | * https://api.kde.org/frameworks/ki18n/html/prg_guide.html
68 |
69 | > Version 8 of [Zren's i18n scripts](https://github.com/Zren/plasma-applet-lib).
70 |
--------------------------------------------------------------------------------
/uninstall:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Version 4
3 |
4 | # Eg: kpackagetool6 --type "Plasma/Applet" --remove package
5 |
6 | if [ -f "$PWD/package/metadata.json" ]; then # Plasma6 (and later versions of Plasma5)
7 | packageNamespace=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPlugin", {}).get("Id", ""))' < "$PWD/package/metadata.json"`
8 | packageServiceType=`python3 -c 'import sys, json; print(json.load(sys.stdin).get("KPackageStructure",""))' < "$PWD/package/metadata.json"`
9 | if [ -z "$packageServiceType" ]; then # desktoptojson will set KPlugin.ServiceTypes[0] instead of KPackageStructure
10 | packageServiceType=`python3 -c 'import sys, json; print((json.load(sys.stdin).get("KPlugin", {}).get("ServiceTypes", [])+[""])[0])' < "$PWD/package/metadata.json"`
11 | echo "[warning] metadata.json needs KPackageStructure set in Plasma6"
12 | fi
13 | elif [ -f "$PWD/package/metadata.desktop" ]; then # Plasma5
14 | packageNamespace=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
15 | packageServiceType=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-ServiceTypes"`
16 | else
17 | echo "[error] Could not find 'package/metadata.json' or 'package/metadata.desktop'"
18 | exit 1
19 | fi
20 | echo "Namespace: ${packageNamespace}"
21 | echo "Type: ${packageServiceType}"
22 | if [ -z "$packageServiceType" ]; then
23 | echo "[error] Could not parse metadata"
24 | exit 1
25 | fi
26 |
27 | if command -v kpackagetool6 &> /dev/null ; then kpackagetool="kpackagetool6" # Plasma6
28 | elif command -v kpackagetool5 &> /dev/null ; then kpackagetool="kpackagetool5" # Plasma5
29 | else
30 | echo "[error] Could not find 'kpackagetool6'"
31 | exit 1
32 | fi
33 |
34 | # Eg: kpackagetool6 --type "Plasma/Applet" --remove package
35 | "$kpackagetool" -t "${packageServiceType}" -r package
36 |
--------------------------------------------------------------------------------