├── images ├── preview.png └── TahoeLauncher.jpg ├── .github └── FUNDING.yml ├── contents ├── locale │ ├── nl │ │ └── LC_MESSAGES │ │ │ └── plasma_applet_TahoeLauncher.mo │ └── pl │ │ └── LC_MESSAGES │ │ └── plasma_applet_TahoeLauncher.mo ├── ui │ ├── js │ │ └── colorType.js │ ├── icons │ │ ├── lucide │ │ │ ├── LICENSE │ │ │ ├── flask-conical.svg │ │ │ ├── film.svg │ │ │ ├── gamepad-2.svg │ │ │ ├── cpu.svg │ │ │ └── paperclip.svg │ │ ├── feather │ │ │ ├── LICENSE │ │ │ ├── star.svg │ │ │ ├── code.svg │ │ │ ├── tool.svg │ │ │ ├── pwr.svg │ │ │ ├── search.svg │ │ │ ├── image.svg │ │ │ ├── globe.svg │ │ │ ├── trash-2.svg │ │ │ ├── file-text.svg │ │ │ └── stngs.svg │ │ └── AppsIcon.svg │ ├── CategoryPill.qml │ ├── Scrollbar.qml │ ├── SearchBar.qml │ ├── AppCategorySwitcher.qml │ ├── Highlight.qml │ ├── SearchList.qml │ ├── CompactRepresentation.qml │ ├── MenuButton.qml │ ├── AppsCategorized.qml │ ├── ActionMenu.qml │ ├── AllAppsList.qml │ ├── AppListViewDelegate.qml │ ├── MainView.qml │ ├── AppGridViewDelegate.qml │ ├── main.qml │ ├── MenuRepresentation.qml │ ├── AppGridView.qml │ ├── AppListView.qml │ └── ConfigGeneral.qml ├── config │ ├── config.qml │ └── main.xml └── code │ └── tools.js ├── metadata.json ├── README.md ├── translate ├── ReadMe.md ├── template.pot ├── pl.po └── nl.po └── LICENSE /images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EliverLara/TahoeLauncher/HEAD/images/preview.png -------------------------------------------------------------------------------- /images/TahoeLauncher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EliverLara/TahoeLauncher/HEAD/images/TahoeLauncher.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: EliverLara 2 | ko_fi: eliverlara 3 | custom: https://www.paypal.me/EliverLara/ 4 | -------------------------------------------------------------------------------- /contents/locale/nl/LC_MESSAGES/plasma_applet_TahoeLauncher.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EliverLara/TahoeLauncher/HEAD/contents/locale/nl/LC_MESSAGES/plasma_applet_TahoeLauncher.mo -------------------------------------------------------------------------------- /contents/locale/pl/LC_MESSAGES/plasma_applet_TahoeLauncher.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EliverLara/TahoeLauncher/HEAD/contents/locale/pl/LC_MESSAGES/plasma_applet_TahoeLauncher.mo -------------------------------------------------------------------------------- /contents/ui/js/colorType.js: -------------------------------------------------------------------------------- 1 | function isDark(color) { 2 | 3 | var r = color.r; 4 | var g = color.g; 5 | var b = color.b; 6 | 7 | // Using the HSP value, determine whether the color is light or dark 8 | var colorArray = [r, g , b ].map(v => { 9 | if (v <= 0.03928) { 10 | return v / 12.92 11 | } 12 | 13 | return Math.pow((v + 0.055) / 1.055, 2.4) 14 | }) 15 | 16 | var luminance = 0.2126 * colorArray[0] + 0.7152 * colorArray[1] + 0.0722 * colorArray[2] 17 | 18 | return luminance <= 0.179 19 | } -------------------------------------------------------------------------------- /contents/ui/icons/lucide/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Lucide Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /contents/ui/CategoryPill.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import org.kde.kirigami as Kirigami 3 | 4 | Text { 5 | id: pill 6 | 7 | property bool selected: false 8 | signal clicked 9 | 10 | topPadding: 4 11 | bottomPadding: topPadding 12 | leftPadding: 8 13 | rightPadding: 8 14 | font.pointSize: 10 15 | 16 | color: selected ? "#000000" : main.textColor 17 | opacity: selected ? 0.8 : 0.4 18 | 19 | Rectangle { 20 | id: pillBg 21 | anchors.fill: parent 22 | anchors.centerIn: parent 23 | z: -1 24 | color: selected ? Qt.rgba(255, 255, 255, 0.5) : main.isDarkTheme ? Qt.rgba(255, 255, 255, 0.2) : Qt.rgba(255, 255, 255, 0.5) 25 | radius: 8 26 | } 27 | 28 | MouseArea { 29 | anchors.fill: pill 30 | onClicked: pill.clicked(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPackageStructure": "Plasma/Applet", 3 | "KPlugin": { 4 | "Authors": [ 5 | { 6 | "Email": "eliverlara@gmail.com", 7 | "Name": "EliverLara" 8 | } 9 | ], 10 | "Category": "Application Launchers", 11 | "Description": "A modern Launcher for plasma inspired on MacOS!", 12 | "Description[nl]": "Een moderne programmastarter voor Plasma, geïnspireerd op die van macOS!", 13 | "Description[pl]": "Nowoczesny launcher dla Plasma inspirowany macOS!", 14 | "EnabledByDefault": true, 15 | "Icon": "start-here-kde", 16 | "Id": "TahoeLauncher", 17 | "Name": "Tahoe Launcher", 18 | "Name[nl]": "Tahoe-programmastarter", 19 | "Name[pl]": "Tahoe Launcher", 20 | "Version": "0.1", 21 | "Website": "https://github.com/EliverLara/TahoeLauncher" 22 | }, 23 | "X-Plasma-API-Minimum-Version": "6.0", 24 | "X-Plasma-Provides": [ 25 | "org.kde.plasma.launchermenu" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /contents/ui/Scrollbar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls.Basic 3 | 4 | ScrollBar { 5 | id: control 6 | z: 2 7 | implicitWidth: 12 8 | padding: 0 9 | 10 | contentItem: Rectangle { 11 | implicitWidth: 10 12 | implicitHeight: 100 13 | radius: width / 2 14 | color: control.pressed ? main.textColor : main.dimmedTextColor 15 | // Hide the ScrollBar when it's not needed. 16 | opacity: control.policy === ScrollBar.AlwaysOn || (control.active && control.size < 1.0) ? 0.75 : 0 17 | 18 | // Animate the changes in opacity (default duration is 250 ms). 19 | Behavior on opacity { 20 | NumberAnimation {} 21 | } 22 | } 23 | 24 | 25 | background: Rectangle{ 26 | color: control.active ? main.contrastBgColor : "transparent" 27 | opacity: control.policy === ScrollBar.AlwaysOn || (control.active && control.size < 1.0) ? 1 : 0 28 | 29 | // Animate the changes in opacity (default duration is 250 ms). 30 | Behavior on opacity { 31 | NumberAnimation {} 32 | } 33 | radius: width / 2 34 | } 35 | } -------------------------------------------------------------------------------- /contents/ui/icons/feather/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Cole Bemis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Tahoe Launcher 4 | 5 | A modern Launcher for KDE plasma inspired on MacOS! 6 | 7 | ![preview](images/preview.png) 8 | ![settings](images/TahoeLauncher.jpg) 9 | 10 |
11 | 12 | ## Dependencies 13 | - KDE Plasma >= 6.0 14 | 15 | ## Easy Installation 16 | #### KDE Store (Preferred way) 17 | 1. Right click on the desktop 18 | 2. Click on "Add Widgets" 19 | 3. Click on "Get New Widgets" 20 | 4. Click on "Download New Plasma Widgets" 21 | 5. Search for "TahoeLauncher" 22 | 6. Click on "Install" and you're done! 23 | 24 | ## Usage 25 | 1. Right click on the desktop. 26 | 2. Click "Add New Widgets" 27 | 3. Search for "TahoeLauncher" 28 | 4. Drag and drop to your desired place. 29 | 30 | ## Internationalization 31 | 32 | If you would like to contribute by adding translations for your language, please follow the instructions in the [Translations Readme](translate/ReadMe.md). 33 | 34 | ## Support Me ♥ 35 | If you enjoy what I do, consider supporting me, every little bit means the world! 36 |
37 | 38 | Buy Me A Coffee 39 | Done using PayPal 40 | 41 | ## License 42 | [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) -------------------------------------------------------------------------------- /contents/ui/icons/feather/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 45 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 48 | 49 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 45 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/pwr.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 51 | 52 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 46 | 53 | 54 | -------------------------------------------------------------------------------- /contents/config/config.qml: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 by Eike Hein * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ***************************************************************************/ 19 | 20 | import QtQuick 2.15 21 | 22 | import org.kde.plasma.configuration 2.0 23 | 24 | ConfigModel { 25 | ConfigCategory { 26 | name: i18n("General") 27 | icon: "preferences-desktop-plasma" 28 | source: "ConfigGeneral.qml" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contents/ui/SearchBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | import QtQuick.Controls 4 | import org.kde.kirigami as Kirigami 5 | 6 | RowLayout { 7 | 8 | property alias textField: textField 9 | property alias showMenuButton: menuButton.visible 10 | 11 | Kirigami.Icon { 12 | id: searchIcon 13 | Layout.rightMargin: 0 14 | source: main.showAllApps ? Qt.resolvedUrl('icons/AppsIcon.svg') : "favorite-symbolic" 15 | isMask: main.showAllApps 16 | color: main.dimmedTextColor 17 | MouseArea { 18 | anchors.fill: parent 19 | onClicked: { 20 | main.showAllApps = !main.showAllApps; 21 | textField.forceActiveFocus(Qt.BacktabFocusReason) 22 | } 23 | } 24 | } 25 | 26 | TextField { 27 | id: textField 28 | Layout.fillHeight: true 29 | Layout.fillWidth: true 30 | font.pointSize: 18 31 | 32 | placeholderText: i18n("Applications") 33 | placeholderTextColor: main.dimmedTextColor 34 | background: Rectangle{ 35 | color: "transparent" 36 | } 37 | focus: true 38 | onTextChanged: { 39 | textField.forceActiveFocus(Qt.ShortcutFocusReason) 40 | runnerModel.query = text; 41 | } 42 | 43 | Keys.onPressed: event => { 44 | if (event.key == Qt.Key_Escape) { 45 | event.accepted = true; 46 | if (searching) { 47 | clear(); 48 | } else { 49 | root.toggle() 50 | } 51 | } 52 | 53 | } 54 | } 55 | 56 | MenuButton { 57 | id: menuButton 58 | } 59 | } -------------------------------------------------------------------------------- /contents/ui/icons/feather/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 49 | 55 | 59 | 60 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 46 | 53 | 57 | 58 | -------------------------------------------------------------------------------- /contents/ui/AppCategorySwitcher.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | ScrollView { 5 | id: scrollview 6 | 7 | property alias model: categorySwitcher.model 8 | 9 | signal categorySwitched(int index) 10 | 11 | ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 12 | ScrollBar.vertical.policy: ScrollBar.AlwaysOff 13 | 14 | AppListView { 15 | id: categorySwitcher 16 | spacing: 7 17 | orientation: ListView.Horizontal 18 | showScrollbar: false 19 | property var contentHeight: 0 20 | 21 | anchors.topMargin: (parent.height - contentHeight ) /2 22 | anchors.fill: parent 23 | anchors.centerIn: parent 24 | 25 | delegate: CategoryPill { 26 | id: del 27 | required property var model 28 | required property var index 29 | selected: categorySwitcher.currentIndex == index 30 | text: model.name 31 | 32 | onClicked: categorySwitcher.currentIndex = index 33 | 34 | Component.onCompleted: { 35 | categorySwitcher.contentHeight = del.height 36 | } 37 | } 38 | 39 | onCurrentIndexChanged: categorySwitched(categorySwitcher.currentItem.model.modelIndex) 40 | } 41 | 42 | MouseArea { 43 | anchors.fill: parent 44 | // Accept mouse wheel events, but pass other events through to the ScrollView 45 | acceptedButtons: Qt.NoButton 46 | hoverEnabled: true 47 | 48 | onWheel: (wheel) => { 49 | // Check if the scroll wheel is moving vertically 50 | if (wheel.angleDelta.y !== 0) { 51 | 52 | categorySwitcher.flick(wheel.angleDelta.y * 15, 0); 53 | 54 | wheel.accepted = true; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /contents/ui/icons/feather/trash-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 48 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/file-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 44 | 48 | 55 | 62 | 66 | 67 | -------------------------------------------------------------------------------- /contents/ui/icons/feather/stngs.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 40 | 46 | 50 | 51 | -------------------------------------------------------------------------------- /contents/ui/icons/lucide/flask-conical.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 39 | 43 | 47 | 51 | 52 | -------------------------------------------------------------------------------- /contents/ui/icons/lucide/film.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 39 | 48 | 55 | 62 | 69 | 76 | 83 | 90 | 97 | 98 | -------------------------------------------------------------------------------- /contents/ui/icons/lucide/gamepad-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 37 | 41 | 45 | 49 | 50 | -------------------------------------------------------------------------------- /contents/ui/Highlight.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import org.kde.ksvg as KSvg 3 | import org.kde.kirigami as Kirigami 4 | import Qt5Compat.GraphicalEffects 5 | 6 | Rectangle { 7 | id: highlight 8 | /*! 9 | This property holds whether the control is hovered. 10 | 11 | This is set automatically when used in a ListView and GridView. 12 | */ 13 | property bool hovered: ListView.view !== null || GridView.view !== null 14 | 15 | /*! 16 | This property holds whether the highlight has a pressed appearance. 17 | */ 18 | property bool pressed: false 19 | 20 | /*! 21 | \qmlproperty int Highlight::marginHints 22 | 23 | This property holds the margin hints used by the background. 24 | */ 25 | property alias marginHints: background.margins 26 | 27 | /*! 28 | This property holds whether the item is active. True by default. Set it to 29 | false to visually mark an item that's in the "current item" or "selected" 30 | state but is not currently being hovered. 31 | */ 32 | property bool active: true 33 | 34 | /*! 35 | This property holds whether the item should not show a background. False by default. Set it to 36 | true to visually hide the background, mainly used when glow is enabled in plasmoid config 37 | */ 38 | property bool hideBg: true 39 | 40 | width: { 41 | const view = ListView.view; 42 | return view ? view.width - view.leftMargin - view.rightMargin : undefined; 43 | } 44 | 45 | radius: 10 46 | z: -20 47 | color: main.contrastBgColor//"transparent" 48 | clip: true 49 | 50 | // apply rounded corners mask 51 | layer.enabled: true 52 | layer.effect: OpacityMask { 53 | maskSource: Rectangle { 54 | x: highlight.x; y: highlight.y 55 | width: highlight.width 56 | height: highlight.height 57 | radius: highlight.radius 58 | } 59 | } 60 | 61 | KSvg.FrameSvgItem { 62 | id: background 63 | 64 | anchors.fill: parent 65 | 66 | opacity: highlight.hideBg ? 0 : 1 67 | 68 | imagePath: "widgets/viewitem" 69 | prefix: { 70 | if (highlight.pressed) { 71 | return highlight.hovered ? 'selected+hover' : 'selected'; 72 | } 73 | 74 | return highlight.hovered ? 'hover' : 'normal'; 75 | } 76 | 77 | Behavior on opacity { 78 | enabled: Kirigami.Units.veryShortDuration > 0 79 | NumberAnimation { 80 | duration: Kirigami.Units.veryShortDuration 81 | easing.type: Easing.OutQuad 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /contents/ui/SearchList.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2022 by Friedrich Schriewer * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ****************************************************************************/ 19 | 20 | import QtQuick 21 | import org.kde.plasma.extras as PlasmaExtras 22 | import QtQuick.Controls 23 | import org.kde.kirigami as Kirigami 24 | 25 | AppListView { 26 | id: searchList 27 | 28 | Loader { 29 | anchors.fill: parent 30 | width: searchList.width - (Kirigami.Units.gridUnit * 4) 31 | 32 | active: searchList.count === 0 33 | visible: active 34 | asynchronous: true 35 | 36 | sourceComponent: PlasmaExtras.PlaceholderMessage { 37 | id: emptyHint 38 | 39 | iconName: "edit-none" 40 | opacity: 0 41 | text: i18nc("@info:status", "No matches") 42 | 43 | Connections { 44 | target: runnerModel 45 | function onQueryFinished() { 46 | showAnimation.restart() 47 | } 48 | } 49 | 50 | NumberAnimation { 51 | id: showAnimation 52 | duration: Kirigami.Units.longDuration 53 | easing.type: Easing.OutCubic 54 | property: "opacity" 55 | target: emptyHint 56 | to: 1 57 | } 58 | } 59 | } 60 | 61 | Connections { 62 | target: runnerModel 63 | function onQueryChanged() { 64 | searchList.model = runnerModel.modelForRow(0) 65 | searchList.blockingHoverFocus = true 66 | searchList.interceptedPosition = null 67 | searchList.currentIndex = 0 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /contents/ui/icons/lucide/cpu.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 39 | 48 | 55 | 62 | 69 | 76 | 83 | 90 | 97 | 104 | 111 | 112 | -------------------------------------------------------------------------------- /translate/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Translate 2 | 3 | ## Status 4 | 5 | | Locale | Lines | % Done| 6 | |----------|---------|-------| 7 | | Template | 39 | | 8 | | nl | 39/39 | 100% | 9 | | pl | 39/39 | 100% | 10 | 11 | 12 | ## New Translations 13 | 14 | * Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/EliverLara/TahoeLauncher/issues/new), name the file `spanish.txt`, attach the txt file to the issue (drag and drop). 15 | 16 | Or if you know how to make a pull request 17 | 18 | * 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 ""`. 19 | * Your region's locale code can be found at: https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes/28357857#28357857 20 | 21 | ## Scripts 22 | 23 | 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/`. 24 | 25 | 26 | * `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. 27 | * `python3 ./kpac localetest` will convert the `.po` to the `*.mo` files then run `plasmoidviewer` (part of `plasma-sdk`). 28 | 29 | ## How it works 30 | 31 | Since KDE Frameworks v5.37, translations can be bundled with the zipped `*.plasmoid` file downloaded from the store. 32 | 33 | * `xgettext` extracts the messages from the source code into a `template.pot`. 34 | * Translators copy the `template.pot` to `fr.po` to translate the French language. 35 | * When the source code is updated, we use `msgmerge` to update the `fr.po` based on the updated `template.pot`. 36 | * When testing or releasing the widget, we convert the `.po` files to their binary `.mo` form with `msgfmt`. 37 | 38 | The binary `.mo` translation files are placed in `package/contents/locale/` so you may want to add `*.mo` to your `.gitignore`. 39 | 40 | ``` 41 | package/contents/locale/fr/LC_MESSAGES/plasma_applet_TahoeLauncher.mo 42 | ``` 43 | 44 | ## Links 45 | 46 | * https://develop.kde.org/docs/plasma/widget/translations-i18n/ 47 | * https://l10n.kde.org/stats/gui/trunk-kf5/team/fr/plasma-desktop/ 48 | * https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems 49 | * https://api.kde.org/frameworks/ki18n/html/prg_guide.html 50 | 51 | > Version 8 of [Zren's i18n scripts](https://github.com/Zren/plasma-applet-lib). 52 | -------------------------------------------------------------------------------- /contents/ui/icons/lucide/paperclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 39 | 43 | 44 | -------------------------------------------------------------------------------- /contents/ui/CompactRepresentation.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2013-2014 by Eike Hein * 3 | * Copyright (C) 2021 by Prateek SU * 4 | * Copyright (C) 2022 by Friedrich Schriewer * 5 | * * 6 | * This program is free software; you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation; either version 2 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program; if not, write to the * 18 | * Free Software Foundation, Inc., * 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 20 | ****************************************************************************/ 21 | 22 | import QtQuick 2.15 23 | import QtQuick.Layouts 1.15 24 | 25 | import org.kde.plasma.plasmoid 2.0 26 | import org.kde.plasma.core as PlasmaCore 27 | 28 | import org.kde.kirigami as Kirigami 29 | 30 | Item { 31 | id: root 32 | 33 | property QtObject dashWindow: null 34 | readonly property bool useCustomButtonImage: (Plasmoid.configuration.useCustomButtonImage && Plasmoid.configuration.customButtonImage.length != 0) 35 | 36 | Kirigami.Icon { 37 | id: buttonIcon 38 | 39 | width: Plasmoid.configuration.activationIndicator ? parent.width * 0.65 : parent.width 40 | height: Plasmoid.configuration.activationIndicator ? parent.height * 0.65 : parent.height 41 | anchors.centerIn: parent 42 | 43 | source: useCustomButtonImage ? Plasmoid.configuration.customButtonImage : Plasmoid.configuration.icon 44 | 45 | active: mouseArea.containsMouse 46 | 47 | smooth: true 48 | 49 | } 50 | 51 | MouseArea 52 | { 53 | id: mouseArea 54 | 55 | anchors.fill: parent 56 | 57 | hoverEnabled: true 58 | 59 | onClicked: { 60 | dashWindow.visible = !dashWindow.visible; 61 | } 62 | } 63 | 64 | Component.onCompleted: { 65 | dashWindow = Qt.createQmlObject("MenuRepresentation {}", root); 66 | plasmoid.activated.connect(function() { 67 | dashWindow.visible = !dashWindow.visible; 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | start-here-kde-symbolic 12 | 13 | 14 | 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | false 25 | 26 | 27 | 28 | false 29 | 30 | 31 | 32 | true 33 | 34 | 35 | 36 | 2 37 | 38 | 39 | 40 | 0 41 | 42 | 43 | 44 | 0 45 | 46 | 47 | 48 | false 49 | 50 | 51 | 52 | false 53 | 54 | 55 | 56 | 3 57 | 58 | 59 | 60 | 5 61 | 62 | 63 | 64 | 4 65 | 66 | 67 | 68 | false 69 | 70 | 71 | 72 | true 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /contents/ui/MenuButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import org.kde.plasma.plasmoid 3 | import org.kde.plasma.components 3.0 as PC3 4 | import org.kde.plasma.extras as PlasmaExtras 5 | 6 | Item { 7 | 8 | width: button.width 9 | height: button.height 10 | 11 | property var menuModel: [ 12 | { 13 | title: i18n("Show apps in a list"), 14 | action: () => { 15 | plasmoid.configuration.showAllAppsInList = true; 16 | plasmoid.configuration.showAllAppsInGrid = false; 17 | plasmoid.configuration.showAllAppsCategorized = false; 18 | }, 19 | checked: plasmoid.configuration.showAllAppsInList, 20 | enabled: true 21 | }, 22 | { 23 | title: i18n("Show apps in a grid"), 24 | action: () => { 25 | plasmoid.configuration.showAllAppsInList = false; 26 | plasmoid.configuration.showAllAppsInGrid = true; 27 | plasmoid.configuration.showAllAppsCategorized = false; 28 | }, 29 | checked: plasmoid.configuration.showAllAppsInGrid, 30 | enabled: true 31 | }, 32 | { 33 | title: i18n("Show apps categorized"), 34 | action: () => { 35 | plasmoid.configuration.showAllAppsInList = false; 36 | plasmoid.configuration.showAllAppsInGrid = false; 37 | plasmoid.configuration.showAllAppsCategorized = true; 38 | }, 39 | checked: plasmoid.configuration.showAllAppsCategorized, 40 | enabled: main.showAllApps 41 | } 42 | ] 43 | 44 | PC3.RoundButton { 45 | id: button 46 | Accessible.role: Accessible.ButtonMenu 47 | 48 | flat: true 49 | // Make it look pressed while the menu is open 50 | down: contextMenu.status === PlasmaExtras.Menu.Open || pressed 51 | 52 | icon.name: "application-menu" 53 | 54 | background: Rectangle { 55 | color: button.down ? main.contrastBgColor : "transparent" 56 | radius: height / 2 57 | } 58 | onPressed: contextMenu.openRelative() 59 | } 60 | 61 | Instantiator { 62 | model: menuModel 63 | delegate: PlasmaExtras.MenuItem { 64 | required property int index 65 | required property var model 66 | 67 | icon: model.checked ? "checkmark-symbolic" : "" 68 | checked: model.checked 69 | text: model.title 70 | enabled: model.enabled 71 | // icon: model.decoration 72 | onClicked: model.action() 73 | } 74 | onObjectAdded: (index, object) => contextMenu.addMenuItem(object) 75 | onObjectRemoved: (index, object) => contextMenu.removeMenuItem(object) 76 | } 77 | 78 | PlasmaExtras.Menu { 79 | id: contextMenu 80 | visualParent: button 81 | placement: PlasmaExtras.Menu.BottomPosedLeftAlignedPopup 82 | onStatusChanged: { 83 | if ( contextMenu.status === PlasmaExtras.Menu.Closed && Qt.application.layoutDirection == Qt.LeftToRight) { 84 | nextItemInFocusChain(false).forceActiveFocus(Qt.BacktabFocusReason) 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contents/ui/AppsCategorized.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | AppListView { 6 | id: appsCategorized 7 | 8 | showSectionSeparator: false 9 | highlightFollowsCurrentItem: false 10 | spacing: 0 11 | 12 | delegate: ColumnLayout { 13 | id: category 14 | 15 | property var currentCategory: slicedCategories[index] 16 | property bool expanded: false 17 | 18 | width: appsCategorized.availableWidth 19 | height: categoryHeader.height + root.cellSizeHeight 20 | clip: true 21 | spacing: 0 22 | 23 | ColumnLayout { 24 | id: categoryHeader 25 | width: parent.width 26 | spacing: 10 27 | 28 | /* 29 | * Provides appearance of spacing in the bottom of each category grid 30 | */ 31 | Item { 32 | Layout.fillWidth: true 33 | Layout.fillHeight: true 34 | visible: index > 0 // not showing on the first item 35 | } 36 | Rectangle { 37 | id: separator 38 | width: parent.width 39 | height: 1.5 40 | // not showing on the first item to avoid duplicating with searchbar separator 41 | color: index > 0 ? main.contrastBgColor : "transparent" 42 | } 43 | RowLayout { 44 | Layout.fillWidth: true 45 | 46 | Text { 47 | text:currentCategory.name 48 | font.bold: true 49 | font.pixelSize: 15 50 | color: main.textColor 51 | } 52 | 53 | Item { 54 | Layout.fillWidth: true 55 | Layout.fillHeight: true 56 | } 57 | 58 | Text { 59 | Layout.alignment: Qt.AlignHCenter | Qt.AlignRight 60 | text: category.expanded ? i18n("Show less") : i18n("Show more") 61 | visible: grid.rows > 1 62 | font.bold: true 63 | font.pixelSize: 15 64 | color: main.textColor 65 | MouseArea { 66 | anchors.fill: parent 67 | onClicked: { 68 | category.expanded = !category.expanded 69 | } 70 | } 71 | } 72 | } 73 | 74 | /* 75 | * Provides appearance of spacing between category header 76 | * and the start of each category grid 77 | */ 78 | 79 | Item { 80 | Layout.fillWidth: true 81 | Layout.fillHeight: true 82 | } 83 | } 84 | 85 | 86 | GridView { 87 | id: grid 88 | 89 | leftMargin: fs.innerPadding / 2 // Centers the grid 90 | 91 | property var rows: { 92 | if(grid.model.count%root.columns == 0 ) { 93 | return Math.floor(grid.model.count/root.columns); 94 | } 95 | return Math.floor((grid.model.count/root.columns)+1); 96 | } 97 | 98 | property var expandedHeight: rows * root.cellSizeHeight 99 | property bool canMoveWithKeyboard: false 100 | 101 | interactive: false 102 | width: appsCategorized.availableWidth 103 | height: expandedHeight 104 | cellWidth: root.cellSizeWidth 105 | cellHeight: root.cellSizeHeight 106 | model: rootModel.modelForRow(currentCategory.modelIndex); 107 | 108 | delegate: AppGridViewDelegate { 109 | triggerModel: grid.model 110 | } 111 | } 112 | 113 | onExpandedChanged: updateHeight() 114 | 115 | Behavior on height { 116 | NumberAnimation { duration: 200 } 117 | } 118 | 119 | function updateHeight () { 120 | if(category.expanded) { 121 | category.height = grid.expandedHeight + categoryHeader.height; 122 | }else { 123 | category.height = root.cellSizeHeight + categoryHeader.height; 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /contents/ui/ActionMenu.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013 Aurélien Gâteau 3 | SPDX-FileCopyrightText: 2014-2015 Eike Hein 4 | 5 | SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 2.15 9 | 10 | import org.kde.plasma.extras 2.0 as PlasmaExtras 11 | 12 | Item { 13 | id: root 14 | 15 | property QtObject menu 16 | property Item visualParent 17 | property variant actionList 18 | property bool opened: menu ? (menu.status !== PlasmaExtras.Menu.Closed) : false 19 | 20 | signal actionClicked(string actionId, variant actionArgument) 21 | signal closed 22 | 23 | onActionListChanged: refreshMenu(); 24 | 25 | onOpenedChanged: { 26 | if (!opened) { 27 | closed(); 28 | } 29 | } 30 | 31 | function open(x, y) { 32 | if (!actionList) { 33 | return; 34 | } 35 | 36 | if (x && y) { 37 | menu.open(x, y); 38 | } else { 39 | menu.open(); 40 | } 41 | } 42 | 43 | function refreshMenu() { 44 | if (menu) { 45 | menu.destroy(); 46 | } 47 | 48 | if (!actionList) { 49 | return; 50 | } 51 | 52 | menu = contextMenuComponent.createObject(root); 53 | 54 | fillMenu(menu, actionList); 55 | } 56 | 57 | function fillMenu(menu, items) { 58 | items.forEach(function(actionItem) { 59 | if (actionItem.subActions) { 60 | // This is a menu 61 | var submenuItem = contextSubmenuItemComponent.createObject( 62 | menu, { "actionItem" : actionItem }); 63 | 64 | fillMenu(submenuItem.submenu, actionItem.subActions); 65 | 66 | } else { 67 | var item = contextMenuItemComponent.createObject( 68 | menu, 69 | { 70 | "actionItem": actionItem, 71 | } 72 | ); 73 | } 74 | }); 75 | 76 | } 77 | 78 | Component { 79 | id: contextMenuComponent 80 | 81 | PlasmaExtras.Menu { 82 | visualParent: root.visualParent 83 | } 84 | } 85 | 86 | Component { 87 | id: contextSubmenuItemComponent 88 | 89 | PlasmaExtras.MenuItem { 90 | id: submenuItem 91 | 92 | property variant actionItem 93 | 94 | text: actionItem.text ? actionItem.text : "" 95 | icon: actionItem.icon ? actionItem.icon : null 96 | 97 | property PlasmaExtras.Menu submenu: PlasmaExtras.Menu { 98 | visualParent: submenuItem.action 99 | } 100 | } 101 | } 102 | 103 | Component { 104 | id: contextMenuItemComponent 105 | 106 | PlasmaExtras.MenuItem { 107 | property variant actionItem 108 | 109 | text : actionItem.text ? actionItem.text : "" 110 | enabled : actionItem.type !== "title" && ("enabled" in actionItem ? actionItem.enabled : true) 111 | separator : actionItem.type === "separator" 112 | section : actionItem.type === "title" 113 | icon : actionItem.icon ? actionItem.icon : null 114 | checkable : actionItem.checkable ? actionItem.checkable : false 115 | checked : actionItem.checked ? actionItem.checked : false 116 | 117 | onClicked: { 118 | root.actionClicked(actionItem.actionId, actionItem.actionArgument); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /contents/ui/AllAppsList.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.15 3 | import QtQuick.Layouts 1.0 4 | import org.kde.draganddrop 2.0 5 | 6 | ColumnLayout { 7 | id: allApps 8 | spacing: 0 9 | 10 | property QtObject allAppsModel: rootModel.modelForRow(2) 11 | property QtObject recentAppsModel 12 | property QtObject currentModel: rootModel.modelForRow(2) 13 | 14 | property var currentStateIndex: 0// Plasmoid.configuration.defaultPage 15 | 16 | property bool showItemsInGrid: plasmoid.configuration.showAllAppsInGrid 17 | property bool showItemsInList: plasmoid.configuration.showAllAppsInList 18 | property bool showItemsCategorized: plasmoid.configuration.showAllAppsCategorized 19 | 20 | property Component preferredAppsViewComponent: showItemsInGrid ? applicationsGridViewComponent 21 | : showItemsInList ? applicationsListViewComponent 22 | : applicationsCategorizedViewComponent 23 | 24 | property alias viewItem: appViewLoader.item 25 | 26 | property var appsCategoriesList: { 27 | 28 | var categories = []; 29 | var categoryName; 30 | var categoryIcon; 31 | 32 | for (var i = 2; i < rootModel.count - 2; i++) { 33 | categoryName = rootModel.data(rootModel.index(i, 0), Qt.DisplayRole); 34 | categoryIcon = rootModel.data(rootModel.index(i, 0), Qt.DecorationRole); 35 | categories.push({ 36 | name: categoryName, 37 | modelIndex: i, 38 | icon: categoryIcon 39 | }); 40 | } 41 | allApps.allAppsModel = rootModel.modelForRow(2) 42 | allApps.currentModel = rootModel.modelForRow(2) 43 | return categories; 44 | } 45 | 46 | property var slicedCategories: appsCategoriesList.slice(1) 47 | 48 | function updateShowedModel(index){ 49 | currentModel = rootModel.modelForRow(index); 50 | } 51 | 52 | function reset(){ 53 | currentStateIndex = 0 54 | } 55 | 56 | Connections { 57 | target: main 58 | 59 | function onShowAllAppsChanged() { 60 | // We can't show favorites/recent apps categorized 61 | if(showItemsCategorized) { 62 | plasmoid.configuration.showAllAppsCategorized = false; 63 | plasmoid.configuration.showAllAppsInList = true; 64 | } 65 | } 66 | } 67 | 68 | 69 | AppCategorySwitcher { 70 | id: categorySwitcher 71 | 72 | Layout.preferredWidth: parent.width-fs.innerPadding 73 | Layout.preferredHeight: visible ? 40 : 0 74 | model: appsCategoriesList 75 | visible: !showItemsCategorized && main.showAllApps 76 | 77 | Component.onCompleted: { 78 | categorySwitcher.categorySwitched.connect(updateShowedModel) 79 | } 80 | } 81 | 82 | Loader { 83 | id: appViewLoader 84 | 85 | Layout.fillHeight: true 86 | Layout.fillWidth: true 87 | 88 | sourceComponent: preferredAppsViewComponent 89 | active: true 90 | } 91 | 92 | onPreferredAppsViewComponentChanged: { 93 | appViewLoader.sourceComponent = preferredAppsViewComponent; 94 | } 95 | 96 | Component { 97 | id: applicationsListViewComponent 98 | AppListView { 99 | id: appList 100 | 101 | anchors.fill: parent 102 | 103 | showSectionSeparator: false 104 | 105 | model: main.showAllApps ? currentModel : globalFavorites 106 | } 107 | } 108 | 109 | Component { 110 | id: applicationsGridViewComponent 111 | 112 | AppGridView { 113 | id: grid 114 | anchors.fill: parent 115 | anchors.leftMargin: fs.innerPadding / 2 116 | 117 | model: main.showAllApps ? currentModel : globalFavorites 118 | canMoveWithKeyboard: true 119 | //viewItem.highlightFollowsCurrentItem: false 120 | } 121 | } 122 | 123 | Component { 124 | id: applicationsCategorizedViewComponent 125 | AppsCategorized { 126 | model: slicedCategories 127 | anchors.fill: parent 128 | } 129 | } 130 | 131 | Component.onCompleted: { 132 | allApps.recentAppsModel = rootModel.modelForRow(0); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /translate/template.pot: -------------------------------------------------------------------------------- 1 | # Translation of TahoeLauncher in LANGUAGE 2 | # Copyright (C) 2025 3 | # This file is distributed under the same license as the TahoeLauncher package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: TahoeLauncher\n" 10 | "Report-Msgid-Bugs-To: https://github.com/EliverLara/TahoeLauncher\n" 11 | "POT-Creation-Date: 2025-12-10 17:24-0600\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../metadata.json 21 | msgid "Tahoe Launcher" 22 | msgstr "" 23 | 24 | #: ../metadata.json 25 | msgid "A modern Launcher for plasma inspired on MacOS!" 26 | msgstr "" 27 | 28 | #: ../contents/code/tools.js 29 | msgid "Remove from Favorites" 30 | msgstr "" 31 | 32 | #: ../contents/code/tools.js 33 | msgid "Add to Favorites" 34 | msgstr "" 35 | 36 | #: ../contents/code/tools.js 37 | msgid "On All Activities" 38 | msgstr "" 39 | 40 | #: ../contents/code/tools.js 41 | msgid "On the Current Activity" 42 | msgstr "" 43 | 44 | #: ../contents/code/tools.js 45 | msgid "Show in Favorites" 46 | msgstr "" 47 | 48 | #: ../contents/config/config.qml 49 | msgid "General" 50 | msgstr "" 51 | 52 | #: ../contents/ui/AppsCategorized.qml 53 | msgid "Show less" 54 | msgstr "" 55 | 56 | #: ../contents/ui/AppsCategorized.qml 57 | msgid "Show more" 58 | msgstr "" 59 | 60 | #: ../contents/ui/ConfigGeneral.qml 61 | msgid "Icon:" 62 | msgstr "" 63 | 64 | #: ../contents/ui/ConfigGeneral.qml 65 | msgctxt "@item:inmenu Open icon chooser dialog" 66 | msgid "Choose…" 67 | msgstr "" 68 | 69 | #: ../contents/ui/ConfigGeneral.qml 70 | msgctxt "@item:inmenu Reset icon to default" 71 | msgid "Clear Icon" 72 | msgstr "" 73 | 74 | #: ../contents/ui/ConfigGeneral.qml 75 | msgid "Launcher Positioning:" 76 | msgstr "" 77 | 78 | #: ../contents/ui/ConfigGeneral.qml 79 | msgid "Default" 80 | msgstr "" 81 | 82 | #: ../contents/ui/ConfigGeneral.qml 83 | msgid "Horizontal Center" 84 | msgstr "" 85 | 86 | #: ../contents/ui/ConfigGeneral.qml 87 | msgid "Screen Center" 88 | msgstr "" 89 | 90 | #: ../contents/ui/ConfigGeneral.qml 91 | msgid "Floating" 92 | msgstr "" 93 | 94 | #: ../contents/ui/ConfigGeneral.qml 95 | msgid "Offset Screen Edge (0 is Default):" 96 | msgstr "" 97 | 98 | #: ../contents/ui/ConfigGeneral.qml 99 | msgid "Offset Panel (0 is Default):" 100 | msgstr "" 101 | 102 | #: ../contents/ui/ConfigGeneral.qml 103 | msgid "Grids and lists" 104 | msgstr "" 105 | 106 | #: ../contents/ui/ConfigGeneral.qml 107 | msgid "Grid icon size:" 108 | msgstr "" 109 | 110 | #: ../contents/ui/ConfigGeneral.qml 111 | msgid "Small" 112 | msgstr "" 113 | 114 | #: ../contents/ui/ConfigGeneral.qml 115 | msgid "Medium" 116 | msgstr "" 117 | 118 | #: ../contents/ui/ConfigGeneral.qml 119 | msgid "Large" 120 | msgstr "" 121 | 122 | #: ../contents/ui/ConfigGeneral.qml 123 | msgid "Huge" 124 | msgstr "" 125 | 126 | #: ../contents/ui/ConfigGeneral.qml 127 | msgid "Number of columns in grid" 128 | msgstr "" 129 | 130 | #: ../contents/ui/ConfigGeneral.qml 131 | msgid "Number of rows in grid" 132 | msgstr "" 133 | 134 | #: ../contents/ui/ConfigGeneral.qml 135 | msgid "Lists:" 136 | msgstr "" 137 | 138 | #: ../contents/ui/ConfigGeneral.qml 139 | msgid "Compact list items" 140 | msgstr "" 141 | 142 | #: ../contents/ui/ConfigGeneral.qml 143 | msgid "Show list item description" 144 | msgstr "" 145 | 146 | #: ../contents/ui/ConfigGeneral.qml 147 | msgid "Use system font settings" 148 | msgstr "" 149 | 150 | #: ../contents/ui/ConfigGeneral.qml 151 | msgid "Enabled" 152 | msgstr "" 153 | 154 | #: ../contents/ui/main.qml 155 | msgid "Edit Applications…" 156 | msgstr "" 157 | 158 | #: ../contents/ui/MenuButton.qml 159 | msgid "Show apps in a list" 160 | msgstr "" 161 | 162 | #: ../contents/ui/MenuButton.qml 163 | msgid "Show apps in a grid" 164 | msgstr "" 165 | 166 | #: ../contents/ui/MenuButton.qml 167 | msgid "Show apps categorized" 168 | msgstr "" 169 | 170 | #: ../contents/ui/SearchBar.qml 171 | msgid "Applications" 172 | msgstr "" 173 | 174 | #: ../contents/ui/SearchList.qml 175 | msgctxt "@info:status" 176 | msgid "No matches" 177 | msgstr "" 178 | -------------------------------------------------------------------------------- /translate/pl.po: -------------------------------------------------------------------------------- 1 | # Translation of TahoeLauncher in Polish 2 | # Copyright (C) 2025 3 | # This file is distributed under the same license as the TahoeLauncher package. 4 | # RygorrMortis , 2025. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: TahoeLauncher\n" 10 | "Report-Msgid-Bugs-To: https://github.com/EliverLara/TahoeLauncher\n" 11 | "POT-Creation-Date: 2025-12-10 17:24-0600\n" 12 | "PO-Revision-Date: 2025-12-14 00:00+0000\n" 13 | "Last-Translator: RygorrMortis \n" 14 | "Language-Team: Polish \n" 15 | "Language: pl\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../metadata.json 21 | msgid "Tahoe Launcher" 22 | msgstr "Tahoe Launcher" 23 | 24 | #: ../metadata.json 25 | msgid "A modern Launcher for plasma inspired on MacOS!" 26 | msgstr "Nowoczesny launcher dla Plasma inspirowany macOS!" 27 | 28 | #: ../contents/code/tools.js 29 | msgid "Remove from Favorites" 30 | msgstr "Usuń z ulubionych" 31 | 32 | #: ../contents/code/tools.js 33 | msgid "Add to Favorites" 34 | msgstr "Dodaj do ulubionych" 35 | 36 | #: ../contents/code/tools.js 37 | msgid "On All Activities" 38 | msgstr "We wszystkich aktywnościach" 39 | 40 | #: ../contents/code/tools.js 41 | msgid "On the Current Activity" 42 | msgstr "W bieżącej aktywności" 43 | 44 | #: ../contents/code/tools.js 45 | msgid "Show in Favorites" 46 | msgstr "Pokaż w ulubionych" 47 | 48 | #: ../contents/config/config.qml 49 | msgid "General" 50 | msgstr "Ogólne" 51 | 52 | #: ../contents/ui/AppsCategorized.qml 53 | msgid "Show less" 54 | msgstr "Pokaż mniej" 55 | 56 | #: ../contents/ui/AppsCategorized.qml 57 | msgid "Show more" 58 | msgstr "Pokaż więcej" 59 | 60 | #: ../contents/ui/ConfigGeneral.qml 61 | msgid "Icon:" 62 | msgstr "Ikona:" 63 | 64 | #: ../contents/ui/ConfigGeneral.qml 65 | msgctxt "@item:inmenu Open icon chooser dialog" 66 | msgid "Choose…" 67 | msgstr "Wybierz…" 68 | 69 | #: ../contents/ui/ConfigGeneral.qml 70 | msgctxt "@item:inmenu Reset icon to default" 71 | msgid "Clear Icon" 72 | msgstr "Wyczyść ikonę" 73 | 74 | #: ../contents/ui/ConfigGeneral.qml 75 | msgid "Launcher Positioning:" 76 | msgstr "Pozycjonowanie launchera:" 77 | 78 | #: ../contents/ui/ConfigGeneral.qml 79 | msgid "Default" 80 | msgstr "Domyślne" 81 | 82 | #: ../contents/ui/ConfigGeneral.qml 83 | msgid "Horizontal Center" 84 | msgstr "Środek w poziomie" 85 | 86 | #: ../contents/ui/ConfigGeneral.qml 87 | msgid "Screen Center" 88 | msgstr "Środek ekranu" 89 | 90 | #: ../contents/ui/ConfigGeneral.qml 91 | msgid "Floating" 92 | msgstr "Pływający" 93 | 94 | #: ../contents/ui/ConfigGeneral.qml 95 | msgid "Offset Screen Edge (0 is Default):" 96 | msgstr "Odsunięcie od krawędzi ekranu (0 to domyślne):" 97 | 98 | #: ../contents/ui/ConfigGeneral.qml 99 | msgid "Offset Panel (0 is Default):" 100 | msgstr "Odsunięcie od panelu (0 to domyślne):" 101 | 102 | #: ../contents/ui/ConfigGeneral.qml 103 | msgid "Grids and lists" 104 | msgstr "Siatki i listy" 105 | 106 | #: ../contents/ui/ConfigGeneral.qml 107 | msgid "Grid icon size:" 108 | msgstr "Rozmiar ikon w siatce:" 109 | 110 | #: ../contents/ui/ConfigGeneral.qml 111 | msgid "Small" 112 | msgstr "Mały" 113 | 114 | #: ../contents/ui/ConfigGeneral.qml 115 | msgid "Medium" 116 | msgstr "Średni" 117 | 118 | #: ../contents/ui/ConfigGeneral.qml 119 | msgid "Large" 120 | msgstr "Duży" 121 | 122 | #: ../contents/ui/ConfigGeneral.qml 123 | msgid "Huge" 124 | msgstr "Ogromny" 125 | 126 | #: ../contents/ui/ConfigGeneral.qml 127 | msgid "Number of columns in grid" 128 | msgstr "Liczba kolumn w siatce" 129 | 130 | #: ../contents/ui/ConfigGeneral.qml 131 | msgid "Number of rows in grid" 132 | msgstr "Liczba wierszy w siatce" 133 | 134 | #: ../contents/ui/ConfigGeneral.qml 135 | msgid "Lists:" 136 | msgstr "Listy:" 137 | 138 | #: ../contents/ui/ConfigGeneral.qml 139 | msgid "Compact list items" 140 | msgstr "Kompaktowe elementy listy" 141 | 142 | #: ../contents/ui/ConfigGeneral.qml 143 | msgid "Show list item description" 144 | msgstr "Pokaż opis elementu listy" 145 | 146 | #: ../contents/ui/ConfigGeneral.qml 147 | msgid "Use system font settings" 148 | msgstr "Użyj systemowych ustawień czcionek" 149 | 150 | #: ../contents/ui/ConfigGeneral.qml 151 | msgid "Enabled" 152 | msgstr "Włączone" 153 | 154 | #: ../contents/ui/main.qml 155 | msgid "Edit Applications…" 156 | msgstr "Edytuj aplikacje…" 157 | 158 | #: ../contents/ui/MenuButton.qml 159 | msgid "Show apps in a list" 160 | msgstr "Pokaż aplikacje na liście" 161 | 162 | #: ../contents/ui/MenuButton.qml 163 | msgid "Show apps in a grid" 164 | msgstr "Pokaż aplikacje w siatce" 165 | 166 | #: ../contents/ui/MenuButton.qml 167 | msgid "Show apps categorized" 168 | msgstr "Pokaż aplikacje skategoryzowane" 169 | 170 | #: ../contents/ui/SearchBar.qml 171 | msgid "Applications" 172 | msgstr "Aplikacje" 173 | 174 | #: ../contents/ui/SearchList.qml 175 | msgctxt "@info:status" 176 | msgid "No matches" 177 | msgstr "Brak dopasowań" 178 | -------------------------------------------------------------------------------- /translate/nl.po: -------------------------------------------------------------------------------- 1 | # Translation of TahoeLauncher in nl 2 | # Copyright (C) 2025 3 | # This file is distributed under the same license as the TahoeLauncher package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: TahoeLauncher\n" 10 | "Report-Msgid-Bugs-To: https://github.com/EliverLara/TahoeLauncher\n" 11 | "POT-Creation-Date: 2025-12-10 17:24-0600\n" 12 | "PO-Revision-Date: 2025-12-12 10:59+0100\n" 13 | "Last-Translator: Heimen Stoffels \n" 14 | "Language-Team: \n" 15 | "Language: nl\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "X-Generator: Poedit 3.8\n" 20 | 21 | #: ../metadata.json 22 | msgid "Tahoe Launcher" 23 | msgstr "Tahoe-programmastarter" 24 | 25 | #: ../metadata.json 26 | msgid "A modern Launcher for plasma inspired on MacOS!" 27 | msgstr "Een moderne programmastarter voor Plasma, geïnspireerd op die van macOS!" 28 | 29 | #: ../contents/code/tools.js 30 | msgid "Remove from Favorites" 31 | msgstr "Verwijderen uit favorieten" 32 | 33 | #: ../contents/code/tools.js 34 | msgid "Add to Favorites" 35 | msgstr "Toevoegen aan favorieten" 36 | 37 | #: ../contents/code/tools.js 38 | msgid "On All Activities" 39 | msgstr "Op alle activiteiten" 40 | 41 | #: ../contents/code/tools.js 42 | msgid "On the Current Activity" 43 | msgstr "Op huidige activiteit" 44 | 45 | #: ../contents/code/tools.js 46 | msgid "Show in Favorites" 47 | msgstr "Tonen in favorieten" 48 | 49 | #: ../contents/config/config.qml 50 | msgid "General" 51 | msgstr "Algemeen" 52 | 53 | #: ../contents/ui/AppsCategorized.qml 54 | msgid "Show less" 55 | msgstr "Minder tonen" 56 | 57 | #: ../contents/ui/AppsCategorized.qml 58 | msgid "Show more" 59 | msgstr "Meer tonen" 60 | 61 | #: ../contents/ui/ConfigGeneral.qml 62 | msgid "Icon:" 63 | msgstr "Pictogram:" 64 | 65 | #: ../contents/ui/ConfigGeneral.qml 66 | msgctxt "@item:inmenu Open icon chooser dialog" 67 | msgid "Choose…" 68 | msgstr "Kiezen…" 69 | 70 | #: ../contents/ui/ConfigGeneral.qml 71 | msgctxt "@item:inmenu Reset icon to default" 72 | msgid "Clear Icon" 73 | msgstr "Pictogram wissen" 74 | 75 | #: ../contents/ui/ConfigGeneral.qml 76 | msgid "Launcher Positioning:" 77 | msgstr "Locatie van programmastarter:" 78 | 79 | #: ../contents/ui/ConfigGeneral.qml 80 | msgid "Default" 81 | msgstr "Standaard" 82 | 83 | #: ../contents/ui/ConfigGeneral.qml 84 | msgid "Horizontal Center" 85 | msgstr "Midden (horizontaal)" 86 | 87 | #: ../contents/ui/ConfigGeneral.qml 88 | msgid "Screen Center" 89 | msgstr "Midden van scherm" 90 | 91 | #: ../contents/ui/ConfigGeneral.qml 92 | msgid "Floating" 93 | msgstr "Zwevend" 94 | 95 | #: ../contents/ui/ConfigGeneral.qml 96 | msgid "Offset Screen Edge (0 is Default):" 97 | msgstr "Afstand tot schermrand (0 = standaard):" 98 | 99 | #: ../contents/ui/ConfigGeneral.qml 100 | msgid "Offset Panel (0 is Default):" 101 | msgstr "Afstand tot paneel (0 = standaard):" 102 | 103 | #: ../contents/ui/ConfigGeneral.qml 104 | msgid "Grids and lists" 105 | msgstr "Roosters en lijsten" 106 | 107 | #: ../contents/ui/ConfigGeneral.qml 108 | msgid "Grid icon size:" 109 | msgstr "Grootte van pictogrammen op rooster:" 110 | 111 | #: ../contents/ui/ConfigGeneral.qml 112 | msgid "Small" 113 | msgstr "Klein" 114 | 115 | #: ../contents/ui/ConfigGeneral.qml 116 | msgid "Medium" 117 | msgstr "Gemiddeld" 118 | 119 | #: ../contents/ui/ConfigGeneral.qml 120 | msgid "Large" 121 | msgstr "Groot" 122 | 123 | #: ../contents/ui/ConfigGeneral.qml 124 | msgid "Huge" 125 | msgstr "Enorm" 126 | 127 | #: ../contents/ui/ConfigGeneral.qml 128 | msgid "Number of columns in grid" 129 | msgstr "Aantal kolommen op rooster" 130 | 131 | #: ../contents/ui/ConfigGeneral.qml 132 | msgid "Number of rows in grid" 133 | msgstr "Aantal rijen op rooster" 134 | 135 | #: ../contents/ui/ConfigGeneral.qml 136 | msgid "Lists:" 137 | msgstr "Lijsten:" 138 | 139 | #: ../contents/ui/ConfigGeneral.qml 140 | msgid "Compact list items" 141 | msgstr "Compacte lijstitems" 142 | 143 | #: ../contents/ui/ConfigGeneral.qml 144 | msgid "Show list item description" 145 | msgstr "Beschrijvingen van lijstitems tonen" 146 | 147 | #: ../contents/ui/ConfigGeneral.qml 148 | msgid "Use system font settings" 149 | msgstr "Systeemlettertype gebruiken" 150 | 151 | #: ../contents/ui/ConfigGeneral.qml 152 | msgid "Enabled" 153 | msgstr "Ingeschakeld" 154 | 155 | #: ../contents/ui/main.qml 156 | msgid "Edit Applications…" 157 | msgstr "Programma's bewerken…" 158 | 159 | #: ../contents/ui/MenuButton.qml 160 | msgid "Show apps in a list" 161 | msgstr "Programma's als lijst tonen" 162 | 163 | #: ../contents/ui/MenuButton.qml 164 | msgid "Show apps in a grid" 165 | msgstr "Programma's als rooster tonen" 166 | 167 | #: ../contents/ui/MenuButton.qml 168 | msgid "Show apps categorized" 169 | msgstr "Programma's gecategoriseerd tonen" 170 | 171 | #: ../contents/ui/SearchBar.qml 172 | msgid "Applications" 173 | msgstr "Programma's" 174 | 175 | #: ../contents/ui/SearchList.qml 176 | msgctxt "@info:status" 177 | msgid "No matches" 178 | msgstr "Er zijn geen zoekresultaten" 179 | -------------------------------------------------------------------------------- /contents/ui/AppListViewDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Layouts 1.12 3 | import Qt5Compat.GraphicalEffects 4 | import org.kde.plasma.components 3.0 as PlasmaComponents 5 | import org.kde.kirigami 2.13 as Kirigami 6 | import QtQuick.Controls 2.15 7 | import QtQuick.Templates as T 8 | import org.kde.plasma.plasmoid 9 | 10 | import "../code/tools.js" as Tools 11 | 12 | T.ItemDelegate { 13 | id: listDelegate 14 | 15 | property bool compact: Plasmoid.configuration.compactListItems 16 | property bool showDescription: Plasmoid.configuration.showListItemDescription 17 | 18 | property int itemHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) 19 | 20 | implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, 21 | implicitContentWidth + leftPadding + rightPadding) 22 | implicitHeight: itemHeight 23 | leftPadding: compact ? 5 : 8 24 | rightPadding: compact ? 5 : 8 25 | bottomPadding: compact ? 5 : 8 26 | topPadding: compact ? 5 : 8 27 | 28 | property bool isDraging: false 29 | 30 | signal aboutToShowActionMenu(variant actionMenu) 31 | 32 | property bool hasActionList: ((model.favoriteId !== null) || (("hasActionList" in model) && (model.hasActionList !== null))) 33 | 34 | property var triggerModel 35 | 36 | onAboutToShowActionMenu: { 37 | var actionList = listDelegate.hasActionList ? model.actionList : []; 38 | //Tools.fillActionMenu(i18n, actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId); 39 | Tools.fillActionMenu(i18n, actionMenu, actionList, globalFavorites, model.favoriteId); 40 | } 41 | 42 | function openActionMenu(x, y) { 43 | aboutToShowActionMenu(actionMenu); 44 | actionMenu.visualParent = listDelegate; 45 | actionMenu.open(x, y); 46 | } 47 | function actionTriggered(actionId, actionArgument) { 48 | var close = (Tools.triggerAction(triggerModel, index, actionId, actionArgument) === true); 49 | if (close) { root.toggle(); } 50 | } 51 | function trigger() { 52 | triggerModel.trigger(index, "", null); 53 | root.toggle() 54 | } 55 | 56 | contentItem: RowLayout { 57 | id: row 58 | spacing: 8 59 | Kirigami.Icon { 60 | id: icon 61 | implicitWidth: compact ? Kirigami.Units.iconSizes.smallMedium : Kirigami.Units.iconSizes.medium 62 | implicitHeight: implicitWidth 63 | Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter 64 | 65 | source: model.decoration // || root.icon.name || root.icon.source 66 | } 67 | 68 | GridLayout { 69 | id: gridLayout 70 | 71 | Layout.fillWidth: true 72 | 73 | rows: listDelegate.compact ? 1 : 2 74 | columns: listDelegate.compact ? 2 : 1 75 | rowSpacing: 0 76 | columnSpacing: Kirigami.Units.largeSpacing 77 | 78 | Label { 79 | id: label 80 | Layout.fillWidth: !descriptionLabel.visible 81 | Layout.maximumWidth: listDelegate.width - listDelegate.leftPadding - listDelegate.rightPadding - icon.width - row.spacing 82 | text: ("name" in model ? model.name : model.display) 83 | textFormat: Text.PlainText 84 | elide: Text.ElideRight 85 | wrapMode: Text.NoWrap 86 | verticalAlignment: Text.AlignVCenter 87 | //maximumLineCount: root.isMultilineText ? Infinity : 1 88 | color: main.textColor 89 | } 90 | 91 | Label { 92 | id: descriptionLabel 93 | Layout.fillWidth: true 94 | visible: text && showDescription 95 | opacity: 0.75 96 | text: model.description 97 | textFormat: Text.PlainText 98 | font: Kirigami.Theme.smallFont 99 | elide: Text.ElideRight 100 | verticalAlignment: Text.AlignVCenter 101 | horizontalAlignment: listDelegate.compact ? Text.AlignRight : Text.AlignLeft 102 | maximumLineCount: 1 103 | color: main.textColor 104 | } 105 | } 106 | } 107 | 108 | MouseArea { 109 | id: ma 110 | anchors.fill: parent 111 | z: parent.z + 1 112 | acceptedButtons: Qt.LeftButton | Qt.RightButton 113 | cursorShape: Qt.PointingHandCursor 114 | hoverEnabled: !listView.movedWithWheel && !listView.blockingHoverFocus 115 | onClicked: { 116 | if (mouse.button == Qt.RightButton) { 117 | if (listDelegate.hasActionList) { 118 | var mapped = mapToItem(listDelegate, mouse.x, mouse.y); 119 | listDelegate.openActionMenu(mapped.x, mapped.y); 120 | } 121 | } else { trigger(); } 122 | } 123 | onReleased: { isDraging: false; } 124 | 125 | onEntered: { 126 | // - When the movedWithKeyboard condition is broken, we do not want to 127 | // select the hovered item without moving the mouse. 128 | // - Don't highlight separators. 129 | // - Don't switch category items on hover if the setting isn't enabled 130 | if (listView.movedWithKeyboard) { 131 | return 132 | } 133 | 134 | // forceActiveFocus() touches multiple items, so check for 135 | // activeFocus first to be more efficient. 136 | if (!listView.activeFocus) { 137 | listView.forceActiveFocus(Qt.MouseFocusReason) 138 | } 139 | // No need to check currentIndex first because it's 140 | // built into QQuickListView::setCurrentIndex() already 141 | listView.currentIndex = index 142 | } 143 | onPositionChanged: { 144 | isDraging = pressed 145 | if (pressed && canDrag){ 146 | if ("pluginName" in model) { 147 | dragHelper.startDrag(kicker, model.url, model.decoration, "text/x-plasmoidservicename", model.pluginName); 148 | } else { 149 | dragHelper.startDrag(kicker, model.url, model.decoration); 150 | } 151 | } 152 | } 153 | } 154 | ActionMenu { 155 | id: actionMenu 156 | 157 | onActionClicked: { 158 | visualParent.actionTriggered(actionId, actionArgument); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /contents/ui/MainView.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2022 by Friedrich Schriewer * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ****************************************************************************/ 19 | import QtQuick 20 | import QtQuick.Layouts 21 | import Qt5Compat.GraphicalEffects 22 | import org.kde.plasma.core as PlasmaCore 23 | import org.kde.plasma.components 3.0 as PlasmaComponents 24 | import org.kde.coreaddons as KCoreAddons 25 | 26 | import org.kde.plasma.plasma5support as P5Support 27 | import org.kde.kirigami as Kirigami 28 | 29 | import "js/colorType.js" as ColorType 30 | 31 | Item { 32 | id: main 33 | property bool searching: (searchBar.textField.text != "") 34 | 35 | readonly property color textColor: Kirigami.Theme.textColor 36 | readonly property string textFont: Kirigami.Theme.defaultFont//plasmoid.configuration.useSystemFontSettings ? Kirigami.Theme.defaultFont : "SF Pro Text" 37 | readonly property real textSize: 10//plasmoid.configuration.useSystemFontSettings ? Kirigami.Theme.defaultFont.pointSize : 11 38 | readonly property color bgColor: Kirigami.Theme.backgroundColor 39 | readonly property color highlightColor: Kirigami.Theme.highlightColor 40 | readonly property color highlightedTextColor: Kirigami.Theme.highlightedTextColor 41 | readonly property bool isTop: plasmoid.location == PlasmaCore.Types.TopEdge & plasmoid.configuration.launcherPosition != 2 & !plasmoid.configuration.floating 42 | 43 | property bool isDarkTheme: ColorType.isDark(bgColor) 44 | property color contrastBgColor: isDarkTheme ? Qt.rgba(255, 255, 255, 0.15) : Qt.rgba(0, 0, 0, 0.1) 45 | property color dimmedTextColor: Qt.rgba(textColor.r, textColor.g, textColor.b, 0.7) 46 | 47 | property bool showAllApps: true 48 | 49 | function reload() { 50 | searchBar.textField.clear() 51 | appList.reset() 52 | } 53 | function reset(){ 54 | searchBar.textField.clear() 55 | appList.reset() 56 | } 57 | 58 | ColumnLayout { 59 | 60 | anchors.top: parent.top 61 | anchors.left: parent.left 62 | anchors.right: parent.right 63 | anchors.bottom: parent.bottom 64 | 65 | spacing: 0 66 | 67 | SearchBar { 68 | id: searchBar 69 | Layout.fillWidth: true 70 | Layout.preferredHeight: 40 71 | Layout.maximumHeight: Layout.preferredHeight 72 | Layout.rightMargin: fs.innerPadding 73 | showMenuButton: !searching 74 | Keys.priority: Keys.AfterItem 75 | Keys.forwardTo: searching ? searchList : appList.viewItem 76 | } 77 | 78 | Rectangle { 79 | Layout.fillWidth: true 80 | Layout.rightMargin: fs.innerPadding 81 | height: 1.5 82 | color: main.contrastBgColor 83 | } 84 | 85 | AllAppsList{ 86 | id: appList 87 | state: "visible" 88 | Layout.fillHeight: true 89 | Layout.fillWidth: true 90 | 91 | Keys.priority: Keys.AfterItem 92 | Keys.forwardTo: searchBar.textField 93 | 94 | visible: opacity > 0 95 | states: [ 96 | State { 97 | name: "visible"; when: (!searching) 98 | PropertyChanges { target: appList; opacity: 1.0 } 99 | }, 100 | State { 101 | name: "hidden"; when: (searching) 102 | PropertyChanges { target: appList; opacity: 0.0} 103 | } 104 | ] 105 | transitions: [ 106 | Transition { 107 | to: "visible" 108 | PropertyAnimation {properties: 'opacity'; duration: 100; easing.type: Easing.OutQuart} 109 | }, 110 | Transition { 111 | to: "hidden" 112 | PropertyAnimation {properties: 'opacity'; duration: 100; easing.type: Easing.OutQuart} 113 | } 114 | ] 115 | } 116 | 117 | SearchList { 118 | id: searchList 119 | state: "hidden" 120 | visible: opacity > 0 121 | 122 | Layout.fillWidth: true 123 | Layout.fillHeight: true 124 | 125 | states: [ 126 | State { 127 | name: "visible"; when: (searching) 128 | PropertyChanges { target: searchList; opacity: 1.0 } 129 | }, 130 | State { 131 | name: "hidden"; when: (!searching) 132 | PropertyChanges { target: searchList; opacity: 0.0} 133 | } 134 | ] 135 | transitions: [ 136 | Transition { 137 | to: "visible" 138 | PropertyAnimation {properties: 'opacity'; duration: 100; easing.type: Easing.OutQuart} 139 | }, 140 | Transition { 141 | to: "hidden" 142 | PropertyAnimation {properties: 'opacity'; duration: 100; easing.type: Easing.OutQuart} 143 | } 144 | ] 145 | 146 | Keys.priority: Keys.AfterItem 147 | Keys.forwardTo: searchBar.textField 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /contents/ui/AppGridViewDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import Qt5Compat.GraphicalEffects 3 | import org.kde.plasma.components 3.0 as PlasmaComponents 4 | import org.kde.kirigami as Kirigami 5 | import QtQuick.Controls 2.15 6 | 7 | import "../code/tools.js" as Tools 8 | 9 | Item { 10 | id: gridDelegate 11 | 12 | property var triggerModel 13 | 14 | width: root.cellSizeWidth 15 | height: root.cellSizeHeight 16 | 17 | signal itemActivated(int index, string actionId, string argument) 18 | signal actionTriggered(string actionId, variant actionArgument) 19 | signal aboutToShowActionMenu(variant actionMenu) 20 | 21 | property bool isDraging: false 22 | property bool highlighted: false 23 | 24 | property bool hasActionList: ((model.favoriteId !== null) || (("hasActionList" in model) && (model.hasActionList !== null))) 25 | 26 | 27 | function openActionMenu(visualParent, x, y) { 28 | aboutToShowActionMenu(actionMenu); 29 | actionMenu.visualParent = visualParent; 30 | actionMenu.open(x, y); 31 | } 32 | 33 | function trigger() { 34 | triggerModel.trigger(index, "", null); 35 | root.toggle() 36 | } 37 | 38 | onAboutToShowActionMenu: actionMenu => { 39 | const actionList = (model.hasActionList !== null) ? model.actionList : []; 40 | Tools.fillActionMenu(i18n, actionMenu, actionList, globalFavorites, model.favoriteId); 41 | } 42 | onActionTriggered: (actionId, actionArgument) => { 43 | if (Tools.triggerAction(triggerModel, model.index, actionId, actionArgument) === true) { 44 | kicker.expanded = false; 45 | } 46 | } 47 | 48 | Kirigami.Icon { 49 | id: appicon 50 | y: (2 * highlightItemSvg.margins.top) 51 | anchors.horizontalCenter: parent.horizontalCenter 52 | width: root.iconSize 53 | height: width 54 | source: model.decoration 55 | } 56 | 57 | PlasmaComponents.Label { 58 | id: appname 59 | text: ("name" in model ? model.name : model.display) 60 | font.family: main.textFont 61 | font.pointSize: main.textSize 62 | font.weight: 650 63 | color: main.textColor 64 | anchors { 65 | top: appicon.bottom 66 | left: parent.left 67 | right: parent.right 68 | topMargin: Kirigami.Units.largeSpacing * 1.2 69 | leftMargin: Kirigami.Units.smallSpacing 70 | rightMargin: Kirigami.Units.smallSpacing 71 | } 72 | textFormat: Text.PlainText 73 | elide: Text.ElideMiddle 74 | horizontalAlignment: Text.AlignHCenter 75 | maximumLineCount: 2 76 | wrapMode: Text.Wrap 77 | } 78 | 79 | 80 | Rectangle { 81 | id: appiconHighlight 82 | 83 | width: appicon.width + 4 * highlightItemSvg.margins.top 84 | height: appicon.height + 4 * highlightItemSvg.margins.top 85 | radius: 14 86 | color: "transparent" 87 | border.width: 2 88 | border.color: main.dimmedTextColor 89 | 90 | anchors.top: parent.top 91 | anchors.horizontalCenter: parent.horizontalCenter 92 | } 93 | 94 | DropShadow { 95 | id: appIconShadow 96 | visible: true 97 | anchors.fill: appicon 98 | // cached: true 99 | horizontalOffset: 0 100 | verticalOffset: 0 101 | radius: 15.0 102 | samples: 16 103 | color: "#000000"//main.glowColor1 104 | source: appicon 105 | 106 | } 107 | state: "default" 108 | states: [ 109 | State { 110 | name: "highlight"; when: (grid.canMoveWithKeyboard ? focus : highlighted) 111 | PropertyChanges { target: appIconShadow; color: "#4D000000"} 112 | PropertyChanges { target: appiconHighlight; opacity: 0.4} 113 | }, 114 | State { 115 | name: "default"; when: (grid.canMoveWithKeyboard ? !focus : !highlighted) 116 | PropertyChanges { target: appIconShadow; color: "#33000000"} 117 | PropertyChanges { target: appiconHighlight; opacity: 0} 118 | } 119 | ] 120 | transitions: highlight 121 | 122 | MouseArea { 123 | id: ma 124 | anchors.fill: parent 125 | z: parent.z + 1 126 | acceptedButtons: Qt.LeftButton | Qt.RightButton 127 | cursorShape: Qt.PointingHandCursor 128 | hoverEnabled: !grid.movedWithWheel 129 | onClicked: { 130 | if (mouse.button == Qt.RightButton ) { 131 | if (gridDelegate.hasActionList) { 132 | var mapped = mapToItem(gridDelegate, mouse.x, mouse.y); 133 | gridDelegate.openActionMenu(gridDelegate, mouse.x, mouse.y); 134 | } 135 | } else { trigger(); } 136 | } 137 | onReleased: { isDraging: false } 138 | onEntered: { 139 | // - When the movedWithKeyboard condition is broken, we do not want to 140 | // select the hovered item without moving the mouse. 141 | // - Don't highlight separators. 142 | // - Don't switch category items on hover if the setting isn't enabled 143 | if (grid.movedWithKeyboard) { 144 | return; 145 | } 146 | 147 | // forceActiveFocus() touches multiple items, so check for 148 | // activeFocus first to be more efficient. 149 | if (!grid.activeFocus) { 150 | grid.forceActiveFocus(Qt.MouseFocusReason); 151 | } 152 | // No need to check currentIndex first because it's 153 | // built into QQuickListView::setCurrentIndex() already 154 | grid.currentIndex = index; 155 | highlighted = true; 156 | } 157 | 158 | onExited: { highlighted = false; } 159 | 160 | onPositionChanged: { 161 | isDraging = pressed 162 | if (pressed){ 163 | if ("pluginName" in model) { 164 | dragHelper.startDrag(kicker, model.url, model.decoration, "text/x-plasmoidservicename", model.pluginName); 165 | } else { 166 | kicker.dragSource = gridDelegate; 167 | dragHelper.startDrag(kicker, model.url, model.icon); 168 | } 169 | } 170 | } 171 | } 172 | ActionMenu { 173 | id: actionMenu 174 | 175 | onActionClicked: { 176 | actionTriggered(actionId, actionArgument); 177 | } 178 | } 179 | Transition { 180 | id: highlight 181 | ColorAnimation {duration: 100 } 182 | PropertyAnimation {properties: 'opacity'; duration: 100; easing.type: Easing.OutQuart} 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /contents/code/tools.js: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013 Aurélien Gâteau 3 | SPDX-FileCopyrightText: 2013-2015 Eike Hein 4 | SPDX-FileCopyrightText: 2017 Ivan Cukic 5 | 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | .pragma library 10 | 11 | function fillActionMenu(i18n, actionMenu, actionList, favoriteModel, favoriteId) { 12 | // Accessing actionList can be a costly operation, so we don't 13 | // access it until we need the menu. 14 | 15 | var actions = createFavoriteActions(i18n, favoriteModel, favoriteId); 16 | 17 | if (actions) { 18 | if (actionList && actionList.length > 0) { 19 | var actionListCopy = Array.from(actionList); 20 | var separator = { "type": "separator" }; 21 | actionListCopy.push(separator); 22 | // actionList = actions.concat(actionList); // this crashes Qt O.o 23 | actionListCopy.push.apply(actionListCopy, actions); 24 | actionList = actionListCopy; 25 | } else { 26 | actionList = actions; 27 | } 28 | } 29 | 30 | actionMenu.actionList = actionList; 31 | } 32 | 33 | function createFavoriteActions(i18n, favoriteModel, favoriteId) { 34 | if (!favoriteModel || !favoriteModel.enabled || !favoriteId) { 35 | return null; 36 | } 37 | 38 | 39 | if (favoriteModel.activities === undefined || 40 | favoriteModel.activities.activities.length <= 1) { 41 | var action = {}; 42 | 43 | if (favoriteModel.isFavorite(favoriteId)) { 44 | action.text = i18n("Remove from Favorites"); 45 | action.icon = "bookmark-remove"; 46 | action.actionId = "_kicker_favorite_remove"; 47 | } else if (favoriteModel.maxFavorites === -1 || favoriteModel.count < favoriteModel.maxFavorites) { 48 | action.text = i18n("Add to Favorites"); 49 | action.icon = "bookmark-new"; 50 | action.actionId = "_kicker_favorite_add"; 51 | } else { 52 | return null; 53 | } 54 | 55 | action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; 56 | 57 | return [action]; 58 | 59 | } else { 60 | var actions = []; 61 | 62 | var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); 63 | 64 | var activities = favoriteModel.activities.activities; 65 | 66 | // Adding the item to link/unlink to all activities 67 | 68 | var linkedToAllActivities = 69 | !(linkedActivities.indexOf(":global") === -1); 70 | 71 | actions.push({ 72 | text : i18n("On All Activities"), 73 | checkable : true, 74 | 75 | actionId : linkedToAllActivities ? 76 | "_kicker_favorite_remove_from_activity" : 77 | "_kicker_favorite_set_to_activity", 78 | checked : linkedToAllActivities, 79 | 80 | actionArgument : { 81 | favoriteModel: favoriteModel, 82 | favoriteId: favoriteId, 83 | favoriteActivity: "" 84 | } 85 | }); 86 | 87 | 88 | // Adding items for each activity separately 89 | 90 | var addActivityItem = function(activityId, activityName) { 91 | var linkedToThisActivity = 92 | !(linkedActivities.indexOf(activityId) === -1); 93 | 94 | actions.push({ 95 | text : activityName, 96 | checkable : true, 97 | checked : linkedToThisActivity && !linkedToAllActivities, 98 | 99 | actionId : 100 | // If we are on all activities, and the user clicks just one 101 | // specific activity, unlink from everything else 102 | linkedToAllActivities ? "_kicker_favorite_set_to_activity" : 103 | 104 | // If we are linked to the current activity, just unlink from 105 | // that single one 106 | linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : 107 | 108 | // Otherwise, link to this activity, but do not unlink from 109 | // other ones 110 | "_kicker_favorite_add_to_activity", 111 | 112 | actionArgument : { 113 | favoriteModel : favoriteModel, 114 | favoriteId : favoriteId, 115 | favoriteActivity : activityId 116 | } 117 | }); 118 | }; 119 | 120 | // Adding the item to link/unlink to the current activity 121 | 122 | addActivityItem(favoriteModel.activities.currentActivity, i18n("On the Current Activity")); 123 | 124 | actions.push({ 125 | type: "separator", 126 | actionId: "_kicker_favorite_separator" 127 | }); 128 | 129 | // Adding the items for each activity 130 | 131 | activities.forEach(function(activityId) { 132 | addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); 133 | }); 134 | 135 | return [{ 136 | text : i18n("Show in Favorites"), 137 | icon : "favorite", 138 | subActions : actions 139 | }]; 140 | } 141 | } 142 | 143 | function triggerAction(model, index, actionId, actionArgument) { 144 | function startsWith(txt, needle) { 145 | return txt.substr(0, needle.length) === needle; 146 | } 147 | 148 | if (startsWith(actionId, "_kicker_favorite_")) { 149 | handleFavoriteAction(actionId, actionArgument); 150 | return; 151 | } 152 | 153 | var closeRequested = model.trigger(index, actionId, actionArgument); 154 | 155 | if (closeRequested) { 156 | return true; 157 | } 158 | 159 | return false; 160 | } 161 | 162 | function handleFavoriteAction(actionId, actionArgument) { 163 | var favoriteId = actionArgument.favoriteId; 164 | var favoriteModel = actionArgument.favoriteModel; 165 | 166 | if (favoriteModel === null || favoriteId === null) { 167 | return null; 168 | } 169 | 170 | if (actionId === "_kicker_favorite_remove") { 171 | favoriteModel.removeFavorite(favoriteId); 172 | } else if (actionId === "_kicker_favorite_add") { 173 | favoriteModel.addFavorite(favoriteId); 174 | } else if (actionId === "_kicker_favorite_remove_from_activity") { 175 | favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); 176 | } else if (actionId === "_kicker_favorite_add_to_activity") { 177 | favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); 178 | } else if (actionId === "_kicker_favorite_set_to_activity") { 179 | favoriteModel.setFavoriteOn(favoriteId, actionArgument.favoriteActivity); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | 2 | /*************************************************************************** 3 | * Copyright (C) 2014-2015 by Eike Hein * 4 | * Copyright (C) 2021 by Prateek SU * 5 | * * 6 | * This program is free software; you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation; either version 2 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program; if not, write to the * 18 | * Free Software Foundation, Inc., * 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 20 | ***************************************************************************/ 21 | 22 | import QtQuick 2.15 23 | import QtQuick.Layouts 1.15 24 | import org.kde.plasma.plasmoid 2.0 25 | 26 | import org.kde.plasma.core as PlasmaCore 27 | import org.kde.plasma.components 3.0 as PlasmaComponents 28 | 29 | import org.kde.ksvg as KSvg 30 | 31 | import org.kde.plasma.private.kicker 0.1 as Kicker 32 | 33 | import org.kde.kirigami 2.20 as Kirigami 34 | 35 | PlasmoidItem { 36 | id: kicker 37 | 38 | anchors.fill: parent 39 | 40 | signal reset 41 | 42 | property bool isDash: false 43 | 44 | preferredRepresentation: fullRepresentation 45 | 46 | compactRepresentation: null 47 | fullRepresentation: compactRepresentation 48 | 49 | property Item dragSource: null 50 | 51 | property QtObject globalFavorites: rootModel.favoritesModel 52 | property QtObject systemFavorites: rootModel.systemFavoritesModel 53 | 54 | Plasmoid.icon: Plasmoid.configuration.useCustomButtonImage ? Plasmoid.configuration.customButtonImage : Plasmoid.configuration.icon 55 | 56 | function action_menuedit() { 57 | processRunner.runMenuEditor(); 58 | } 59 | 60 | Component { 61 | id: compactRepresentation 62 | CompactRepresentation { } 63 | } 64 | 65 | Component { 66 | id: menuRepresentation 67 | MenuRepresentation { } 68 | } 69 | 70 | Kicker.RootModel { 71 | id: rootModel 72 | 73 | autoPopulate: false 74 | 75 | appNameFormat: 0// Plasmoid.configuration.appNameFormat 76 | flat: true 77 | sorted: true 78 | showSeparators: false 79 | appletInterface: kicker 80 | 81 | showAllApps: true 82 | showAllAppsCategorized: false 83 | showTopLevelItems: true//!kicker.isDash 84 | showRecentApps: true // Plasmoid.configuration.showRecentApps 85 | showRecentDocs: true //Plasmoid.configuration.showRecentDocs 86 | // showRecentContacts: true//Plasmoid.configuration.showRecentContacts 87 | // recentOrdering: 1 // Plasmoid.configuration.recentOrdering 88 | 89 | onRecentOrderingChanged: { 90 | Plasmoid.configuration.recentOrdering = recentOrdering; 91 | } 92 | 93 | Component.onCompleted: { 94 | favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + Plasmoid.id) 95 | 96 | // kicker.logListModel("rootmodel", rootModel); 97 | if (!Plasmoid.configuration.favoritesPortedToKAstats) { 98 | if (favoritesModel.count < 1) { 99 | favoritesModel.portOldFavorites(Plasmoid.configuration.favoriteApps); 100 | } 101 | Plasmoid.configuration.favoritesPortedToKAstats = true; 102 | } 103 | } 104 | } 105 | 106 | Connections { 107 | target: globalFavorites 108 | 109 | function onFavoritesChanged() { 110 | Plasmoid.configuration.favoriteApps = target.favorites; 111 | } 112 | } 113 | 114 | Connections { 115 | target: systemFavorites 116 | 117 | function onFavoritesChanged() { 118 | Plasmoid.configuration.favoriteSystemActions = target.favorites; 119 | } 120 | } 121 | 122 | Connections { 123 | target: Plasmoid.configuration 124 | 125 | function onFavoriteAppsChanged() { 126 | globalFavorites.favorites = Plasmoid.configuration.favoriteApps; 127 | } 128 | 129 | function onFavoriteSystemActionsChanged() { 130 | systemFavorites.favorites = Plasmoid.configuration.favoriteSystemActions; 131 | } 132 | } 133 | 134 | Kicker.RunnerModel { 135 | id: runnerModel 136 | 137 | favoritesModel: globalFavorites 138 | appletInterface: kicker 139 | mergeResults: true 140 | } 141 | 142 | Kicker.DragHelper { 143 | id: dragHelper 144 | 145 | dragIconSize: Kirigami.Units.iconSizes.medium 146 | } 147 | 148 | Kicker.ProcessRunner { 149 | id: processRunner; 150 | } 151 | 152 | KSvg.FrameSvgItem { 153 | id: highlightItemSvg 154 | 155 | visible: false 156 | 157 | imagePath: "widgets/viewitem" 158 | prefix: "hover" 159 | } 160 | 161 | KSvg.FrameSvgItem { 162 | id: panelSvg 163 | visible: false 164 | imagePath: "widgets/panel-background" 165 | } 166 | 167 | KSvg.FrameSvgItem { 168 | id: dialogSvg 169 | visible: false 170 | imagePath: "dialogs/background" 171 | } 172 | 173 | PlasmaComponents.Label { 174 | id: toolTipDelegate 175 | 176 | width: contentWidth 177 | height: contentHeight 178 | 179 | property Item toolTip 180 | 181 | text: (toolTip != null) ? toolTip.text : "" 182 | } 183 | 184 | function resetDragSource() { 185 | dragSource = null; 186 | } 187 | 188 | Plasmoid.contextualActions: [ 189 | PlasmaCore.Action { 190 | text: i18n("Edit Applications…") 191 | icon.name: "kmenuedit" 192 | visible: Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable 193 | onTriggered: processRunner.runMenuEditor() 194 | } 195 | ] 196 | 197 | Component.onCompleted: { 198 | //plasmoid.setAction("menuedit", i18n("Edit Applications...")); 199 | rootModel.refreshed.connect(reset); 200 | 201 | dragHelper.dropped.connect(resetDragSource); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /contents/ui/MenuRepresentation.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2014 by Weng Xuetian * 3 | * Copyright (C) 2013-2017 by Eike Hein * 4 | * Copyright (C) 2021 by Prateek SU * 5 | * Copyright (C) 2022 by Friedrich Schriewer * 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program; if not, write to the * 19 | * Free Software Foundation, Inc., * 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 21 | ****************************************************************************/ 22 | 23 | import QtQuick 24 | import QtQuick.Layouts 25 | import QtQml 26 | import org.kde.plasma.core as PlasmaCore 27 | import org.kde.plasma.components as PlasmaComponents 28 | import org.kde.plasma.plasmoid 29 | import org.kde.kirigami as Kirigami 30 | 31 | PlasmaCore.Dialog { 32 | id: root 33 | 34 | objectName: "popupWindow" 35 | flags: Qt.WindowStaysOnTopHint 36 | 37 | location: Plasmoid.configuration.floating || Plasmoid.configuration.launcherPosition == 2 ? "Floating" : Plasmoid.location 38 | hideOnWindowDeactivate: true 39 | 40 | Plasmoid.status: root.visible ? PlasmaCore.Types.RequiresAttentionStatus : PlasmaCore.Types.PassiveStatus 41 | 42 | property int iconSize: { 43 | switch(Plasmoid.configuration.appsIconSize){ 44 | case 0: return Kirigami.Units.iconSizes.smallMedium; 45 | case 1: return Kirigami.Units.iconSizes.medium; 46 | case 2: return Kirigami.Units.iconSizes.large; 47 | case 3: return Kirigami.Units.iconSizes.huge; 48 | default: return 64 49 | } 50 | } 51 | 52 | property int columns: Plasmoid.configuration.numberColumns 53 | 54 | property int cellSizeHeight: iconSize 55 | + Kirigami.Units.gridUnit * 2 56 | + (2 * Math.max( 57 | highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 58 | highlightItemSvg.margins.left + highlightItemSvg.margins.right 59 | ) 60 | ) 61 | property int cellSizeWidth: cellSizeHeight //+ Kirigami.Units.gridUnit 62 | property int rows: plasmoid.configuration.numberOfRows 63 | 64 | onVisibleChanged: { 65 | if (!visible) { 66 | reset(); 67 | } else { 68 | var pos = popupPosition(width, height); 69 | x = pos.x; 70 | y = pos.y; 71 | requestActivate(); 72 | } 73 | } 74 | 75 | onHeightChanged: { 76 | var pos = popupPosition(width, height); 77 | x = pos.x; 78 | y = pos.y; 79 | } 80 | 81 | onWidthChanged: { 82 | var pos = popupPosition(width, height); 83 | x = pos.x; 84 | y = pos.y; 85 | } 86 | 87 | function toggle() { 88 | root.visible = false; 89 | } 90 | 91 | function reset() { 92 | main.reset() 93 | } 94 | 95 | function popupPosition(width, height) { 96 | var screenAvail = Plasmoid.availableScreenRect; 97 | var screen/*Geom*/ = kicker.screenGeometry; 98 | //QtBug - QTBUG-64115 99 | /*var screen = Qt.rect(screenAvail.x + screenGeom.x, 100 | screenAvail.y + screenGeom.y, 101 | screenAvail.width, 102 | screenAvail.height);*/ 103 | 104 | var offset = 0 105 | 106 | if (Plasmoid.configuration.offsetX > 0 && Plasmoid.configuration.floating) { 107 | offset = Plasmoid.configuration.offsetX 108 | } else { 109 | offset = plasmoid.configuration.floating ? parent.height * 0.35 : 0 110 | } 111 | // Fall back to bottom-left of screen area when the applet is on the desktop or floating. 112 | var x = offset; 113 | var y = screen.height - height - offset; 114 | var horizMidPoint = screen.x + (screen.width / 2); 115 | var vertMidPoint = screen.y + (screen.height / 2); 116 | var appletTopLeft = parent.mapToGlobal(0, 0); 117 | var appletBottomLeft = parent.mapToGlobal(0, parent.height); 118 | 119 | if (Plasmoid.configuration.launcherPosition != 0){ 120 | x = horizMidPoint - width / 2; 121 | } else { 122 | x = (appletTopLeft.x < horizMidPoint) ? screen.x : (screen.x + screen.width) - width; 123 | if (Plasmoid.configuration.floating) { 124 | if (appletTopLeft.x < horizMidPoint) { 125 | x += offset 126 | } else if (appletTopLeft.x + width > horizMidPoint){ 127 | x -= offset 128 | } 129 | } 130 | } 131 | 132 | if (Plasmoid.configuration.launcherPosition != 2){ 133 | if (Plasmoid.location == PlasmaCore.Types.TopEdge) { 134 | if (Plasmoid.configuration.floating) { 135 | /*this is floatingAvatar.width*/ 136 | if (Plasmoid.configuration.offsetY > 0) { 137 | offset = (125 * 1) / 2 + Plasmoid.configuration.offsetY 138 | } else { 139 | offset = (125 * 1) / 2 + parent.height * 0.125 140 | } 141 | } 142 | y = screen.y + parent.height + panelSvg.margins.bottom + offset; 143 | } else { 144 | if (Plasmoid.configuration.offsetY > 0) { 145 | offset = Plasmoid.configuration.offsetY 146 | } 147 | y = screen.y + screen.height - parent.height - height - panelSvg.margins.top - offset * 2.5; 148 | } 149 | } else { 150 | y = vertMidPoint - height / 2 151 | } 152 | 153 | return Qt.point(x, y); 154 | } 155 | 156 | FocusScope { 157 | id: fs 158 | focus: true 159 | width: (root.cellSizeWidth * Plasmoid.configuration.numberColumns)+ innerPadding*2//Kirigami.Units.gridUnit*2 160 | // Searchbar.height + separator.height + categories switcher.height 161 | height: 40 + 2 + 40 + (root.cellSizeHeight *rows) + innerPadding//550 * 1 162 | 163 | 164 | // We want the MainView to have an uniform margin through different plasma themes 165 | property real innerPadding: 15 166 | 167 | Item { 168 | id: mainItem 169 | x: - dialogSvg.margins.left 170 | y: - dialogSvg.margins.top 171 | width: parent.width + dialogSvg.margins.left + dialogSvg.margins.right 172 | height: parent.height + dialogSvg.margins.top + dialogSvg.margins.bottom 173 | 174 | MainView { 175 | id: main 176 | width: mainItem.width - (fs.innerPadding) 177 | height: mainItem.height - (fs.innerPadding*2) 178 | x: fs.innerPadding 179 | y: fs.innerPadding 180 | } 181 | } 182 | 183 | Keys.onPressed: { 184 | if (event.key == Qt.Key_Escape) { 185 | root.visible = false; 186 | } 187 | } 188 | } 189 | 190 | function refreshModel() { 191 | main.reload() 192 | } 193 | 194 | Component.onCompleted: { 195 | kicker.reset.connect(reset); 196 | rootModel.refresh(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /contents/ui/AppGridView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 2.15 3 | import org.kde.kirigami as Kirigami 4 | 5 | GridView { 6 | id: grid 7 | 8 | property bool movedWithKeyboard: false 9 | property bool movedWithWheel: false 10 | property bool canMoveWithKeyboard: false 11 | 12 | readonly property int columns: plasmoid.configuration.numberColumns 13 | 14 | focus: true 15 | clip: true 16 | currentIndex: count > 0 ? 0 : -1 17 | interactive: height < contentHeight 18 | boundsBehavior: Flickable.StopAtBounds 19 | // default keyboard navigation doesn't allow focus reasons to be used 20 | // and eats up/down key events when at the beginning or end of the list. 21 | keyNavigationEnabled: false 22 | keyNavigationWraps: false 23 | 24 | highlightMoveDuration: 0 25 | 26 | cellWidth: root.cellSizeWidth 27 | cellHeight: root.cellSizeHeight 28 | 29 | delegate: AppGridViewDelegate { 30 | id: favitem 31 | triggerModel: grid.model 32 | } 33 | 34 | move: normalTransition 35 | moveDisplaced: normalTransition 36 | 37 | Transition { 38 | id: normalTransition 39 | NumberAnimation { 40 | duration: Kirigami.Units.shortDuration 41 | properties: "x, y" 42 | easing.type: Easing.OutCubic 43 | } 44 | } 45 | 46 | ScrollBar.vertical: Scrollbar { 47 | id: verticalScrollBar 48 | parent: grid 49 | z: 2 50 | height: grid.height 51 | width: 12 52 | anchors.right: grid.right 53 | active: grid.movedWithWheel 54 | 55 | } 56 | 57 | Kirigami.WheelHandler { 58 | id: wheelHandler 59 | target: grid 60 | filterMouseEvents: true 61 | // `20 * Qt.styleHints.wheelScrollLines` is the default speed. 62 | horizontalStepSize: 20 * Qt.styleHints.wheelScrollLines 63 | verticalStepSize: 20 * Qt.styleHints.wheelScrollLines 64 | 65 | onWheel: wheel => { 66 | grid.movedWithWheel = true 67 | grid.movedWithKeyboard = false 68 | movedWithWheelTimer.restart() 69 | } 70 | } 71 | 72 | Connections { 73 | target: root 74 | function onVisibleChanged() { 75 | if (!root.visible) { 76 | grid.currentIndex = 0 77 | grid.positionViewAtBeginning() 78 | } 79 | } 80 | } 81 | 82 | // Used to block hover events temporarily after using keyboard navigation. 83 | // If you have one hand on the touch pad or mouse and another hand on the keyboard, 84 | // it's easy to accidentally reset the highlight/focus position to the mouse position. 85 | Timer { 86 | id: movedWithKeyboardTimer 87 | interval: 200 88 | onTriggered: grid.movedWithKeyboard = false 89 | } 90 | 91 | Timer { 92 | id: movedWithWheelTimer 93 | interval: 200 94 | onTriggered: grid.movedWithWheel = false 95 | } 96 | 97 | function focusCurrentItem(event, focusReason) { 98 | currentItem.forceActiveFocus(focusReason) 99 | event.accepted = true 100 | } 101 | 102 | Keys.onPressed: event => { 103 | const targetX = currentItem ? currentItem.x : contentX 104 | let targetY = currentItem ? currentItem.y : contentY 105 | let targetIndex = currentIndex 106 | // supports mirroring 107 | const atLeft = currentIndex % columns === (Qt.application.layoutDirection == Qt.RightToLeft ? columns - 1 : 0) 108 | // at the beginning of a line 109 | const isLeading = currentIndex % columns === 0 110 | // at the top of a given column and in the top row 111 | const atTop = currentIndex < columns 112 | // supports mirroring 113 | const atRight = currentIndex % columns === (Qt.application.layoutDirection == Qt.RightToLeft ? 0 : columns - 1) 114 | // at the end of a line 115 | const isTrailing = currentIndex % columns === columns - 1 116 | // at bottom of a given column, not necessarily in the last row 117 | let atBottom = currentIndex >= count - columns 118 | // Implements the keyboard navigation described in https://www.w3.org/TR/wai-aria-practices-1.2/#grid 119 | if (count > 1) { 120 | switch (event.key) { 121 | case Qt.Key_Left: if (!atLeft && !searchBar.textField.activeFocus) { 122 | moveCurrentIndexLeft() 123 | focusCurrentItem(event, Qt.BacktabFocusReason) 124 | } break 125 | case Qt.Key_H: if (!atLeft && !searchBar.textField.activeFocus && event.modifiers & Qt.ControlModifier) { 126 | moveCurrentIndexLeft() 127 | focusCurrentItem(event, Qt.BacktabFocusReason) 128 | } break 129 | case Qt.Key_Up: if (!atTop) { 130 | moveCurrentIndexUp() 131 | focusCurrentItem(event, Qt.BacktabFocusReason) 132 | } break 133 | case Qt.Key_K: if (!atTop && event.modifiers & Qt.ControlModifier) { 134 | moveCurrentIndexUp() 135 | focusCurrentItem(event, Qt.BacktabFocusReason) 136 | } break 137 | case Qt.Key_Right: if (!atRight && !searchBar.textField.activeFocus) { 138 | moveCurrentIndexRight() 139 | focusCurrentItem(event, Qt.TabFocusReason) 140 | } break 141 | case Qt.Key_L: if (!atRight && !searchBar.textField.activeFocus && event.modifiers & Qt.ControlModifier) { 142 | moveCurrentIndexRight() 143 | focusCurrentItem(event, Qt.TabFocusReason) 144 | } break 145 | case Qt.Key_Down: if (!atBottom) { 146 | moveCurrentIndexDown() 147 | focusCurrentItem(event, Qt.TabFocusReason) 148 | } break 149 | case Qt.Key_J: if (!atBottom && event.modifiers & Qt.ControlModifier) { 150 | moveCurrentIndexDown() 151 | focusCurrentItem(event, Qt.TabFocusReason) 152 | } break 153 | case Qt.Key_Home: if (event.modifiers === Qt.ControlModifier && currentIndex !== 0) { 154 | currentIndex = 0 155 | focusCurrentItem(event, Qt.BacktabFocusReason) 156 | } else if (!isLeading) { 157 | targetIndex -= currentIndex % columns 158 | currentIndex = Math.max(targetIndex, 0) 159 | focusCurrentItem(event, Qt.BacktabFocusReason) 160 | } break 161 | case Qt.Key_End: if (event.modifiers === Qt.ControlModifier && currentIndex !== count - 1) { 162 | currentIndex = count - 1 163 | focusCurrentItem(event, Qt.TabFocusReason) 164 | } else if (!isTrailing) { 165 | targetIndex += columns - 1 - (currentIndex % columns) 166 | currentIndex = Math.min(targetIndex, count - 1) 167 | focusCurrentItem(event, Qt.TabFocusReason) 168 | } break 169 | case Qt.Key_PageUp: if (!atTop) { 170 | targetY = targetY - height + 1 171 | targetIndex = indexAt(targetX, targetY) 172 | // TODO: Find a more efficient, but accurate way to do this 173 | while (targetIndex === -1) { 174 | targetY += 1 175 | targetIndex = indexAt(targetX, targetY) 176 | } 177 | currentIndex = Math.max(targetIndex, 0) 178 | focusCurrentItem(event, Qt.BacktabFocusReason) 179 | } break 180 | case Qt.Key_PageDown: if (!atBottom) { 181 | targetY = targetY + height - 1 182 | targetIndex = indexAt(targetX, targetY) 183 | // TODO: Find a more efficient, but accurate way to do this 184 | while (targetIndex === -1) { 185 | targetY -= 1 186 | targetIndex = indexAt(targetX, targetY) 187 | } 188 | currentIndex = Math.min(targetIndex, count - 1) 189 | focusCurrentItem(event, Qt.TabFocusReason) 190 | } break 191 | case Qt.Key_Return: 192 | /* Fall through*/ 193 | case Qt.Key_Enter: 194 | grid.currentItem.trigger(); 195 | grid.currentItem.forceActiveFocus(Qt.ShortcutFocusReason); 196 | event.accepted = true; 197 | break; 198 | } 199 | } 200 | movedWithKeyboard = event.accepted 201 | if (movedWithKeyboard) { 202 | movedWithKeyboardTimer.restart() 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /contents/ui/AppListView.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2022 by Friedrich Schriewer * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; if not, write to the * 16 | * Free Software Foundation, Inc., * 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 18 | ****************************************************************************/ 19 | 20 | import QtQuick 21 | import QtQuick.Controls 22 | import org.kde.plasma.extras as PlasmaExtras 23 | import org.kde.kirigami as Kirigami 24 | 25 | ListView { 26 | id: listView 27 | 28 | property real availableWidth: listView.width - fs.innerPadding 29 | 30 | property bool showSectionSeparator: true 31 | 32 | property bool showScrollbar: true // Used to hide scrollbar in AppCategorySwitcher 33 | 34 | // This helps to prevent list focus back to cursor position when search changes 35 | property var interceptedPosition: null 36 | property bool blockingHoverFocus: false 37 | 38 | // This helps to prevent focus getting back to cursor position when navigating through the list 39 | property bool movedWithKeyboard: false 40 | property bool movedWithWheel: false 41 | 42 | Accessible.role: Accessible.List 43 | 44 | focus: true 45 | clip: true 46 | currentIndex: count > 0 ? 0 : -1 47 | interactive: height < contentHeight 48 | boundsBehavior: Flickable.StopAtBounds 49 | // default keyboard navigation doesn't allow focus reasons to be used 50 | // and eats up/down key events when at the beginning or end of the list. 51 | keyNavigationEnabled: false 52 | keyNavigationWraps: false 53 | 54 | // This is actually needed. The highlight will animate from thin to wide otherwise. 55 | highlightResizeDuration: 0 56 | highlightMoveDuration: 50 57 | 58 | highlight: Highlight{} 59 | 60 | delegate: AppListViewDelegate { 61 | triggerModel: listView.model 62 | width: listView.availableWidth 63 | } 64 | 65 | move: normalTransition 66 | moveDisplaced: normalTransition 67 | 68 | Transition { 69 | id: normalTransition 70 | NumberAnimation { 71 | duration: Kirigami.Units.shortDuration 72 | properties: "x, y" 73 | easing.type: Easing.OutCubic 74 | } 75 | } 76 | 77 | section { 78 | property: showSectionSeparator ? "group" : "nosection" 79 | criteria: ViewSection.FullString 80 | delegate: PlasmaExtras.ListSectionHeader { 81 | required property string section 82 | width: listView.availableWidth 83 | text: section 84 | } 85 | } 86 | 87 | ScrollBar.vertical: Scrollbar { 88 | id: verticalScrollBar 89 | parent: listView 90 | z: 2 91 | height: listView.height 92 | anchors.right: listView.right 93 | visible: showScrollbar 94 | active: listView.movedWithWheel 95 | } 96 | 97 | Kirigami.WheelHandler { 98 | target: listView 99 | filterMouseEvents: true 100 | // `20 * Qt.styleHints.wheelScrollLines` is the default speed. 101 | horizontalStepSize: 20 * Qt.styleHints.wheelScrollLines 102 | verticalStepSize: 20 * Qt.styleHints.wheelScrollLines 103 | 104 | onWheel: wheel => { 105 | 106 | if (wheel.angleDelta.y !== 0) { // This provides horizontal scrolling 107 | listView.flick(wheel.angleDelta.y * 15, 0); 108 | } 109 | 110 | listView.movedWithWheel = true 111 | listView.movedWithKeyboard = false 112 | movedWithWheelTimer.restart() 113 | } 114 | } 115 | 116 | Connections { 117 | target: root 118 | function onVisibleChanged() { 119 | if (!root.visible) { 120 | listView.currentIndex = 0 121 | listView.positionViewAtBeginning() 122 | } 123 | } 124 | } 125 | 126 | // Used to block hover events temporarily after using keyboard navigation. 127 | // If you have one hand on the touch pad or mouse and another hand on the keyboard, 128 | // it's easy to accidentally reset the highlight/focus position to the mouse position. 129 | Timer { 130 | id: movedWithKeyboardTimer 131 | interval: 200 132 | onTriggered: listView.movedWithKeyboard = false 133 | } 134 | 135 | Timer { 136 | id: movedWithWheelTimer 137 | interval: 200 138 | onTriggered: listView.movedWithWheel = false 139 | } 140 | 141 | function focusCurrentItem(event, focusReason) { 142 | currentItem.forceActiveFocus(focusReason) 143 | event.accepted = true 144 | } 145 | 146 | Keys.onPressed: event => { 147 | const targetX = currentItem ? currentItem.x : contentX 148 | let targetY = currentItem ? currentItem.y : contentY 149 | let targetIndex = currentIndex 150 | const atFirst = currentIndex === 0 151 | const atLast = currentIndex === count - 1 152 | if (count >= 1) { 153 | switch (event.key) { 154 | case Qt.Key_Up: if (!atFirst) { 155 | decrementCurrentIndex() 156 | 157 | if (currentItem.isSeparator) { 158 | decrementCurrentIndex() 159 | } 160 | 161 | focusCurrentItem(event, Qt.BacktabFocusReason) 162 | } break 163 | case Qt.Key_K: if (!atFirst && event.modifiers & Qt.ControlModifier) { 164 | decrementCurrentIndex() 165 | focusCurrentItem(event, Qt.BacktabFocusReason) 166 | } break 167 | case Qt.Key_Down: if (!atLast) { 168 | incrementCurrentIndex() 169 | 170 | if (currentItem.isSeparator) { 171 | incrementCurrentIndex() 172 | } 173 | 174 | focusCurrentItem(event, Qt.TabFocusReason) 175 | } break 176 | case Qt.Key_J: if (!atLast && event.modifiers & Qt.ControlModifier) { 177 | incrementCurrentIndex() 178 | focusCurrentItem(event, Qt.TabFocusReason) 179 | } break 180 | case Qt.Key_Home: if (!atFirst) { 181 | currentIndex = 0 182 | focusCurrentItem(event, Qt.BacktabFocusReason) 183 | } break 184 | case Qt.Key_End: if (!atLast) { 185 | currentIndex = count - 1 186 | focusCurrentItem(event, Qt.TabFocusReason) 187 | } break 188 | case Qt.Key_PageUp: if (!atFirst) { 189 | targetY = targetY - height + 1 190 | targetIndex = indexAt(targetX, targetY) 191 | // TODO: Find a more efficient, but accurate way to do this 192 | while (targetIndex === -1) { 193 | targetY += 1 194 | targetIndex = indexAt(targetX, targetY) 195 | } 196 | currentIndex = Math.max(targetIndex, 0) 197 | focusCurrentItem(event, Qt.BacktabFocusReason) 198 | } break 199 | case Qt.Key_PageDown: if (!atLast) { 200 | targetY = targetY + height - 1 201 | targetIndex = indexAt(targetX, targetY) 202 | // TODO: Find a more efficient, but accurate way to do this 203 | while (targetIndex === -1) { 204 | targetY -= 1 205 | targetIndex = indexAt(targetX, targetY) 206 | } 207 | currentIndex = Math.min(targetIndex, count - 1) 208 | focusCurrentItem(event, Qt.TabFocusReason) 209 | } break 210 | case Qt.Key_Return: 211 | /* Fall through*/ 212 | case Qt.Key_Enter: 213 | listView.currentItem.trigger(); 214 | listView.currentItem.forceActiveFocus(Qt.ShortcutFocusReason); 215 | event.accepted = true; 216 | break; 217 | } 218 | } 219 | 220 | movedWithKeyboard = event.accepted 221 | 222 | if (movedWithKeyboard) { 223 | movedWithKeyboardTimer.restart() 224 | } 225 | } 226 | 227 | Connections { 228 | target: blockHoverFocusHandler 229 | enabled: blockHoverFocusHandler.enabled && !listView.interceptedPosition 230 | function onPointChanged() { 231 | listView.interceptedPosition = blockHoverFocusHandler.point.position 232 | } 233 | } 234 | 235 | Connections { 236 | target: blockHoverFocusHandler 237 | enabled: blockHoverFocusHandler.enabled && listView.interceptedPosition && listView.blockingHoverFocus 238 | function onPointChanged() { 239 | if (blockHoverFocusHandler.point.position === listView.interceptedPosition) { 240 | return; 241 | } 242 | listView.blockingHoverFocus = false 243 | } 244 | } 245 | 246 | HoverHandler { 247 | id: blockHoverFocusHandler 248 | enabled: (!listView.interceptedPosition || listView.blockingHoverFocus) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /contents/ui/icons/AppsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 33 | 35 | 37 | 42 | 47 | 51 | 56 | 61 | 62 | 64 | 69 | 73 | 78 | 79 | 81 | 85 | 89 | 90 | 97 | 100 | 101 | 109 | 111 | 115 | 120 | 121 | 129 | 136 | 140 | 144 | 145 | 153 | 160 | 164 | 168 | 169 | 177 | 185 | 193 | 198 | 202 | 207 | 208 | 217 | 225 | 232 | 235 | 236 | 244 | 251 | 255 | 260 | 261 | 268 | 271 | 272 | 273 | 284 | 295 | 300 | 301 | -------------------------------------------------------------------------------- /contents/ui/ConfigGeneral.qml: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (C) 2013-2014 by Eike Hein * 3 | * Copyright (C) 2021 by Prateek SU * 4 | * Copyright (C) 2022 by Friedrich Schriewer * 5 | * * 6 | * This program is free software; you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation; either version 2 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program; if not, write to the * 18 | * Free Software Foundation, Inc., * 19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * 20 | ****************************************************************************/ 21 | 22 | import QtQuick 2.15 23 | import QtQuick.Controls 2.15 24 | import QtQuick.Dialogs 25 | 26 | import org.kde.plasma.components 3.0 as PlasmaComponents 27 | import org.kde.plasma.core as PlasmaCore 28 | import org.kde.draganddrop 2.0 as DragDrop 29 | import org.kde.kirigami 2.3 as Kirigami 30 | 31 | import org.kde.ksvg 1.0 as KSvg 32 | import org.kde.plasma.plasmoid 2.0 33 | import org.kde.kcmutils as KCM 34 | 35 | import org.kde.iconthemes as KIconThemes 36 | 37 | 38 | KCM.SimpleKCM { 39 | id: configGeneral 40 | 41 | property bool isDash: (Plasmoid.pluginName === "org.kde.plasma.kickerdash") 42 | 43 | property string cfg_icon: Plasmoid.configuration.icon 44 | property bool cfg_useCustomButtonImage: Plasmoid.configuration.useCustomButtonImage 45 | property string cfg_customButtonImage: Plasmoid.configuration.customButtonImage 46 | 47 | property alias cfg_floating: floating.checked 48 | property alias cfg_launcherPosition: launcherPosition.currentIndex 49 | property alias cfg_offsetX: screenOffset.value 50 | property alias cfg_offsetY: panelOffset.value 51 | 52 | property alias cfg_useSystemFontSettings: useSystemFontSettings.checked 53 | 54 | property alias cfg_appsIconSize: appsIconSize.currentIndex 55 | property alias cfg_numberColumns: numberColumns.value 56 | property alias cfg_numberOfRows: numberOfRows.value 57 | 58 | property alias cfg_compactListItems: compactListItems.checked 59 | property alias cfg_showListItemDescription: showListItemDescription.checked 60 | 61 | 62 | Kirigami.FormLayout { 63 | 64 | anchors.left: parent.left 65 | anchors.right: parent.right 66 | 67 | Button { 68 | id: iconButton 69 | 70 | Kirigami.FormData.label: i18n("Icon:") 71 | 72 | implicitWidth: previewFrame.width + Kirigami.Units.smallSpacing * 2 73 | implicitHeight: previewFrame.height + Kirigami.Units.smallSpacing * 2 74 | 75 | // Just to provide some visual feedback when dragging; 76 | // cannot have checked without checkable enabled 77 | checkable: true 78 | checked: dropArea.containsAcceptableDrag 79 | 80 | onPressed: iconMenu.opened ? iconMenu.close() : iconMenu.open() 81 | 82 | DragDrop.DropArea { 83 | id: dropArea 84 | 85 | property bool containsAcceptableDrag: false 86 | 87 | anchors.fill: parent 88 | 89 | onDragEnter: { 90 | // Cannot use string operations (e.g. indexOf()) on "url" basic type. 91 | var urlString = event.mimeData.url.toString(); 92 | 93 | // This list is also hardcoded in KIconDialog. 94 | var extensions = [".png", ".xpm", ".svg", ".svgz"]; 95 | containsAcceptableDrag = urlString.indexOf("file:///") === 0 && extensions.some(function (extension) { 96 | return urlString.indexOf(extension) === urlString.length - extension.length; // "endsWith" 97 | }); 98 | 99 | if (!containsAcceptableDrag) { 100 | event.ignore(); 101 | } 102 | } 103 | onDragLeave: containsAcceptableDrag = false 104 | 105 | onDrop: { 106 | if (containsAcceptableDrag) { 107 | // Strip file:// prefix, we already verified in onDragEnter that we have only local URLs. 108 | iconDialog.setCustomButtonImage(event.mimeData.url.toString().substr("file://".length)); 109 | } 110 | containsAcceptableDrag = false; 111 | } 112 | } 113 | 114 | KIconThemes.IconDialog { 115 | id: iconDialog 116 | 117 | function setCustomButtonImage(image) { 118 | configGeneral.cfg_customButtonImage = image || configGeneral.cfg_icon || "start-here-kde-symbolic" 119 | configGeneral.cfg_useCustomButtonImage = true; 120 | } 121 | 122 | onIconNameChanged: setCustomButtonImage(iconName); 123 | } 124 | 125 | KSvg.FrameSvgItem { 126 | id: previewFrame 127 | anchors.centerIn: parent 128 | imagePath: Plasmoid.location === PlasmaCore.Types.Vertical || Plasmoid.location === PlasmaCore.Types.Horizontal 129 | ? "widgets/panel-background" : "widgets/background" 130 | width: Kirigami.Units.iconSizes.large + fixedMargins.left + fixedMargins.right 131 | height: Kirigami.Units.iconSizes.large + fixedMargins.top + fixedMargins.bottom 132 | 133 | Kirigami.Icon { 134 | anchors.centerIn: parent 135 | width: Kirigami.Units.iconSizes.large 136 | height: width 137 | source: configGeneral.cfg_useCustomButtonImage ? configGeneral.cfg_customButtonImage : configGeneral.cfg_icon 138 | } 139 | } 140 | 141 | Menu { 142 | id: iconMenu 143 | 144 | // Appear below the button 145 | y: +parent.height 146 | 147 | onClosed: iconButton.checked = false; 148 | 149 | MenuItem { 150 | text: i18nc("@item:inmenu Open icon chooser dialog", "Choose…") 151 | icon.name: "document-open-folder" 152 | onClicked: iconDialog.open() 153 | } 154 | MenuItem { 155 | text: i18nc("@item:inmenu Reset icon to default", "Clear Icon") 156 | icon.name: "edit-clear" 157 | onClicked: { 158 | configGeneral.cfg_icon = "start-here-kde-symbolic" 159 | configGeneral.cfg_useCustomButtonImage = false 160 | } 161 | } 162 | } 163 | } 164 | 165 | ComboBox { 166 | id: launcherPosition 167 | Kirigami.FormData.label: i18n("Launcher Positioning:") 168 | model: [ 169 | i18n("Default"), 170 | i18n("Horizontal Center"), 171 | i18n("Screen Center"), 172 | ] 173 | onCurrentIndexChanged: { 174 | if (currentIndex == 2) { 175 | floating.enabled = false 176 | floating.checked = true 177 | } else { 178 | floating.enabled = true 179 | } 180 | } 181 | } 182 | CheckBox { 183 | id: floating 184 | text: i18n("Floating") 185 | onCheckedChanged: { 186 | screenOffset.visible = checked 187 | panelOffset.visible = checked 188 | } 189 | } 190 | Slider { 191 | id: screenOffset 192 | visible: Plasmoid.configuration.floating 193 | Kirigami.FormData.label: i18n("Offset Screen Edge (0 is Default):") 194 | from: 0 195 | value: 0 196 | to: 100 197 | stepSize: 1 198 | PlasmaComponents.ToolTip { 199 | text: screenOffset.value 200 | } 201 | } 202 | Slider { 203 | id: panelOffset 204 | visible: Plasmoid.configuration.floating 205 | Kirigami.FormData.label: i18n("Offset Panel (0 is Default):") 206 | from: 0 207 | value: 0 208 | to: 100 209 | stepSize: 1 210 | PlasmaComponents.ToolTip { 211 | text: panelOffset.value 212 | } 213 | } 214 | 215 | Kirigami.Separator { 216 | Kirigami.FormData.isSection: true 217 | Kirigami.FormData.label: i18n("Grids and lists") 218 | } 219 | 220 | ComboBox { 221 | id: appsIconSize 222 | Kirigami.FormData.label: i18n("Grid icon size:") 223 | model: [i18n("Small"),i18n("Medium"),i18n("Large"), i18n("Huge")] 224 | } 225 | 226 | SpinBox{ 227 | id: numberColumns 228 | 229 | from: 4 230 | to: 8 231 | Kirigami.FormData.label: i18n("Number of columns in grid") 232 | } 233 | SpinBox{ 234 | id: numberOfRows 235 | 236 | from: 4 237 | to: 8 238 | Kirigami.FormData.label: i18n("Number of rows in grid") 239 | } 240 | CheckBox { 241 | id: compactListItems 242 | Kirigami.FormData.label: i18n("Lists:") 243 | text: i18n("Compact list items") 244 | } 245 | CheckBox { 246 | id: showListItemDescription 247 | text: i18n("Show list item description") 248 | } 249 | Item { 250 | Kirigami.FormData.isSection: true 251 | } 252 | CheckBox { 253 | id: useSystemFontSettings 254 | Kirigami.FormData.label: i18n("Use system font settings") 255 | text: i18n("Enabled") 256 | checked: Plasmoid.configuration.useSystemFontSettings 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------