├── .gitignore ├── COPYING ├── LICENSE ├── README.md ├── TODO.md ├── package ├── contents │ ├── config │ │ ├── config.qml │ │ └── main.xml │ └── ui │ │ ├── ActionMenu.qml │ │ ├── CompactRepresentation.qml │ │ ├── ConfigGeneral.qml │ │ ├── DashboardRepresentation.qml │ │ ├── ItemGridDelegate.qml │ │ ├── ItemGridView.qml │ │ ├── ItemListDelegate.qml │ │ ├── ItemListDialog.qml │ │ ├── ItemListView.qml │ │ ├── MiniButton.qml │ │ ├── code │ │ └── tools.js │ │ └── main.qml ├── metadata.desktop └── metadata.json ├── packageDark ├── contents │ ├── config │ │ ├── config.qml │ │ └── main.xml │ └── ui │ │ ├── ActionMenu.qml │ │ ├── CompactRepresentation.qml │ │ ├── ConfigGeneral.qml │ │ ├── DashboardRepresentation.qml │ │ ├── ItemGridDelegate.qml │ │ ├── ItemGridView.qml │ │ ├── ItemListDelegate.qml │ │ ├── ItemListDialog.qml │ │ ├── ItemListView.qml │ │ ├── MiniButton.qml │ │ ├── code │ │ └── tools.js │ │ └── main.qml ├── metadata.desktop └── metadata.json └── preview.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | package/CMakeLists.txt* 2 | package/qml.qrc 3 | packageDark/CMakeLists.txt* 4 | packageDark/qml.qrc 5 | 6 | check.sh 7 | 8 | build* 9 | 10 | test* 11 | 12 | # Prerequisites 13 | *.d 14 | 15 | # Compiled Object files 16 | *.slo 17 | *.lo 18 | *.o 19 | *.obj 20 | 21 | # Precompiled Headers 22 | *.gch 23 | *.pch 24 | 25 | # Compiled Dynamic libraries 26 | *.so 27 | *.dylib 28 | *.dll 29 | 30 | # Fortran module files 31 | *.mod 32 | *.smod 33 | 34 | # Compiled Static libraries 35 | *.lai 36 | *.la 37 | *.a 38 | *.lib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | NOTE! The GPL below is copyrighted by the Free Software Foundation, but 2 | the instance of code that it refers to (the kde programs) are copyrighted 3 | by the authors who actually wrote it. 4 | 5 | --------------------------------------------------------------------------- 6 | 7 | GNU GENERAL PUBLIC LICENSE 8 | Version 2, June 1991 9 | 10 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 11 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 12 | Everyone is permitted to copy and distribute verbatim copies 13 | of this license document, but changing it is not allowed. 14 | 15 | Preamble 16 | 17 | The licenses for most software are designed to take away your 18 | freedom to share and change it. By contrast, the GNU General Public 19 | License is intended to guarantee your freedom to share and change free 20 | software--to make sure the software is free for all its users. This 21 | General Public License applies to most of the Free Software 22 | Foundation's software and to any other program whose authors commit to 23 | using it. (Some other Free Software Foundation software is covered by 24 | the GNU Library General Public License instead.) You can apply it to 25 | your programs, too. 26 | 27 | When we speak of free software, we are referring to freedom, not 28 | price. Our General Public Licenses are designed to make sure that you 29 | have the freedom to distribute copies of free software (and charge for 30 | this service if you wish), that you receive source code or can get it 31 | if you want it, that you can change the software or use pieces of it 32 | in new free programs; and that you know you can do these things. 33 | 34 | To protect your rights, we need to make restrictions that forbid 35 | anyone to deny you these rights or to ask you to surrender the rights. 36 | These restrictions translate to certain responsibilities for you if you 37 | distribute copies of the software, or if you modify it. 38 | 39 | For example, if you distribute copies of such a program, whether 40 | gratis or for a fee, you must give the recipients all the rights that 41 | you have. You must make sure that they, too, receive or can get the 42 | source code. And you must show them these terms so they know their 43 | rights. 44 | 45 | We protect your rights with two steps: (1) copyright the software, and 46 | (2) offer you this license which gives you legal permission to copy, 47 | distribute and/or modify the software. 48 | 49 | Also, for each author's protection and ours, we want to make certain 50 | that everyone understands that there is no warranty for this free 51 | software. If the software is modified by someone else and passed on, we 52 | want its recipients to know that what they have is not the original, so 53 | that any problems introduced by others will not reflect on the original 54 | authors' reputations. 55 | 56 | Finally, any free program is threatened constantly by software 57 | patents. We wish to avoid the danger that redistributors of a free 58 | program will individually obtain patent licenses, in effect making the 59 | program proprietary. To prevent this, we have made it clear that any 60 | patent must be licensed for everyone's free use or not licensed at all. 61 | 62 | The precise terms and conditions for copying, distribution and 63 | modification follow. 64 | 65 | GNU GENERAL PUBLIC LICENSE 66 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 67 | 68 | 0. This License applies to any program or other work which contains 69 | a notice placed by the copyright holder saying it may be distributed 70 | under the terms of this General Public License. The "Program", below, 71 | refers to any such program or work, and a "work based on the Program" 72 | means either the Program or any derivative work under copyright law: 73 | that is to say, a work containing the Program or a portion of it, 74 | either verbatim or with modifications and/or translated into another 75 | language. (Hereinafter, translation is included without limitation in 76 | the term "modification".) Each licensee is addressed as "you". 77 | 78 | Activities other than copying, distribution and modification are not 79 | covered by this License; they are outside its scope. The act of 80 | running the Program is not restricted, and the output from the Program 81 | is covered only if its contents constitute a work based on the 82 | Program (independent of having been made by running the Program). 83 | Whether that is true depends on what the Program does. 84 | 85 | 1. You may copy and distribute verbatim copies of the Program's 86 | source code as you receive it, in any medium, provided that you 87 | conspicuously and appropriately publish on each copy an appropriate 88 | copyright notice and disclaimer of warranty; keep intact all the 89 | notices that refer to this License and to the absence of any warranty; 90 | and give any other recipients of the Program a copy of this License 91 | along with the Program. 92 | 93 | You may charge a fee for the physical act of transferring a copy, and 94 | you may at your option offer warranty protection in exchange for a fee. 95 | 96 | 2. You may modify your copy or copies of the Program or any portion 97 | of it, thus forming a work based on the Program, and copy and 98 | distribute such modifications or work under the terms of Section 1 99 | above, provided that you also meet all of these conditions: 100 | 101 | a) You must cause the modified files to carry prominent notices 102 | stating that you changed the files and the date of any change. 103 | 104 | b) You must cause any work that you distribute or publish, that in 105 | whole or in part contains or is derived from the Program or any 106 | part thereof, to be licensed as a whole at no charge to all third 107 | parties under the terms of this License. 108 | 109 | c) If the modified program normally reads commands interactively 110 | when run, you must cause it, when started running for such 111 | interactive use in the most ordinary way, to print or display an 112 | announcement including an appropriate copyright notice and a 113 | notice that there is no warranty (or else, saying that you provide 114 | a warranty) and that users may redistribute the program under 115 | these conditions, and telling the user how to view a copy of this 116 | License. (Exception: if the Program itself is interactive but 117 | does not normally print such an announcement, your work based on 118 | the Program is not required to print an announcement.) 119 | 120 | These requirements apply to the modified work as a whole. If 121 | identifiable sections of that work are not derived from the Program, 122 | and can be reasonably considered independent and separate works in 123 | themselves, then this License, and its terms, do not apply to those 124 | sections when you distribute them as separate works. But when you 125 | distribute the same sections as part of a whole which is a work based 126 | on the Program, the distribution of the whole must be on the terms of 127 | this License, whose permissions for other licensees extend to the 128 | entire whole, and thus to each and every part regardless of who wrote it. 129 | 130 | Thus, it is not the intent of this section to claim rights or contest 131 | your rights to work written entirely by you; rather, the intent is to 132 | exercise the right to control the distribution of derivative or 133 | collective works based on the Program. 134 | 135 | In addition, mere aggregation of another work not based on the Program 136 | with the Program (or with a work based on the Program) on a volume of 137 | a storage or distribution medium does not bring the other work under 138 | the scope of this License. 139 | 140 | 3. You may copy and distribute the Program (or a work based on it, 141 | under Section 2) in object code or executable form under the terms of 142 | Sections 1 and 2 above provided that you also do one of the following: 143 | 144 | a) Accompany it with the complete corresponding machine-readable 145 | source code, which must be distributed under the terms of Sections 146 | 1 and 2 above on a medium customarily used for software interchange; or, 147 | 148 | b) Accompany it with a written offer, valid for at least three 149 | years, to give any third party, for a charge no more than your 150 | cost of physically performing source distribution, a complete 151 | machine-readable copy of the corresponding source code, to be 152 | distributed under the terms of Sections 1 and 2 above on a medium 153 | customarily used for software interchange; or, 154 | 155 | c) Accompany it with the information you received as to the offer 156 | to distribute corresponding source code. (This alternative is 157 | allowed only for noncommercial distribution and only if you 158 | received the program in object code or executable form with such 159 | an offer, in accord with Subsection b above.) 160 | 161 | The source code for a work means the preferred form of the work for 162 | making modifications to it. For an executable work, complete source 163 | code means all the source code for all modules it contains, plus any 164 | associated interface definition files, plus the scripts used to 165 | control compilation and installation of the executable. However, as a 166 | special exception, the source code distributed need not include 167 | anything that is normally distributed (in either source or binary 168 | form) with the major components (compiler, kernel, and so on) of the 169 | operating system on which the executable runs, unless that component 170 | itself accompanies the executable. 171 | 172 | If distribution of executable or object code is made by offering 173 | access to copy from a designated place, then offering equivalent 174 | access to copy the source code from the same place counts as 175 | distribution of the source code, even though third parties are not 176 | compelled to copy the source along with the object code. 177 | 178 | 4. You may not copy, modify, sublicense, or distribute the Program 179 | except as expressly provided under this License. Any attempt 180 | otherwise to copy, modify, sublicense or distribute the Program is 181 | void, and will automatically terminate your rights under this License. 182 | However, parties who have received copies, or rights, from you under 183 | this License will not have their licenses terminated so long as such 184 | parties remain in full compliance. 185 | 186 | 5. You are not required to accept this License, since you have not 187 | signed it. However, nothing else grants you permission to modify or 188 | distribute the Program or its derivative works. These actions are 189 | prohibited by law if you do not accept this License. Therefore, by 190 | modifying or distributing the Program (or any work based on the 191 | Program), you indicate your acceptance of this License to do so, and 192 | all its terms and conditions for copying, distributing or modifying 193 | the Program or works based on it. 194 | 195 | 6. Each time you redistribute the Program (or any work based on the 196 | Program), the recipient automatically receives a license from the 197 | original licensor to copy, distribute or modify the Program subject to 198 | these terms and conditions. You may not impose any further 199 | restrictions on the recipients' exercise of the rights granted herein. 200 | You are not responsible for enforcing compliance by third parties to 201 | this License. 202 | 203 | 7. If, as a consequence of a court judgment or allegation of patent 204 | infringement or for any other reason (not limited to patent issues), 205 | conditions are imposed on you (whether by court order, agreement or 206 | otherwise) that contradict the conditions of this License, they do not 207 | excuse you from the conditions of this License. If you cannot 208 | distribute so as to satisfy simultaneously your obligations under this 209 | License and any other pertinent obligations, then as a consequence you 210 | may not distribute the Program at all. For example, if a patent 211 | license would not permit royalty-free redistribution of the Program by 212 | all those who receive copies directly or indirectly through you, then 213 | the only way you could satisfy both it and this License would be to 214 | refrain entirely from distribution of the Program. 215 | 216 | If any portion of this section is held invalid or unenforceable under 217 | any particular circumstance, the balance of the section is intended to 218 | apply and the section as a whole is intended to apply in other 219 | circumstances. 220 | 221 | It is not the purpose of this section to induce you to infringe any 222 | patents or other property right claims or to contest validity of any 223 | such claims; this section has the sole purpose of protecting the 224 | integrity of the free software distribution system, which is 225 | implemented by public license practices. Many people have made 226 | generous contributions to the wide range of software distributed 227 | through that system in reliance on consistent application of that 228 | system; it is up to the author/donor to decide if he or she is willing 229 | to distribute software through any other system and a licensee cannot 230 | impose that choice. 231 | 232 | This section is intended to make thoroughly clear what is believed to 233 | be a consequence of the rest of this License. 234 | 235 | 8. If the distribution and/or use of the Program is restricted in 236 | certain countries either by patents or by copyrighted interfaces, the 237 | original copyright holder who places the Program under this License 238 | may add an explicit geographical distribution limitation excluding 239 | those countries, so that distribution is permitted only in or among 240 | countries not thus excluded. In such case, this License incorporates 241 | the limitation as if written in the body of this License. 242 | 243 | 9. The Free Software Foundation may publish revised and/or new versions 244 | of the General Public License from time to time. Such new versions will 245 | be similar in spirit to the present version, but may differ in detail to 246 | address new problems or concerns. 247 | 248 | Each version is given a distinguishing version number. If the Program 249 | specifies a version number of this License which applies to it and "any 250 | later version", you have the option of following the terms and conditions 251 | either of that version or of any later version published by the Free 252 | Software Foundation. If the Program does not specify a version number of 253 | this License, you may choose any version ever published by the Free Software 254 | Foundation. 255 | 256 | 10. If you wish to incorporate parts of the Program into other free 257 | programs whose distribution conditions are different, write to the author 258 | to ask for permission. For software which is copyrighted by the Free 259 | Software Foundation, write to the Free Software Foundation; we sometimes 260 | make exceptions for this. Our decision will be guided by the two goals 261 | of preserving the free status of all derivatives of our free software and 262 | of promoting the sharing and reuse of software generally. 263 | 264 | NO WARRANTY 265 | 266 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 267 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 268 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 269 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 270 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 271 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 272 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 273 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 274 | REPAIR OR CORRECTION. 275 | 276 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 277 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 278 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 279 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 280 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 281 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 282 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 283 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 284 | POSSIBILITY OF SUCH DAMAGES. 285 | 286 | END OF TERMS AND CONDITIONS 287 | 288 | How to Apply These Terms to Your New Programs 289 | 290 | If you develop a new program, and you want it to be of the greatest 291 | possible use to the public, the best way to achieve this is to make it 292 | free software which everyone can redistribute and change under these terms. 293 | 294 | To do so, attach the following notices to the program. It is safest 295 | to attach them to the start of each source file to most effectively 296 | convey the exclusion of warranty; and each file should have at least 297 | the "copyright" line and a pointer to where the full notice is found. 298 | 299 | 300 | Copyright (C) 19yy 301 | 302 | This program is free software; you can redistribute it and/or modify 303 | it under the terms of the GNU General Public License as published by 304 | the Free Software Foundation; either version 2 of the License, or 305 | (at your option) any later version. 306 | 307 | This program is distributed in the hope that it will be useful, 308 | but WITHOUT ANY WARRANTY; without even the implied warranty of 309 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 310 | GNU General Public License for more details. 311 | 312 | You should have received a copy of the GNU General Public License 313 | along with this program; if not, write to the Free Software 314 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 315 | 316 | 317 | Also add information on how to contact you by electronic and paper mail. 318 | 319 | If the program is interactive, make it output a short notice like this 320 | when it starts in an interactive mode: 321 | 322 | Gnomovision version 69, Copyright (C) 19yy name of author 323 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 324 | This is free software, and you are welcome to redistribute it 325 | under certain conditions; type `show c' for details. 326 | 327 | The hypothetical commands `show w' and `show c' should show the appropriate 328 | parts of the General Public License. Of course, the commands you use may 329 | be called something other than `show w' and `show c'; they could even be 330 | mouse-clicks or menu items--whatever suits your program. 331 | 332 | You should also get your employer (if you work as a programmer) or your 333 | school, if any, to sign a "copyright disclaimer" for the program, if 334 | necessary. Here is a sample; alter the names: 335 | 336 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 337 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 338 | 339 | , 1 April 1989 340 | Ty Coon, President of Vice 341 | 342 | This General Public License does not permit incorporating your program into 343 | proprietary programs. If your program is a subroutine library, you may 344 | consider it more useful to permit linking proprietary applications with the 345 | library. If this is what you want to do, use the GNU Library General 346 | Public License instead of this License. 347 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Launchpad plasma 3 | 4 | A fullscreen menu (plasmoid) for kde plasma :blue_heart: 5 | 6 | ### Features 7 | - Show/hide filter list 8 | - Hide applications 9 | - Favorites apps 10 | 11 | ## Installation 12 | 13 | Download the menu from: [https://store.kde.org/p/1364064/](https://store.kde.org/p/1364064/) 14 | 15 | 16 | ## Screenshots 17 | 18 | ![menu](https://raw.githubusercontent.com/adhec/launchpad-plasma/master/preview.jpg) 19 | 20 | ## License 21 | 22 | This project is [GPL-2.0+](https://choosealicense.com/licenses/gpl-2.0/) license 23 | 24 | ## Contributing 25 | 26 | Contributions are always welcome! 27 | 28 | ## Support 29 | 30 | You can also help the project. 31 | 32 |

33 | 34 | PayPal 35 | 36 | 37 | Liberapay 38 | 39 |

40 | 41 | ## Translation 42 | 43 | [Todo] -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | [ ] change to PC3 scrollview 4 | [x] unhide all apps 5 | [ ] check recursion crash on hide apps 6 | [ ] bug keyboard "kde" grid keynav bottom 7 | [ ] add keyboard sequences 8 | -------------------------------------------------------------------------------- /package/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.0 21 | 22 | import org.kde.plasma.configuration 2.0 23 | 24 | ConfigModel { 25 | ConfigCategory { 26 | name: i18n("General") 27 | icon: "kde" 28 | source: "ConfigGeneral.qml" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | start-here-kde 12 | 13 | 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 64 22 | 23 | 24 | 56 25 | 26 | 27 | 76 28 | 29 | 30 | 32 31 | 32 | 33 | 50 34 | 35 | 36 | false 37 | 38 | 39 | true 40 | 41 | 42 | false 43 | 44 | 45 | 7 46 | 47 | 48 | 5 49 | 50 | 51 | true 52 | 53 | 54 | 55 | true 56 | 57 | 58 | 59 | 60 60 | 61 | 64 | 65 | 66 | 67 | false 68 | 69 | 70 | 71 | background.jpg 72 | 73 | 74 | 64 75 | 76 | 77 | 78 | true 79 | 80 | 81 | 48 82 | 83 | 84 | 600 85 | 86 | 87 | 0 88 | 89 | 90 | 91 | 92 | 93 | 4 94 | 95 | 96 | 2 97 | 98 | 99 | logout,reboot,shutdown 100 | 101 | 102 | 103 | 104 | 105 | false 106 | 107 | 108 | 109 | Medium 110 | 111 | 112 | 113 | bookmarks,baloosearch 114 | 115 | 116 | 117 | false 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /package/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.components 2.0 as PlasmaComponents 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 !== PlasmaComponents.DialogStatus.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 | PlasmaComponents.ContextMenu { 82 | visualParent: root.visualParent 83 | } 84 | } 85 | 86 | Component { 87 | id: contextSubmenuItemComponent 88 | 89 | PlasmaComponents.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 variant submenu : submenu_ 98 | 99 | PlasmaComponents.ContextMenu { 100 | id: submenu_ 101 | visualParent: submenuItem.action 102 | } 103 | } 104 | } 105 | 106 | Component { 107 | id: contextMenuItemComponent 108 | 109 | PlasmaComponents.MenuItem { 110 | property variant actionItem 111 | 112 | text : actionItem.text ? actionItem.text : "" 113 | enabled : actionItem.type !== "title" && ("enabled" in actionItem ? actionItem.enabled : true) 114 | separator : actionItem.type === "separator" 115 | section : actionItem.type === "title" 116 | icon : actionItem.icon ? actionItem.icon : null 117 | checkable : actionItem.checkable ? actionItem.checkable : false 118 | checked : actionItem.checked ? actionItem.checked : false 119 | 120 | onClicked: { 121 | root.actionClicked(actionItem.actionId, actionItem.actionArgument); 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /package/contents/ui/CompactRepresentation.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Layouts 1.15 9 | 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import org.kde.plasma.private.kicker 0.1 as Kicker 13 | 14 | Item { 15 | id: root 16 | 17 | readonly property bool inPanel: (plasmoid.location === PlasmaCore.Types.TopEdge 18 | || plasmoid.location === PlasmaCore.Types.RightEdge 19 | || plasmoid.location === PlasmaCore.Types.BottomEdge 20 | || plasmoid.location === PlasmaCore.Types.LeftEdge) 21 | readonly property bool vertical: (plasmoid.formFactor === PlasmaCore.Types.Vertical) 22 | readonly property bool useCustomButtonImage: (plasmoid.configuration.useCustomButtonImage 23 | && plasmoid.configuration.customButtonImage.length !== 0) 24 | 25 | readonly property Component dashWindowComponent: kicker.isDash ? Qt.createComponent(Qt.resolvedUrl("./DashboardRepresentation.qml"), root) : null 26 | readonly property Kicker.DashboardWindow dashWindow: dashWindowComponent && dashWindowComponent.status === Component.Ready 27 | ? dashWindowComponent.createObject(root, { visualParent: root }) : null 28 | 29 | onWidthChanged: updateSizeHints() 30 | onHeightChanged: updateSizeHints() 31 | 32 | function updateSizeHints() { 33 | if (useCustomButtonImage) { 34 | if (vertical) { 35 | const scaledHeight = Math.floor(parent.width * (buttonIcon.implicitHeight / buttonIcon.implicitWidth)); 36 | root.Layout.minimumHeight = scaledHeight; 37 | root.Layout.maximumHeight = scaledHeight; 38 | root.Layout.minimumWidth = PlasmaCore.Units.iconSizes.small; 39 | root.Layout.maximumWidth = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 40 | } else { 41 | const scaledWidth = Math.floor(parent.height * (buttonIcon.implicitWidth / buttonIcon.implicitHeight)); 42 | root.Layout.minimumWidth = scaledWidth; 43 | root.Layout.maximumWidth = scaledWidth; 44 | root.Layout.minimumHeight = PlasmaCore.Units.iconSizes.small; 45 | root.Layout.maximumHeight = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 46 | } 47 | } else { 48 | root.Layout.minimumWidth = PlasmaCore.Units.iconSizes.small; 49 | root.Layout.maximumWidth = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 50 | root.Layout.minimumHeight = PlasmaCore.Units.iconSizes.small; 51 | root.Layout.maximumHeight = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 52 | } 53 | } 54 | 55 | Connections { 56 | target: PlasmaCore.Units.iconSizeHints 57 | 58 | function onPanelChanged() { 59 | root.updateSizeHints() 60 | } 61 | } 62 | 63 | PlasmaCore.IconItem { 64 | id: buttonIcon 65 | 66 | readonly property double aspectRatio: root.vertical 67 | ? implicitHeight / implicitWidth 68 | : implicitWidth / implicitHeight 69 | 70 | anchors.fill: parent 71 | 72 | active: mouseArea.containsMouse && !justOpenedTimer.running 73 | smooth: true 74 | source: root.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon 75 | 76 | // A custom icon could also be rectangular. However, if a square, custom, icon is given, assume it 77 | // to be an icon and round it to the nearest icon size again to avoid scaling artifacts. 78 | roundToIconSize: !root.useCustomButtonImage || aspectRatio === 1 79 | 80 | onSourceChanged: root.updateSizeHints() 81 | } 82 | 83 | MouseArea 84 | { 85 | id: mouseArea 86 | property bool wasExpanded: false; 87 | 88 | anchors.fill: parent 89 | 90 | hoverEnabled: !root.dashWindow || !root.dashWindow.visible 91 | 92 | onPressed: { 93 | if (!kicker.isDash) { 94 | wasExpanded = plasmoid.expanded 95 | } 96 | } 97 | 98 | onClicked: { 99 | if (kicker.isDash) { 100 | root.dashWindow.toggle(); 101 | justOpenedTimer.start(); 102 | } else { 103 | plasmoid.expanded = !wasExpanded; 104 | } 105 | } 106 | } 107 | 108 | Connections { 109 | target: plasmoid 110 | enabled: kicker.isDash && root.dashWindow !== null 111 | 112 | function onActivated() { 113 | root.dashWindow.toggle(); 114 | justOpenedTimer.start(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /package/contents/ui/ConfigGeneral.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Controls 2.15 9 | 10 | import org.kde.draganddrop 2.0 as DragDrop 11 | import org.kde.kirigami 2.5 as Kirigami 12 | import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons 13 | import org.kde.plasma.core 2.0 as PlasmaCore 14 | import QtQuick.Layouts 1.0 15 | import org.kde.plasma.private.kicker 0.1 as Kicker 16 | 17 | Kirigami.FormLayout { 18 | id: configGeneral 19 | 20 | anchors.left: parent.left 21 | anchors.right: parent.right 22 | 23 | property bool isDash: (plasmoid.pluginName === "org.kde.plasma.kickerdash") 24 | 25 | property string cfg_icon: plasmoid.configuration.icon 26 | property bool cfg_useCustomButtonImage: plasmoid.configuration.useCustomButtonImage 27 | property string cfg_customButtonImage: plasmoid.configuration.customButtonImage 28 | 29 | property alias cfg_backgroundOpacity: backgroundOpacity.value 30 | property alias cfg_sizeApps: sizeApps.currentIndex 31 | property alias cfg_sizeAppsFav: sizeAppsFav.currentIndex 32 | 33 | Button { 34 | id: iconButton 35 | 36 | Kirigami.FormData.label: i18n("Icon:") 37 | 38 | implicitWidth: previewFrame.width + PlasmaCore.Units.smallSpacing * 2 39 | implicitHeight: previewFrame.height + PlasmaCore.Units.smallSpacing * 2 40 | 41 | // Just to provide some visual feedback when dragging; 42 | // cannot have checked without checkable enabled 43 | checkable: true 44 | checked: dropArea.containsAcceptableDrag 45 | 46 | onPressed: iconMenu.opened ? iconMenu.close() : iconMenu.open() 47 | 48 | DragDrop.DropArea { 49 | id: dropArea 50 | 51 | property bool containsAcceptableDrag: false 52 | 53 | anchors.fill: parent 54 | 55 | onDragEnter: { 56 | // Cannot use string operations (e.g. indexOf()) on "url" basic type. 57 | var urlString = event.mimeData.url.toString(); 58 | 59 | // This list is also hardcoded in KIconDialog. 60 | var extensions = [".png", ".xpm", ".svg", ".svgz"]; 61 | containsAcceptableDrag = urlString.indexOf("file:///") === 0 && extensions.some(function (extension) { 62 | return urlString.indexOf(extension) === urlString.length - extension.length; // "endsWith" 63 | }); 64 | 65 | if (!containsAcceptableDrag) { 66 | event.ignore(); 67 | } 68 | } 69 | onDragLeave: containsAcceptableDrag = false 70 | 71 | onDrop: { 72 | if (containsAcceptableDrag) { 73 | // Strip file:// prefix, we already verified in onDragEnter that we have only local URLs. 74 | iconDialog.setCustomButtonImage(event.mimeData.url.toString().substr("file://".length)); 75 | } 76 | containsAcceptableDrag = false; 77 | } 78 | } 79 | 80 | KQuickAddons.IconDialog { 81 | id: iconDialog 82 | 83 | function setCustomButtonImage(image) { 84 | configGeneral.cfg_customButtonImage = image || configGeneral.cfg_icon || "start-here-kde" 85 | configGeneral.cfg_useCustomButtonImage = true; 86 | } 87 | 88 | onIconNameChanged: setCustomButtonImage(iconName); 89 | } 90 | 91 | PlasmaCore.FrameSvgItem { 92 | id: previewFrame 93 | anchors.centerIn: parent 94 | imagePath: plasmoid.location === PlasmaCore.Types.Vertical || plasmoid.location === PlasmaCore.Types.Horizontal 95 | ? "widgets/panel-background" : "widgets/background" 96 | width: PlasmaCore.Units.iconSizes.large + fixedMargins.left + fixedMargins.right 97 | height: PlasmaCore.Units.iconSizes.large + fixedMargins.top + fixedMargins.bottom 98 | 99 | PlasmaCore.IconItem { 100 | anchors.centerIn: parent 101 | width: PlasmaCore.Units.iconSizes.large 102 | height: width 103 | source: configGeneral.cfg_useCustomButtonImage ? configGeneral.cfg_customButtonImage : configGeneral.cfg_icon 104 | } 105 | } 106 | 107 | Menu { 108 | id: iconMenu 109 | 110 | // Appear below the button 111 | y: +parent.height 112 | 113 | onClosed: iconButton.checked = false; 114 | 115 | MenuItem { 116 | text: i18nc("@item:inmenu Open icon chooser dialog", "Choose…") 117 | icon.name: "document-open-folder" 118 | onClicked: iconDialog.open() 119 | } 120 | MenuItem { 121 | text: i18nc("@item:inmenu Reset icon to default", "Clear Icon") 122 | icon.name: "edit-clear" 123 | onClicked: { 124 | configGeneral.cfg_icon = "start-here-kde" 125 | configGeneral.cfg_useCustomButtonImage = false 126 | } 127 | } 128 | } 129 | } 130 | 131 | 132 | Item { 133 | Kirigami.FormData.isSection: true 134 | } 135 | 136 | RowLayout{ 137 | Layout.fillWidth: true 138 | Kirigami.FormData.label: i18n("Background opacity:") 139 | Slider{ 140 | id: backgroundOpacity 141 | from: 0 142 | to: 100 143 | stepSize: 5 144 | implicitWidth: 100 145 | } 146 | Label { 147 | text: backgroundOpacity.value + "% " 148 | } 149 | } 150 | 151 | ComboBox { 152 | id: sizeApps 153 | Kirigami.FormData.label: i18n("Size icons:") 154 | model: [i18n("SmallMedium"), i18n("Medium"), i18n("Large"), i18n("Huge"), i18n("X Huge"), i18n("Enormous") ] 155 | } 156 | 157 | ComboBox { 158 | id: sizeAppsFav 159 | Kirigami.FormData.label: i18n("Size icons favorites:") 160 | model: [i18n("SmallMedium"), i18n("Medium"), i18n("Large"), i18n("Huge"), i18n("X Huge"), i18n("Enormous") ] 161 | } 162 | 163 | RowLayout{ 164 | Button { 165 | text: i18n("Unhide all applications") 166 | onClicked: { 167 | plasmoid.configuration.hiddenApplications = []; 168 | unhideAllAppsPopup.text = i18n("Unhidden!"); 169 | } 170 | } 171 | Label { 172 | id: unhideAllAppsPopup 173 | } 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /package/contents/ui/DashboardRepresentation.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtGraphicalEffects 1.15 9 | // Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6. 10 | import QtQml 2.15 11 | import QtQuick.Layouts 1.1 12 | import org.kde.kquickcontrolsaddons 2.0 13 | import org.kde.kwindowsystem 1.0 14 | import org.kde.plasma.components 2.0 as PlasmaComponents 15 | import org.kde.plasma.components 3.0 as PlasmaComponents3 16 | import org.kde.plasma.core 2.1 as PlasmaCore 17 | import org.kde.plasma.extras 2.0 as PlasmaExtras 18 | import org.kde.plasma.private.shell 2.0 19 | import QtQuick.Controls.Styles 1.4 20 | import org.kde.plasma.private.kicker 0.1 as Kicker 21 | 22 | import QtQuick.Controls 2.15 23 | 24 | import "code/tools.js" as Tools 25 | 26 | Kicker.DashboardWindow { 27 | id: root 28 | 29 | property bool smallScreen: ((Math.floor(width / PlasmaCore.Units.iconSizes.huge) <= 22) || (Math.floor(height / PlasmaCore.Units.iconSizes.huge) <= 14)) 30 | 31 | property int defaultSize: { //TODO 32 | switch(plasmoid.configuration.sizeApps){ 33 | case 0: return PlasmaCore.Units.iconSizes.smallMedium; 34 | case 1: return PlasmaCore.Units.iconSizes.medium; 35 | case 2: return PlasmaCore.Units.iconSizes.large; 36 | case 3: return PlasmaCore.Units.iconSizes.huge; 37 | case 4: return 96 38 | case 5: return PlasmaCore.Units.iconSizes.enormous; 39 | default: return 96 40 | } 41 | } 42 | 43 | property int defaultSizeFavorites: { //TODO 44 | switch(plasmoid.configuration.sizeAppsFav){ 45 | case 0: return PlasmaCore.Units.iconSizes.smallMedium; 46 | case 1: return PlasmaCore.Units.iconSizes.medium; 47 | case 2: return PlasmaCore.Units.iconSizes.large; 48 | case 3: return PlasmaCore.Units.iconSizes.huge; 49 | case 4: return 96 50 | case 5: return PlasmaCore.Units.iconSizes.enormous; 51 | default: return 96 52 | } 53 | } 54 | 55 | property int iconSize: defaultSize //smallScreen ? PlasmaCore.Units.iconSizes.large : PlasmaCore.Units.iconSizes.huge 56 | property int cellSize: iconSize + (2 * PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).height) 57 | + (2 * PlasmaCore.Units.smallSpacing) 58 | + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 59 | highlightItemSvg.margins.left + highlightItemSvg.margins.right)) 60 | 61 | property int cellSizeFav: defaultSizeFavorites 62 | + (2 * PlasmaCore.Units.smallSpacing) 63 | + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 64 | highlightItemSvg.margins.left + highlightItemSvg.margins.right)) 65 | property int columns: Math.floor(((smallScreen ? 70 : 65)/100) * Math.ceil(width / cellSize)) 66 | property bool searching: searchField.text !== "" 67 | property var widgetExplorer: null 68 | property int main_rows: Math.floor(height*0.75/cellSize) * cellSize 69 | property bool showFilters: plasmoid.configuration.showFilters 70 | 71 | keyEventProxy: searchField 72 | backgroundColor: "transparent" 73 | 74 | 75 | 76 | onKeyEscapePressed: { 77 | if (searching) { 78 | searchField.clear(); 79 | } else { 80 | root.toggle(); 81 | } 82 | } 83 | 84 | onVisibleChanged: { 85 | if(visible){ 86 | reset(); 87 | animatorMainColumn.start() 88 | globalFavoritesGrid.state = 'show' 89 | }else{ 90 | globalFavoritesGrid.state = 'hide' 91 | } 92 | } 93 | 94 | onSearchingChanged: { 95 | if (!searching) { 96 | reset(); 97 | } else { 98 | //filterList.currentIndex = -1; 99 | } 100 | } 101 | 102 | function colorWithAlpha(color, alpha) { 103 | return Qt.rgba(color.r, color.g, color.b, alpha) 104 | } 105 | 106 | 107 | function reset() { 108 | searchField.clear(); 109 | globalFavoritesGrid.currentIndex = -1; 110 | mainGrid.forceLayout() 111 | mainGrid.currentIndex = -1 112 | runnerGrid.currentIndex =-1 113 | 114 | filterList.currentIndex = 0; // force layout - all apps 115 | 116 | if(root.showFilters){ 117 | filterList.forceActiveFocus(); 118 | } 119 | else{ 120 | searchField.focus = true; 121 | } 122 | } 123 | 124 | mainItem: MouseArea { 125 | id: rootItem 126 | 127 | anchors.fill: parent 128 | 129 | acceptedButtons: Qt.LeftButton | Qt.RightButton 130 | 131 | LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft 132 | LayoutMirroring.childrenInherit: true 133 | 134 | //Connections { 135 | // target: kicker 136 | 137 | // function onReset() { 138 | // if (!root.searching) { 139 | // filterList.applyFilter(); 140 | // funnelModel.reset(); 141 | // } 142 | // } 143 | 144 | // function onDragSourceChanged() { 145 | // if (!kicker.dragSource) { 146 | // // FIXME TODO HACK: Reset all views post-DND to work around 147 | // // mouse grab bug despite QQuickWindow::mouseGrabberItem==0x0. 148 | // // Needs a more involved hunt through Qt Quick sources later since 149 | // // it's not happening with near-identical code in the menu repr. 150 | // rootModel.refresh(); 151 | // } 152 | // } 153 | //} 154 | 155 | Rectangle { 156 | color: colorWithAlpha(theme.backgroundColor, plasmoid.configuration.backgroundOpacity /100) 157 | anchors.fill: parent 158 | } 159 | 160 | Connections { 161 | target: plasmoid 162 | function onUserConfiguringChanged() { 163 | if (plasmoid.userConfiguring) { 164 | root.hide() 165 | } 166 | } 167 | } 168 | 169 | PlasmaComponents.Menu { 170 | id: contextMenu 171 | 172 | PlasmaComponents.MenuItem { 173 | action: plasmoid.action("configure") 174 | } 175 | } 176 | 177 | PlasmaExtras.Heading { 178 | id: dummyHeading 179 | 180 | visible: false 181 | 182 | width: 0 183 | 184 | level: 5 185 | } 186 | 187 | TextMetrics { 188 | id: headingMetrics 189 | 190 | font: dummyHeading.font 191 | } 192 | 193 | Kicker.FunnelModel { 194 | id: funnelModel 195 | 196 | onSourceModelChanged: { 197 | mainGrid.forceLayout(); 198 | } 199 | } 200 | 201 | 202 | Kicker.ContainmentInterface { 203 | id: containmentInterface 204 | } 205 | 206 | 207 | ColumnLayout{ 208 | anchors { 209 | right: parent.right 210 | top: parent.top 211 | margins: PlasmaCore.Units.smallSpacing *2 212 | } 213 | spacing: 2 214 | MiniButton{ 215 | icon: "system-shutdown" 216 | onClicked: { pmEngine.performOperation("requestShutDown"); root.toggle();} 217 | tooltip: i18n("Leave ...") 218 | } 219 | MiniButton{ 220 | icon: "system-lock-screen" 221 | onClicked: { pmEngine.performOperation("lockScreen"); root.toggle();} 222 | tooltip: i18n("Lock Screen") 223 | } 224 | MiniButton{ 225 | icon: "window-pin" 226 | tooltip: i18n("Show filters") 227 | onClicked: { 228 | plasmoid.configuration.showFilters = !plasmoid.configuration.showFilters 229 | reset() 230 | } 231 | } 232 | } 233 | 234 | Rectangle{ 235 | anchors.centerIn: searchField 236 | width: searchField.width + 2 237 | height: searchField.height + 4 238 | color: colorWithAlpha(theme.textColor, 0) 239 | radius: 6 240 | border.color: colorWithAlpha(theme.textColor, 0.4) //#TODO settings 241 | border.width: 1 242 | } 243 | 244 | PlasmaComponents.TextField { 245 | id: searchField 246 | anchors.bottom: mainColumn.top 247 | anchors.bottomMargin: PlasmaCore.Units.gridUnit * 3 248 | anchors.horizontalCenter: parent.horizontalCenter 249 | implicitWidth: PlasmaCore.Units.gridUnit * 16 250 | font.pointSize: smallScreen ? dummyHeading.font.pointSize : Math.ceil(dummyHeading.font.pointSize) + 3 251 | style: TextFieldStyle { 252 | textColor: theme.textColor 253 | background: Rectangle { 254 | opacity: 0 255 | } 256 | } 257 | //background: Rectangle { 258 | // opacity: 0 259 | // implicitWidth: PlasmaCore.Units.gridUnit * 16 260 | // implicitHeight: PlasmaCore.Units.gridUnit + 8 261 | //} 262 | placeholderText: i18n("Search") 263 | horizontalAlignment: TextInput.AlignHCenter 264 | 265 | onTextChanged: { 266 | runnerModel.query = searchField.text; 267 | } 268 | 269 | function clear() { 270 | text = ""; 271 | } 272 | } 273 | 274 | PlasmaCore.IconItem { 275 | source: 'nepomuk' 276 | anchors { 277 | left: searchField.left 278 | verticalCenter: searchField.verticalCenter 279 | leftMargin: PlasmaCore.Units.smallSpacing * 2 280 | } 281 | height: PlasmaCore.Units.iconSizes.small 282 | width: height 283 | } 284 | 285 | 286 | ScaleAnimator{ id: animatorMainColumn ;from: 1.05; to: 1 ; target: mainColumn; duration: PlasmaCore.Units.longDuration} 287 | 288 | Item { 289 | 290 | id: mainColumn 291 | 292 | //transformOrigin: Item.Top 293 | width: (root.columns * root.cellSize) + PlasmaCore.Units.gridUnit 294 | height: root.main_rows 295 | 296 | anchors.verticalCenter: parent.verticalCenter 297 | anchors.horizontalCenter: parent.horizontalCenter 298 | 299 | property int columns: root.columns 300 | property Item visibleGrid: searching ? runnerGrid : mainGrid 301 | 302 | 303 | function tryActivate(row, col) { 304 | if (visibleGrid) { 305 | visibleGrid.tryActivate(row, col); 306 | } 307 | } 308 | 309 | 310 | ItemGridView { 311 | id: mainGrid 312 | 313 | anchors { 314 | top: parent.top 315 | // topMargin: PlasmaCore.Units.largeSpacing 316 | } 317 | 318 | visible: !searching// opacity !== 0.0 319 | width: parent.width 320 | height: root.main_rows 321 | cellWidth: root.cellSize 322 | cellHeight: cellWidth 323 | iconSize: root.iconSize 324 | 325 | onCurrentIndexChanged: { 326 | 327 | } 328 | 329 | onKeyNavLeft: { 330 | } 331 | 332 | onKeyNavRight: { 333 | if(root.showFilters) 334 | filterListScrollArea.focus = true; 335 | } 336 | 337 | onKeyNavUp: { 338 | 339 | } 340 | 341 | onKeyNavDown: { 342 | globalFavoritesGrid.tryActivate(0,0) 343 | } 344 | 345 | onItemActivated: { 346 | 347 | } 348 | } 349 | 350 | ItemGridView { 351 | id: runnerGrid 352 | 353 | anchors { 354 | top: parent.top 355 | } 356 | width: parent.width 357 | height: root.main_rows 358 | visible: searching 359 | cellWidth: root.cellSize 360 | cellHeight: cellWidth 361 | forceFocusIndex0: true 362 | iconSize: root.iconSize 363 | model : runnerModel.count > 0 ? runnerModel.modelForRow(0) : undefined 364 | onLostFocus: { 365 | if(root.showFilters) 366 | filterList.forceActiveFocus(); 367 | else{ 368 | rootItem.focus = true 369 | } 370 | } 371 | } 372 | 373 | Keys.onPressed: event => { 374 | if(searching){ 375 | event.accepted = true; 376 | return 377 | } 378 | if (event.key === Qt.Key_Tab) { 379 | event.accepted = true; 380 | if (filterList.enabled && root.showFilters) { 381 | filterList.forceActiveFocus(); 382 | } else { 383 | globalFavoritesGrid.tryActivate(0, 0); 384 | } 385 | } else if (event.key === Qt.Key_Backtab) { 386 | event.accepted = true; 387 | if (globalFavoritesGrid.enabled) { 388 | globalFavoritesGrid.tryActivate(0, 0); 389 | } 390 | } 391 | } 392 | } 393 | 394 | Item { 395 | id: filterListColumn 396 | 397 | anchors.top: mainColumn.top 398 | anchors.bottom: mainColumn.bottom 399 | anchors.left: mainColumn.right 400 | anchors.leftMargin: PlasmaCore.Units.gridUnit 401 | anchors.topMargin: PlasmaCore.Units.gridUnit 402 | anchors.right: parent.right 403 | 404 | enabled: root.showFilters 405 | //visible: plasmoid.configuration.showFilters 406 | //onVisibleChanged: { 407 | // if(visible){ 408 | // //animatorFilters.start() 409 | // } 410 | //} 411 | //XAnimator{ id: animatorFilters ;from: 80; to: 0 ; target: filterListScrollArea; 412 | // duration: PlasmaCore.Units.longDuration; 413 | //} 414 | opacity: root.showFilters ? 1 : 0 415 | Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 416 | 417 | PlasmaComponents3.ScrollView { 418 | 419 | id: filterListScrollArea 420 | width: parent.width 421 | height: mainGrid.height 422 | 423 | enabled: !root.searching 424 | 425 | property alias currentIndex: filterList.currentIndex 426 | 427 | opacity: root.visible ? (root.searching ? 0.3 : 1.0) : 0.3 428 | 429 | Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 430 | 431 | PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff 432 | PlasmaComponents3.ScrollBar.vertical.policy: PlasmaComponents3.ScrollBar.AsNeeded 433 | 434 | onEnabledChanged: { 435 | if (!enabled) { 436 | filterList.currentIndex = -1; 437 | } 438 | } 439 | 440 | onCurrentIndexChanged: { 441 | focus = (currentIndex !== -1); 442 | } 443 | 444 | ListView { 445 | id: filterList 446 | 447 | focus: true 448 | property int eligibleWidth: width 449 | property int hItemMargins: Math.max(highlightItemSvg.margins.left + highlightItemSvg.margins.right, 450 | listItemSvg.margins.left + listItemSvg.margins.right) 451 | 452 | boundsBehavior: Flickable.StopAtBounds 453 | snapMode: ListView.SnapToItem 454 | spacing: 0 455 | keyNavigationWraps: true 456 | 457 | highlightResizeDuration: 0 458 | keyNavigationEnabled: true 459 | highlightFollowsCurrentItem: true 460 | highlightMoveDuration: 0 461 | 462 | delegate: Item { 463 | id: item 464 | 465 | property var m: model 466 | property int textWidth: label.contentWidth 467 | property int mouseCol 468 | //property bool hasActionList: ((model.favoriteId !== null) 469 | // || (("hasActionList" in model) && (model.hasActionList === true))) 470 | //property Item menu: actionMenu 471 | 472 | width: ListView.view.width 473 | height: Math.ceil((label.paintedHeight 474 | + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 475 | listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 476 | 477 | Accessible.role: Accessible.MenuItem 478 | Accessible.name: model.display 479 | 480 | PlasmaExtras.Heading { 481 | id: label 482 | 483 | anchors { 484 | fill: parent 485 | leftMargin: highlightItemSvg.margins.left 486 | rightMargin: highlightItemSvg.margins.right 487 | } 488 | 489 | elide: Text.ElideRight 490 | wrapMode: Text.NoWrap 491 | opacity: 1.0 492 | level: 2 493 | 494 | text: model.display 495 | } 496 | MouseArea { 497 | anchors.fill: parent 498 | hoverEnabled: true 499 | onClicked: item.ListView.view.currentIndex = index 500 | onContainsMouseChanged: { 501 | if(containsMouse) 502 | item.ListView.view.currentIndex = index 503 | } 504 | } 505 | } 506 | highlight: PlasmaComponents.Highlight { 507 | opacity: filterListScrollArea.focus ? 1.0 : 0.7 508 | } 509 | 510 | onCurrentIndexChanged: { 511 | if(currentIndex >= 0) 512 | animatorMainColumn.start() 513 | applyFilter() 514 | } 515 | 516 | onCountChanged: { 517 | var width = 0; 518 | for (var i = 0; i < rootModel.count; ++i) { 519 | headingMetrics.text = rootModel.labelForRow(i); 520 | if (headingMetrics.width > width) { 521 | width = headingMetrics.width; 522 | } 523 | } 524 | filterListScrollArea.width = width + hItemMargins + (PlasmaCore.Units.gridUnit * 2); 525 | } 526 | 527 | function applyFilter() { 528 | if (!root.searching && currentIndex >= 0) { 529 | var model = rootModel.modelForRow(currentIndex); 530 | funnelModel.sourceModel = model; 531 | } else { 532 | funnelModel.sourceModel = null; 533 | } 534 | } 535 | 536 | Keys.onPressed: event => { 537 | if (event.key === Qt.Key_Left) { 538 | event.accepted = true; 539 | mainColumn.tryActivate(0,0); 540 | } else if (event.key === Qt.Key_Tab) { 541 | event.accepted = true; 542 | globalFavoritesGrid.tryActivate(0,0) 543 | } else if (event.key === Qt.Key_Backtab) { 544 | event.accepted = true; 545 | mainColumn.tryActivate(0, 0); 546 | } 547 | } 548 | } 549 | } 550 | } 551 | 552 | 553 | 554 | 555 | Item { 556 | id: favoritesColumn 557 | 558 | width: Math.min(Math.floor(root.width*0.7/cellSizeFav) * cellSizeFav,globalFavoritesGrid.model.count * cellSizeFav ) + PlasmaCore.Units.gridUnit*2 559 | height: cellSizeFav 560 | anchors { 561 | bottom: parent.bottom 562 | bottomMargin: PlasmaCore.Units.smallSpacing 563 | horizontalCenter: parent.horizontalCenter 564 | } 565 | 566 | 567 | Rectangle{ 568 | id: rectFavorites 569 | color: colorWithAlpha(theme.backgroundColor, 0.3) 570 | radius: 15 571 | width: favoritesColumn.width + PlasmaCore.Units.smallSpacing*2 572 | height: favoritesColumn.height 573 | anchors.centerIn: globalFavoritesGrid 574 | } 575 | ItemGridView { 576 | id: globalFavoritesGrid 577 | 578 | state: 'hide' 579 | states: [State { 580 | name: "hide"; 581 | AnchorChanges { target: globalFavoritesGrid; anchors.top: favoritesColumn.bottom } 582 | PropertyChanges { target: globalFavoritesGrid; opacity: 0 } 583 | }, 584 | State { 585 | name: "show"; 586 | AnchorChanges { target: globalFavoritesGrid; anchors.top: favoritesColumn.top } 587 | PropertyChanges { target: globalFavoritesGrid; opacity: 1 } 588 | }] 589 | 590 | transitions: Transition { 591 | AnchorAnimation { duration: PlasmaCore.Units.longDuration} 592 | PropertyAnimation { property: "opacity" ;duration: PlasmaCore.Units.longDuration} 593 | } 594 | 595 | 596 | width: parent.width 597 | height: cellSizeFav 598 | cellWidth: cellSizeFav 599 | cellHeight: cellSizeFav 600 | iconSize: defaultSizeFavorites 601 | showLabels: false 602 | dropEnabled: true 603 | usesPlasmaTheme: false 604 | opacity: enabled ? 1.0 : 0.3 605 | onCurrentIndexChanged: { 606 | } 607 | 608 | onKeyNavUp: { 609 | mainColumn.tryActivate(0, 0); 610 | } 611 | 612 | onKeyNavRight: { 613 | } 614 | 615 | onKeyNavDown: { 616 | } 617 | 618 | Keys.onPressed: event => { 619 | if (event.key === Qt.Key_Tab) { 620 | event.accepted = true; 621 | mainColumn.tryActivate(0, 0); 622 | } else if (event.key === Qt.Key_Backtab) { 623 | event.accepted = true; 624 | } 625 | } 626 | } 627 | } 628 | 629 | Keys.onPressed: event => { 630 | if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Down || event.key === Qt.Key_Tab ) { 631 | mainColumn.tryActivate(0,0) 632 | event.accepted = true; 633 | return 634 | } 635 | if(event.modifiers & Qt.ControlModifier ||event.modifiers & Qt.ShiftModifier){ 636 | searchField.focus = true; 637 | return 638 | } 639 | } 640 | 641 | onPressed: mouse => { 642 | if (mouse.button === Qt.RightButton) { 643 | contextMenu.open(mouse.x, mouse.y); 644 | } 645 | } 646 | 647 | onClicked: mouse => { 648 | if (mouse.button === Qt.LeftButton) { 649 | root.toggle(); 650 | } 651 | } 652 | 653 | 654 | } 655 | 656 | function setModels(){ 657 | globalFavoritesGrid.model = globalFavorites 658 | filterList.model = rootModel 659 | funnelModel.sourceModel = rootModel.modelForRow(0) 660 | mainGrid.model = funnelModel 661 | 662 | } 663 | Component.onCompleted: { 664 | rootModel.refreshed.connect(setModels) 665 | rootModel.refresh(); 666 | reset() 667 | } 668 | 669 | } 670 | -------------------------------------------------------------------------------- /package/contents/ui/ItemGridDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.components 2.0 as PlasmaComponents 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import "code/tools.js" as Tools 13 | 14 | Item { 15 | id: item 16 | 17 | width: GridView.view.cellWidth 18 | height: width 19 | 20 | enabled: !model.disabled 21 | 22 | property bool showLabel: true 23 | 24 | property int itemIndex: model.index 25 | property string favoriteId: model.favoriteId !== undefined ? model.favoriteId : "" 26 | property url url: model.url !== undefined ? model.url : "" 27 | property variant icon: model.decoration !== undefined ? model.decoration : "" 28 | property var m: model 29 | property bool hasActionList: ((model.favoriteId !== null) 30 | || (("hasActionList" in model) && (model.hasActionList === true))) 31 | 32 | Accessible.role: Accessible.MenuItem 33 | Accessible.name: model.display 34 | 35 | function openActionMenu(x, y) { 36 | var actionList = hasActionList ? model.actionList : []; 37 | Tools.fillActionMenu(i18n, actionMenu, actionList, GridView.view.model.favoritesModel, model.favoriteId); 38 | actionMenu.visualParent = item; 39 | actionMenu.open(x, y); 40 | } 41 | 42 | function actionTriggered(actionId, actionArgument) { 43 | var close = (Tools.triggerAction(GridView.view.model, model.index, actionId, actionArgument) === true); 44 | 45 | if (close) { 46 | root.toggle(); 47 | } 48 | } 49 | 50 | PlasmaCore.IconItem { 51 | id: icon 52 | //y: item.showLabel ? (2 * highlightItemSvg.margins.top) : undefined 53 | //anchors.verticalCenter: item.showLabel ? undefined : parent.verticalCenter 54 | anchors.horizontalCenter: parent.horizontalCenter 55 | anchors.verticalCenter: parent.verticalCenter 56 | anchors.verticalCenterOffset: item.showLabel ? -(PlasmaCore.Units.gridUnit) : 0 57 | width: iconSize 58 | height: width 59 | colorGroup: PlasmaCore.Theme.ComplementaryColorGroup 60 | animated: false 61 | usesPlasmaTheme: item.GridView.view.usesPlasmaTheme 62 | source: model.decoration 63 | } 64 | 65 | PlasmaComponents.Label { 66 | id: label 67 | 68 | visible: item.showLabel 69 | 70 | anchors { 71 | top: icon.bottom 72 | topMargin: PlasmaCore.Units.smallSpacing 73 | left: parent.left 74 | leftMargin: highlightItemSvg.margins.left 75 | right: parent.right 76 | rightMargin: highlightItemSvg.margins.right 77 | } 78 | 79 | horizontalAlignment: Text.AlignHCenter 80 | 81 | maximumLineCount: 2 82 | elide: Text.ElideMiddle 83 | wrapMode: Text.Wrap 84 | // color: "white" // FIXME TODO: Respect theming? 85 | text: ("name" in model ? model.name : model.display) 86 | } 87 | 88 | PlasmaCore.ToolTipArea { 89 | id: toolTip 90 | 91 | property string text: model.display 92 | 93 | anchors.fill: parent 94 | active: root.visible && label.truncated 95 | mainItem: toolTipDelegate 96 | 97 | onContainsMouseChanged: item.GridView.view.itemContainsMouseChanged(containsMouse) 98 | } 99 | 100 | Keys.onPressed: event => { 101 | if (event.key === Qt.Key_Menu && hasActionList) { 102 | event.accepted = true; 103 | openActionMenu(item); 104 | } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { 105 | event.accepted = true; 106 | 107 | if ("trigger" in GridView.view.model) { 108 | GridView.view.model.trigger(index, "", null); 109 | root.toggle(); 110 | } 111 | 112 | itemGrid.itemActivated(index, "", null); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /package/contents/ui/ItemGridView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.draganddrop 2.0 10 | import org.kde.kquickcontrolsaddons 2.0 11 | import org.kde.plasma.components 2.0 as PlasmaComponents 12 | import org.kde.plasma.core 2.0 as PlasmaCore 13 | import org.kde.plasma.extras 2.0 as PlasmaExtras 14 | import org.kde.plasma.components 3.0 as PlasmaComponents3 15 | 16 | FocusScope { 17 | id: itemGrid 18 | 19 | signal keyNavLeft 20 | signal keyNavRight 21 | signal keyNavUp 22 | signal keyNavDown 23 | 24 | signal itemActivated(int index, string actionId, string argument) 25 | signal lostFocus() 26 | 27 | property bool dragEnabled: true 28 | property bool dropEnabled: false 29 | property bool showLabels: true 30 | property alias usesPlasmaTheme: gridView.usesPlasmaTheme 31 | property bool forceFocusIndex0: false 32 | 33 | property alias currentIndex: gridView.currentIndex 34 | property alias currentItem: gridView.currentItem 35 | property alias contentItem: gridView.contentItem 36 | property alias count: gridView.count 37 | property alias model: gridView.model 38 | 39 | property alias cellWidth: gridView.cellWidth 40 | property alias cellHeight: gridView.cellHeight 41 | property alias iconSize: gridView.iconSize 42 | 43 | 44 | 45 | onDropEnabledChanged: { 46 | if (!dropEnabled && "dropPlaceHolderIndex" in model) { 47 | model.dropPlaceHolderIndex = -1; 48 | } 49 | } 50 | 51 | onFocusChanged: { 52 | if (!focus && !root.keyEventProxy.activeFocus) { 53 | currentIndex = -1; 54 | } 55 | if(!focus) 56 | lostFocus() 57 | } 58 | 59 | function currentRow() { 60 | if (currentIndex === -1) { 61 | return -1; 62 | } 63 | 64 | return Math.floor(currentIndex / Math.floor(width / itemGrid.cellWidth)); 65 | } 66 | 67 | function currentCol() { 68 | if (currentIndex === -1) { 69 | return -1; 70 | } 71 | 72 | return currentIndex - (currentRow() * Math.floor(width / itemGrid.cellWidth)); 73 | } 74 | 75 | function lastRow() { 76 | var columns = Math.floor(width / itemGrid.cellWidth); 77 | return Math.ceil(count / columns) - 1; 78 | } 79 | 80 | function tryActivate(row, col) { 81 | if (count) { 82 | var columns = Math.floor(width / itemGrid.cellWidth); 83 | var rows = Math.ceil(count / columns); 84 | row = Math.min(row, rows - 1); 85 | col = Math.min(col, columns - 1); 86 | currentIndex = Math.min(row ? ((Math.max(1, row) * columns) + col) 87 | : col, 88 | count - 1); 89 | focus = true; 90 | } 91 | } 92 | 93 | function forceLayout() { 94 | gridView.forceLayout(); 95 | } 96 | 97 | ActionMenu { 98 | id: actionMenu 99 | 100 | onActionClicked: { 101 | visualParent.actionTriggered(actionId, actionArgument); 102 | } 103 | } 104 | 105 | DropArea { 106 | id: dropArea 107 | 108 | //anchors.fill: parent 109 | width: itemGrid.width - PlasmaCore.Units.largeSpacing // TODO cover scrollbar 110 | height: itemGrid.height 111 | 112 | 113 | onDragMove: { 114 | if (!itemGrid.dropEnabled || gridView.animating || !kicker.dragSource) { 115 | return; 116 | } 117 | 118 | var x = Math.max(0, event.x - (width % itemGrid.cellWidth)); 119 | var cPos = mapToItem(gridView.contentItem, x, event.y); 120 | var item = gridView.itemAt(cPos.x, cPos.y); 121 | 122 | if (item) { 123 | if (kicker.dragSource.parent === gridView.contentItem) { 124 | if (item !== kicker.dragSource) { 125 | item.GridView.view.model.moveRow(dragSource.itemIndex, item.itemIndex); 126 | } 127 | } else if (kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model 128 | && !itemGrid.model.isFavorite(kicker.dragSource.favoriteId)) { 129 | var hasPlaceholder = (itemGrid.model.dropPlaceholderIndex !== -1); 130 | 131 | itemGrid.model.dropPlaceholderIndex = item.itemIndex; 132 | 133 | if (!hasPlaceholder) { 134 | gridView.currentIndex = (item.itemIndex - 1); 135 | } 136 | } 137 | } else if (kicker.dragSource.parent !== gridView.contentItem 138 | && kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model 139 | && !itemGrid.model.isFavorite(kicker.dragSource.favoriteId)) { 140 | var hasPlaceholder = (itemGrid.model.dropPlaceholderIndex !== -1); 141 | 142 | itemGrid.model.dropPlaceholderIndex = hasPlaceholder ? itemGrid.model.count - 1 : itemGrid.model.count; 143 | 144 | if (!hasPlaceholder) { 145 | gridView.currentIndex = (itemGrid.model.count - 1); 146 | } 147 | } else { 148 | itemGrid.model.dropPlaceholderIndex = -1; 149 | gridView.currentIndex = -1; 150 | } 151 | } 152 | 153 | onDragLeave: { 154 | if ("dropPlaceholderIndex" in itemGrid.model) { 155 | itemGrid.model.dropPlaceholderIndex = -1; 156 | gridView.currentIndex = -1; 157 | } 158 | } 159 | 160 | onDrop: { 161 | if (kicker.dragSource && kicker.dragSource.parent !== gridView.contentItem && kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model) { 162 | itemGrid.model.addFavorite(kicker.dragSource.favoriteId, itemGrid.model.dropPlaceholderIndex); 163 | gridView.currentIndex = -1; 164 | } 165 | } 166 | 167 | Timer { 168 | id: resetAnimationDurationTimer 169 | 170 | interval: 120 171 | repeat: false 172 | 173 | onTriggered: { 174 | gridView.animationDuration = interval - 20; 175 | } 176 | } 177 | 178 | PlasmaComponents3.ScrollView { 179 | id: scrollArea 180 | 181 | width: itemGrid.width 182 | height: itemGrid.height 183 | 184 | focus: true 185 | PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff 186 | 187 | 188 | GridView { 189 | id: gridView 190 | 191 | anchors.fill: parent 192 | signal itemContainsMouseChanged(bool containsMouse) 193 | 194 | property bool usesPlasmaTheme: false 195 | property int iconSize: PlasmaCore.Units.iconSizes.huge 196 | property bool animating: false 197 | property int animationDuration: itemGrid.dropEnabled ? resetAnimationDurationTimer.interval : 0 198 | focus: true 199 | 200 | currentIndex: -1 201 | 202 | move: Transition { 203 | enabled: itemGrid.dropEnabled 204 | 205 | SequentialAnimation { 206 | PropertyAction { target: gridView; property: "animating"; value: true } 207 | 208 | NumberAnimation { 209 | duration: gridView.animationDuration 210 | properties: "x, y" 211 | easing.type: Easing.OutQuad 212 | } 213 | 214 | PropertyAction { target: gridView; property: "animating"; value: false } 215 | } 216 | } 217 | 218 | moveDisplaced: Transition { 219 | enabled: itemGrid.dropEnabled 220 | 221 | SequentialAnimation { 222 | PropertyAction { target: gridView; property: "animating"; value: true } 223 | 224 | NumberAnimation { 225 | duration: gridView.animationDuration 226 | properties: "x, y" 227 | easing.type: Easing.OutQuad 228 | } 229 | 230 | PropertyAction { target: gridView; property: "animating"; value: false } 231 | } 232 | } 233 | 234 | keyNavigationWraps: false 235 | boundsBehavior: Flickable.StopAtBounds 236 | 237 | delegate: ItemGridDelegate { 238 | showLabel: itemGrid.showLabels 239 | } 240 | 241 | highlight: PlasmaComponents.Highlight { 242 | 243 | } 244 | 245 | highlightFollowsCurrentItem: true 246 | highlightMoveDuration: 0 247 | 248 | onCurrentIndexChanged: { 249 | if (currentIndex !== -1) { 250 | hoverArea.hoverEnabled = false 251 | focus = true; 252 | } 253 | } 254 | 255 | onCountChanged: { 256 | if(itemGrid.forceFocusIndex0){ 257 | if(count > 0){ 258 | forceLayout() 259 | currentIndex = 0; 260 | itemGrid.focus = true 261 | }else{ 262 | currentIndex = -1; 263 | itemGrid.focus = false 264 | } 265 | } 266 | animationDuration = 0; 267 | //resetAnimationDurationTimer.start(); 268 | } 269 | 270 | onModelChanged: { 271 | currentIndex = -1; 272 | } 273 | 274 | Keys.onLeftPressed: { 275 | if (itemGrid.currentCol() !== 0) { 276 | event.accepted = true; 277 | moveCurrentIndexLeft(); 278 | } else { 279 | itemGrid.keyNavLeft(); 280 | } 281 | } 282 | 283 | Keys.onRightPressed: { 284 | var columns = Math.floor(width / cellWidth); 285 | 286 | if (itemGrid.currentCol() !== columns - 1 && currentIndex !== count -1) { 287 | event.accepted = true; 288 | moveCurrentIndexRight(); 289 | } else { 290 | itemGrid.keyNavRight(); 291 | } 292 | } 293 | 294 | Keys.onUpPressed: { 295 | if (itemGrid.currentRow() !== 0) { 296 | event.accepted = true; 297 | moveCurrentIndexUp(); 298 | positionViewAtIndex(currentIndex, GridView.Contain); 299 | } else { 300 | itemGrid.keyNavUp(); 301 | } 302 | } 303 | 304 | Keys.onDownPressed: { 305 | if(itemGrid.currentRow() === -1 && itemGrid.count > 0 ){ 306 | currentIndex = 0 307 | } 308 | else{ 309 | if (itemGrid.currentRow() < itemGrid.lastRow()) { 310 | // Fix moveCurrentIndexDown()'s lack of proper spatial nav down 311 | // into partial columns. 312 | event.accepted = true; 313 | var columns = Math.floor(width / cellWidth); 314 | var newIndex = currentIndex + columns; 315 | currentIndex = Math.min(newIndex, count - 1); 316 | positionViewAtIndex(currentIndex, GridView.Contain); 317 | } else { 318 | itemGrid.keyNavDown(); 319 | 320 | } 321 | } 322 | } 323 | 324 | onItemContainsMouseChanged: { 325 | if (!containsMouse) { 326 | if (!actionMenu.opened) { 327 | gridView.currentIndex = -1; 328 | } 329 | 330 | hoverArea.pressX = -1; 331 | hoverArea.pressY = -1; 332 | hoverArea.lastX = -1; 333 | hoverArea.lastY = -1; 334 | hoverArea.pressedItem = null; 335 | hoverArea.hoverEnabled = true; 336 | } 337 | } 338 | } 339 | } 340 | 341 | MouseArea { 342 | id: hoverArea 343 | 344 | anchors.fill: parent 345 | 346 | property int pressX: -1 347 | property int pressY: -1 348 | property int lastX: -1 349 | property int lastY: -1 350 | property Item pressedItem: null 351 | 352 | acceptedButtons: Qt.LeftButton | Qt.RightButton 353 | 354 | hoverEnabled: true 355 | 356 | function updatePositionProperties(x, y) { 357 | // Prevent hover event synthesis in QQuickWindow interfering 358 | // with keyboard navigation by ignoring repeated events with 359 | // identical coordinates. As the work done here would be re- 360 | // dundant in any case, these are safe to ignore. 361 | if (lastX === x && lastY === y) { 362 | return; 363 | } 364 | 365 | lastX = x; 366 | lastY = y; 367 | 368 | var cPos = mapToItem(gridView.contentItem, x, y); 369 | var item = gridView.itemAt(cPos.x, cPos.y); 370 | 371 | if (!item) { 372 | gridView.currentIndex = -1; 373 | pressedItem = null; 374 | } else { 375 | gridView.currentIndex = item.itemIndex; 376 | itemGrid.focus = (itemGrid.currentIndex !== -1) 377 | } 378 | 379 | return item; 380 | } 381 | 382 | onPressed: mouse => { 383 | mouse.accepted = true; 384 | 385 | updatePositionProperties(mouse.x, mouse.y); 386 | 387 | pressX = mouse.x; 388 | pressY = mouse.y; 389 | 390 | if (mouse.button === Qt.RightButton) { 391 | if (gridView.currentItem) { 392 | if (gridView.currentItem.hasActionList) { 393 | var mapped = mapToItem(gridView.currentItem, mouse.x, mouse.y); 394 | gridView.currentItem.openActionMenu(mapped.x, mapped.y); 395 | } 396 | }// else { 397 | // var mapped = mapToItem(rootItem, mouse.x, mouse.y); 398 | // contextMenu.open(mapped.x, mapped.y); 399 | //} 400 | } else { 401 | pressedItem = gridView.currentItem; 402 | } 403 | } 404 | 405 | onReleased: mouse => { 406 | mouse.accepted = true; 407 | updatePositionProperties(mouse.x, mouse.y); 408 | 409 | if (!dragHelper.dragging) { 410 | if (pressedItem) { 411 | if ("trigger" in gridView.model) { 412 | gridView.model.trigger(pressedItem.itemIndex, "", null); 413 | root.toggle(); 414 | } 415 | 416 | itemGrid.itemActivated(pressedItem.itemIndex, "", null); 417 | } else if (mouse.button === Qt.LeftButton) { 418 | root.toggle(); 419 | } 420 | } 421 | 422 | pressX = pressY = -1; 423 | pressedItem = null; 424 | } 425 | 426 | onPositionChanged: mouse => { 427 | var item = pressedItem? pressedItem : updatePositionProperties(mouse.x, mouse.y); 428 | 429 | if (gridView.currentIndex !== -1) { 430 | if (itemGrid.dragEnabled && pressX !== -1 && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { 431 | if ("pluginName" in item.m) { 432 | dragHelper.startDrag(kicker, item.url, item.icon, 433 | "text/x-plasmoidservicename", item.m.pluginName); 434 | } else { 435 | dragHelper.startDrag(kicker, item.url, item.icon); 436 | } 437 | 438 | kicker.dragSource = item; 439 | 440 | pressX = -1; 441 | pressY = -1; 442 | } 443 | } 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /package/contents/ui/ItemListDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.components 2.0 as PlasmaComponents 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import "code/tools.js" as Tools 13 | 14 | Item { 15 | id: item 16 | 17 | height: isSeparator ? separatorHeight : itemHeight 18 | width: ListView.view.width 19 | 20 | // if it's not disabled and is either a leaf node or a node with children 21 | enabled: !isSeparator && !model.disabled && (!isParent || (isParent && hasChildren)) 22 | 23 | signal actionTriggered(string actionId, variant actionArgument) 24 | signal aboutToShowActionMenu(variant actionMenu) 25 | 26 | readonly property real fullTextWidth: Math.ceil(icon.width + label.implicitWidth + arrow.width + row.anchors.leftMargin + row.anchors.rightMargin + row.actualSpacing) 27 | property bool isSeparator: (model.isSeparator === true) 28 | property bool sorted: (model.sorted === true) 29 | property bool hasChildren: (model.hasChildren === true) 30 | property bool hasActionList: ((model.favoriteId !== null) 31 | || (("hasActionList" in model) && (model.hasActionList === true))) 32 | property QtObject childDialog: null 33 | property Item menu: actionMenu 34 | 35 | Accessible.role: isSeparator ? Accessible.Separator: Accessible.MenuItem 36 | Accessible.name: label.text 37 | 38 | onHasChildrenChanged: { 39 | if (!hasChildren && ListView.view.currentItem === item) { 40 | ListView.view.currentIndex = -1; 41 | } 42 | } 43 | 44 | onAboutToShowActionMenu: { 45 | var actionList = item.hasActionList ? model.actionList : []; 46 | Tools.fillActionMenu(i18n, actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId); 47 | } 48 | 49 | onActionTriggered: { 50 | if (Tools.triggerAction(ListView.view.model, model.index, actionId, actionArgument) === true) { 51 | plasmoid.expanded = false; 52 | } 53 | } 54 | 55 | function openActionMenu(visualParent, x, y) { 56 | aboutToShowActionMenu(actionMenu); 57 | actionMenu.visualParent = visualParent; 58 | actionMenu.open(x, y); 59 | } 60 | 61 | ActionMenu { 62 | id: actionMenu 63 | 64 | onActionClicked: { 65 | item.actionTriggered(actionId, actionArgument); 66 | } 67 | } 68 | 69 | MouseArea { 70 | id: mouseArea 71 | 72 | anchors { 73 | left: parent.left 74 | right: parent.right 75 | verticalCenter: parent.verticalCenter 76 | } 77 | 78 | height: parent.height 79 | 80 | property int mouseCol 81 | property bool pressed: false 82 | property int pressX: -1 83 | property int pressY: -1 84 | 85 | hoverEnabled: true 86 | acceptedButtons: Qt.LeftButton | Qt.RightButton 87 | 88 | onPressed: mouse => { 89 | if (mouse.buttons & Qt.RightButton) { 90 | if (item.hasActionList) { 91 | item.openActionMenu(mouseArea, mouse.x, mouse.y); 92 | } 93 | } else { 94 | pressed = true; 95 | pressX = mouse.x; 96 | pressY = mouse.y; 97 | } 98 | } 99 | 100 | onReleased: mouse => { 101 | if (pressed && !item.hasChildren) { 102 | item.ListView.view.model.trigger(index, "", null); 103 | plasmoid.expanded = false; 104 | } 105 | 106 | pressed = false; 107 | pressX = -1; 108 | pressY = -1; 109 | } 110 | 111 | onPositionChanged: mouse => { 112 | if (pressX !== -1 && model.url && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { 113 | dragHelper.startDrag(kicker, model.url, model.decoration); 114 | pressed = false; 115 | pressX = -1; 116 | pressY = -1; 117 | 118 | return; 119 | } 120 | 121 | // FIXME: Correct escape angle calc for right screen edge. 122 | if (justOpenedTimer.running || !item.hasChildren) { 123 | item.ListView.view.currentIndex = index; 124 | } else { 125 | mouseCol = mouse.x; 126 | 127 | if (index === item.ListView.view.currentIndex) { 128 | updateCurrentItem(); 129 | } else if ((index === item.ListView.view.currentIndex - 1) && mouse.y < (itemHeight - 6) 130 | || (index === item.ListView.view.currentIndex + 1) && mouse.y > 5) { 131 | 132 | if ((item.childDialog && item.childDialog.facingLeft) 133 | ? mouse.x > item.ListView.view.eligibleWidth - 5 : mouse.x < item.ListView.view.eligibleWidth + 5) { 134 | updateCurrentItem(); 135 | } 136 | } else if ((item.childDialog && item.childDialog.facingLeft) 137 | ? mouse.x > item.ListView.view.eligibleWidth : mouse.x < item.ListView.view.eligibleWidth) { 138 | updateCurrentItem(); 139 | } 140 | 141 | updateCurrentItemTimer.start(); 142 | } 143 | } 144 | 145 | onContainsMouseChanged: { 146 | if (!containsMouse) { 147 | pressed = false; 148 | pressX = -1; 149 | pressY = -1; 150 | updateCurrentItemTimer.stop(); 151 | } 152 | } 153 | 154 | function updateCurrentItem() { 155 | item.ListView.view.currentIndex = index; 156 | item.ListView.view.eligibleWidth = Math.min(width, mouseCol); 157 | } 158 | 159 | Timer { 160 | id: updateCurrentItemTimer 161 | 162 | interval: 50 163 | repeat: false 164 | 165 | onTriggered: parent.updateCurrentItem() 166 | } 167 | } 168 | 169 | Row { 170 | id: row 171 | 172 | anchors.left: parent.left 173 | anchors.leftMargin: highlightItemSvg.margins.left 174 | anchors.right: parent.right 175 | anchors.rightMargin: highlightItemSvg.margins.right 176 | 177 | height: parent.height 178 | 179 | spacing: PlasmaCore.Units.smallSpacing * 2 180 | readonly property real actualSpacing: ((icon.visible ? 1 : 0) * spacing) + ((arrow.visible ? 1 : 0) * spacing) 181 | 182 | LayoutMirroring.enabled: (Qt.application.layoutDirection === Qt.RightToLeft) 183 | 184 | PlasmaCore.IconItem { 185 | id: icon 186 | 187 | anchors.verticalCenter: parent.verticalCenter 188 | 189 | width: visible ? PlasmaCore.Units.iconSizes.small : 0 190 | height: width 191 | 192 | visible: iconsEnabled 193 | 194 | animated: false 195 | usesPlasmaTheme: false 196 | 197 | source: model.decoration 198 | } 199 | 200 | PlasmaComponents.Label { 201 | id: label 202 | 203 | enabled: !isParent || (isParent && item.hasChildren) 204 | 205 | anchors.verticalCenter: parent.verticalCenter 206 | 207 | width: parent.width - icon.width - arrow.width - parent.actualSpacing 208 | 209 | verticalAlignment: Text.AlignVCenter 210 | 211 | textFormat: Text.PlainText 212 | wrapMode: Text.NoWrap 213 | elide: Text.ElideRight 214 | 215 | text: model.display 216 | } 217 | 218 | PlasmaCore.SvgItem { 219 | id: arrow 220 | 221 | anchors.verticalCenter: parent.verticalCenter 222 | 223 | width: visible ? PlasmaCore.Units.iconSizes.small : 0 224 | height: width 225 | 226 | visible: item.hasChildren 227 | opacity: (item.ListView.view.currentIndex === index) ? 1.0 : 0.4 228 | 229 | svg: arrows 230 | elementId: (Qt.application.layoutDirection === Qt.RightToLeft) ? "left-arrow" : "right-arrow" 231 | } 232 | } 233 | 234 | Component { 235 | id: separatorComponent 236 | 237 | PlasmaCore.SvgItem { 238 | width: parent.width 239 | height: lineSvg.horLineHeight 240 | 241 | svg: lineSvg 242 | elementId: "horizontal-line" 243 | } 244 | } 245 | 246 | Loader { 247 | id: separatorLoader 248 | 249 | anchors.left: parent.left 250 | anchors.leftMargin: highlightItemSvg.margins.left 251 | anchors.right: parent.right 252 | anchors.rightMargin: highlightItemSvg.margins.right 253 | anchors.verticalCenter: parent.verticalCenter 254 | 255 | // Separator positions don't make sense when sorting everything alphabetically 256 | active: item.isSeparator && !item.sorted 257 | 258 | asynchronous: false 259 | sourceComponent: separatorComponent 260 | } 261 | 262 | Keys.onPressed: { 263 | if (event.key === Qt.Key_Menu && item.hasActionList) { 264 | event.accepted = true; 265 | item.openActionMenu(mouseArea); 266 | } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return) && !item.hasChildren) { 267 | if (!item.hasChildren) { 268 | event.accepted = true; 269 | item.ListView.view.model.trigger(index, "", null); 270 | plasmoid.expanded = false; 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /package/contents/ui/ItemListDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.core 2.0 as PlasmaCore 10 | import org.kde.plasma.plasmoid 2.0 11 | 12 | import org.kde.plasma.private.kicker 0.1 as Kicker 13 | 14 | Kicker.SubMenu { 15 | id: itemDialog 16 | 17 | property alias focusParent: itemListView.focusParent 18 | property alias model: funnelModel.sourceModel 19 | 20 | property bool aboutToBeDestroyed: false 21 | 22 | visible: false 23 | hideOnWindowDeactivate: plasmoid.hideOnWindowDeactivate 24 | location: PlasmaCore.Types.Floating 25 | offset: PlasmaCore.Units.smallSpacing 26 | 27 | onWindowDeactivated: { 28 | if (!aboutToBeDestroyed) { 29 | plasmoid.expanded = false; 30 | } 31 | } 32 | 33 | mainItem: ItemListView { 34 | id: itemListView 35 | 36 | height: { 37 | const m = funnelModel.sourceModel; 38 | 39 | if (m === null || m === undefined) { 40 | // TODO: setting height to 0 triggers a warning in PlasmaQuick::Dialog 41 | return 0; 42 | } 43 | 44 | return Math.min( 45 | // either fit in screen boundaries (cut to the nearest item/separator boundary), ... 46 | __subFloorMultipleOf( 47 | itemDialog.availableScreenRectForItem(itemListView).height 48 | - itemDialog.margins.top 49 | - itemDialog.margins.bottom, 50 | itemHeight 51 | ) + m.separatorCount * (separatorHeight - itemHeight) 52 | , 53 | // ...or fit the content itself -- whichever is shorter. 54 | ((m.count - m.separatorCount) * itemHeight) 55 | + (m.separatorCount * separatorHeight) 56 | ); 57 | } 58 | 59 | // get x rounded down to the multiple of y, minus extra y. 60 | function __subFloorMultipleOf(x : real, y : real) : real { 61 | return (Math.floor(x / y) - 1) * y; 62 | } 63 | 64 | iconsEnabled: true 65 | 66 | dialog: itemDialog 67 | 68 | model: funnelModel 69 | 70 | Kicker.FunnelModel { 71 | id: funnelModel 72 | 73 | Component.onCompleted: { 74 | kicker.reset.connect(funnelModel.reset); 75 | } 76 | 77 | onCountChanged: { 78 | if (sourceModel && count === 0) { 79 | itemDialog.delayedDestroy(); 80 | } 81 | } 82 | 83 | onSourceModelChanged: { 84 | itemListView.currentIndex = -1; 85 | } 86 | } 87 | } 88 | 89 | function delayedDestroy() { 90 | aboutToBeDestroyed = true; 91 | plasmoid.hideOnWindowDeactivate = false; 92 | visible = false; 93 | 94 | Qt.callLater(() => itemDialog.destroy()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /package/contents/ui/ItemListView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.kquickcontrolsaddons 2.0 10 | import org.kde.plasma.components 2.0 as PlasmaComponents 11 | import org.kde.plasma.core 2.0 as PlasmaCore 12 | import org.kde.plasma.extras 2.0 as PlasmaExtras 13 | 14 | FocusScope { 15 | id: itemList 16 | 17 | property real minimumWidth: PlasmaCore.Units.gridUnit * 14 18 | property real maximumWidth: minimumWidth * 2 19 | 20 | width: minimumWidth 21 | height: listView.contentHeight 22 | 23 | signal exited 24 | signal keyNavigationAtListEnd 25 | signal appendSearchText(string text) 26 | 27 | property Item focusParent: null 28 | property QtObject dialog: null 29 | property QtObject childDialog: null 30 | property bool iconsEnabled: false 31 | property int itemHeight: Math.ceil((Math.max(theme.mSize(theme.defaultFont).height, PlasmaCore.Units.iconSizes.small) 32 | + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 33 | listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 34 | property int separatorHeight: lineSvg.horLineHeight + (2 * PlasmaCore.Units.smallSpacing) 35 | 36 | property alias currentIndex: listView.currentIndex 37 | property alias currentItem: listView.currentItem 38 | property alias keyNavigationWraps: listView.keyNavigationWraps 39 | property alias showChildDialogs: listView.showChildDialogs 40 | property alias model: listView.model 41 | property alias count: listView.count 42 | property alias containsMouse: listener.containsMouse 43 | property alias resetOnExitDelay: resetIndexTimer.interval 44 | 45 | onFocusParentChanged: { 46 | appendSearchText.connect(focusParent.appendSearchText); 47 | } 48 | 49 | Timer { 50 | id: dialogSpawnTimer 51 | 52 | property bool focusOnSpawn: false 53 | 54 | interval: 70 55 | repeat: false 56 | 57 | onTriggered: { 58 | if (!plasmoid.expanded || model === undefined || currentIndex == -1) { 59 | return; 60 | } 61 | 62 | if (itemList.childDialog != null) { 63 | itemList.childDialog.delayedDestroy(); 64 | } 65 | 66 | // Gets reenabled after the dialog spawn causes a focus-in on the dialog window. 67 | plasmoid.hideOnWindowDeactivate = false; 68 | 69 | itemList.childDialog = itemListDialogComponent.createObject(itemList); 70 | itemList.childDialog.focusParent = itemList; 71 | itemList.childDialog.visualParent = listView.currentItem; 72 | itemList.childDialog.model = model.modelForRow(listView.currentIndex); 73 | itemList.childDialog.visible = true; 74 | 75 | windowSystem.forceActive(itemList.childDialog.mainItem); 76 | itemList.childDialog.mainItem.focus = true; 77 | 78 | if (focusOnSpawn) { 79 | itemList.childDialog.mainItem.showChildDialogs = false; 80 | itemList.childDialog.mainItem.currentIndex = 0; 81 | itemList.childDialog.mainItem.showChildDialogs = true; 82 | } 83 | } 84 | } 85 | 86 | Timer { 87 | id: resetIndexTimer 88 | 89 | interval: (dialog != null) ? 50 : 150 90 | repeat: false 91 | 92 | onTriggered: { 93 | if (focus && (!itemList.childDialog || !itemList.childDialog.mainItem.containsMouse)) { 94 | currentIndex = -1; 95 | itemList.exited(); 96 | } 97 | } 98 | } 99 | 100 | MouseEventListener { 101 | id: listener 102 | 103 | anchors.fill: parent 104 | 105 | hoverEnabled: true 106 | 107 | onContainsMouseChanged: { 108 | listView.eligibleWidth = listView.width; 109 | 110 | if (containsMouse) { 111 | resetIndexTimer.stop(); 112 | itemList.forceActiveFocus(); 113 | } else if ((!itemList.childDialog || !dialog) 114 | && (!currentItem || !currentItem.menu.opened)) { 115 | resetIndexTimer.start(); 116 | } 117 | } 118 | 119 | PlasmaExtras.ScrollArea { 120 | anchors.fill: parent 121 | 122 | focus: true 123 | 124 | ListView { 125 | id: listView 126 | 127 | property bool showChildDialogs: true 128 | property int eligibleWidth: width 129 | 130 | currentIndex: -1 131 | 132 | boundsBehavior: Flickable.StopAtBounds 133 | snapMode: ListView.SnapToItem 134 | spacing: 0 135 | keyNavigationWraps: (dialog != null) 136 | 137 | delegate: ItemListDelegate { 138 | onFullTextWidthChanged: { 139 | if (fullTextWidth > itemList.width) itemList.width = Math.min(fullTextWidth, itemList.maximumWidth); 140 | } 141 | } 142 | 143 | highlight: PlasmaComponents.Highlight { 144 | visible: listView.currentItem && !listView.currentItem.isSeparator 145 | } 146 | 147 | highlightMoveDuration: 0 148 | 149 | onCountChanged: { 150 | currentIndex = -1; 151 | } 152 | 153 | onCurrentIndexChanged: { 154 | if (currentIndex != -1) { 155 | if (itemList.childDialog) { 156 | if (currentItem && currentItem.hasChildren) { 157 | itemList.childDialog.mainItem.width = itemList.minimumWidth; 158 | itemList.childDialog.model = model.modelForRow(currentIndex); 159 | itemList.childDialog.visualParent = listView.currentItem; 160 | } else { 161 | itemList.childDialog.delayedDestroy(); 162 | } 163 | 164 | return; 165 | } 166 | 167 | if (currentItem == null || !currentItem.hasChildren || !plasmoid.expanded) { 168 | dialogSpawnTimer.stop(); 169 | 170 | return; 171 | } 172 | 173 | if (showChildDialogs) { 174 | dialogSpawnTimer.focusOnSpawn = false; 175 | dialogSpawnTimer.restart(); 176 | } 177 | } else if (itemList.childDialog != null) { 178 | itemList.childDialog.delayedDestroy(); 179 | itemList.childDialog = null; 180 | } 181 | } 182 | 183 | onCurrentItemChanged: { 184 | if (currentItem) { 185 | currentItem.menu.closed.connect(resetIndexTimer.restart); 186 | } 187 | } 188 | 189 | Keys.onPressed: event => { 190 | if (event.key === Qt.Key_Up) { 191 | event.accepted = true; 192 | 193 | if (!keyNavigationWraps && currentIndex == 0) { 194 | itemList.keyNavigationAtListEnd(); 195 | 196 | return; 197 | } 198 | 199 | showChildDialogs = false; 200 | decrementCurrentIndex(); 201 | 202 | if (currentItem.isSeparator) { 203 | decrementCurrentIndex(); 204 | } 205 | 206 | showChildDialogs = true; 207 | } else if (event.key === Qt.Key_Down) { 208 | event.accepted = true; 209 | 210 | if (!keyNavigationWraps && currentIndex == count - 1) { 211 | itemList.keyNavigationAtListEnd(); 212 | 213 | return; 214 | } 215 | 216 | showChildDialogs = false; 217 | incrementCurrentIndex(); 218 | 219 | if (currentItem.isSeparator) { 220 | incrementCurrentIndex(); 221 | } 222 | 223 | showChildDialogs = true; 224 | } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && itemList.childDialog != null) { 225 | windowSystem.forceActive(itemList.childDialog.mainItem); 226 | itemList.childDialog.mainItem.focus = true; 227 | itemList.childDialog.mainItem.currentIndex = 0; 228 | } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && itemList.childDialog == null 229 | && currentItem != null && currentItem.hasChildren) { 230 | dialogSpawnTimer.focusOnSpawn = true; 231 | dialogSpawnTimer.restart(); 232 | } else if (event.key === Qt.Key_Left && dialog != null) { 233 | dialog.destroy(); 234 | } else if (event.key === Qt.Key_Escape) { 235 | plasmoid.expanded = false; 236 | } else if (event.key === Qt.Key_Tab) { 237 | //do nothing, and skip appending text 238 | } else if (event.text !== "") { 239 | if (/[\x00-\x1F\x7F]/.test(event.text)) { 240 | // We still want to focus it 241 | appendSearchText(""); 242 | } else { 243 | appendSearchText(event.text); 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | Component.onCompleted: { 252 | windowSystem.monitorWindowFocus(itemList); 253 | 254 | if (dialog == null) { 255 | appendSearchText.connect(root.appendSearchText); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /package/contents/ui/MiniButton.qml: -------------------------------------------------------------------------------- 1 | 2 | import QtQuick 2.15 3 | import QtQuick.Controls 2.15 4 | import org.kde.plasma.core 2.0 as PlasmaCore 5 | 6 | 7 | Rectangle{ 8 | id: root 9 | 10 | property alias icon: icon.source 11 | property alias tooltip: tooltip.text 12 | 13 | signal clicked() 14 | 15 | width: PlasmaCore.Units.iconSizes.large 16 | height: width 17 | radius: width*0.5 18 | color: rootMouseArea.containsMouse ? theme.highlightColor : 'transparent' 19 | 20 | PlasmaCore.IconItem { 21 | id: icon 22 | anchors.centerIn: parent 23 | width: PlasmaCore.Units.iconSizes.smallMedium 24 | opacity: 0.5 25 | } 26 | 27 | ToolTip { 28 | id: tooltip 29 | parent: root 30 | visible: rootMouseArea.containsMouse 31 | 32 | } 33 | 34 | MouseArea { 35 | id: rootMouseArea 36 | anchors.fill: parent 37 | hoverEnabled: true 38 | onClicked: root.clicked() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package/contents/ui/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 separator = { "type": "separator" }; 20 | actionList.push(separator); 21 | // actionList = actions.concat(actionList); // this crashes Qt O.o 22 | actionList.push.apply(actionList, actions); 23 | } else { 24 | actionList = actions; 25 | } 26 | } 27 | 28 | actionMenu.actionList = actionList; 29 | } 30 | 31 | function createFavoriteActions(i18n, favoriteModel, favoriteId) { 32 | if (favoriteModel === null || !favoriteModel.enabled || favoriteId == null) { 33 | return null; 34 | } 35 | 36 | 37 | if (favoriteModel.activities === undefined || 38 | favoriteModel.activities.runningActivities.length <= 1) { 39 | var action = {}; 40 | 41 | if (favoriteModel.isFavorite(favoriteId)) { 42 | action.text = i18n("Remove from Favorites"); 43 | action.icon = "bookmark-remove"; 44 | action.actionId = "_kicker_favorite_remove"; 45 | } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { 46 | action.text = i18n("Add to Favorites"); 47 | action.icon = "bookmark-new"; 48 | action.actionId = "_kicker_favorite_add"; 49 | } else { 50 | return null; 51 | } 52 | 53 | action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; 54 | 55 | return [action]; 56 | 57 | } else { 58 | var actions = []; 59 | 60 | var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); 61 | 62 | var activities = favoriteModel.activities.runningActivities; 63 | 64 | // Adding the item to link/unlink to all activities 65 | 66 | var linkedToAllActivities = 67 | !(linkedActivities.indexOf(":global") === -1); 68 | 69 | actions.push({ 70 | text : i18n("On All Activities"), 71 | checkable : true, 72 | 73 | actionId : linkedToAllActivities ? 74 | "_kicker_favorite_remove_from_activity" : 75 | "_kicker_favorite_set_to_activity", 76 | checked : linkedToAllActivities, 77 | 78 | actionArgument : { 79 | favoriteModel: favoriteModel, 80 | favoriteId: favoriteId, 81 | favoriteActivity: "" 82 | } 83 | }); 84 | 85 | 86 | // Adding items for each activity separately 87 | 88 | var addActivityItem = function(activityId, activityName) { 89 | var linkedToThisActivity = 90 | !(linkedActivities.indexOf(activityId) === -1); 91 | 92 | actions.push({ 93 | text : activityName, 94 | checkable : true, 95 | checked : linkedToThisActivity && !linkedToAllActivities, 96 | 97 | actionId : 98 | // If we are on all activities, and the user clicks just one 99 | // specific activity, unlink from everything else 100 | linkedToAllActivities ? "_kicker_favorite_set_to_activity" : 101 | 102 | // If we are linked to the current activity, just unlink from 103 | // that single one 104 | linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : 105 | 106 | // Otherwise, link to this activity, but do not unlink from 107 | // other ones 108 | "_kicker_favorite_add_to_activity", 109 | 110 | actionArgument : { 111 | favoriteModel : favoriteModel, 112 | favoriteId : favoriteId, 113 | favoriteActivity : activityId 114 | } 115 | }); 116 | }; 117 | 118 | // Adding the item to link/unlink to the current activity 119 | 120 | addActivityItem(favoriteModel.activities.currentActivity, i18n("On the Current Activity")); 121 | 122 | actions.push({ 123 | type: "separator", 124 | actionId: "_kicker_favorite_separator" 125 | }); 126 | 127 | // Adding the items for each activity 128 | 129 | activities.forEach(function(activityId) { 130 | addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); 131 | }); 132 | 133 | return [{ 134 | text : i18n("Show in Favorites"), 135 | icon : "favorite", 136 | subActions : actions 137 | }]; 138 | } 139 | } 140 | 141 | function triggerAction(model, index, actionId, actionArgument) { 142 | function startsWith(txt, needle) { 143 | return txt.substr(0, needle.length) === needle; 144 | } 145 | 146 | if (startsWith(actionId, "_kicker_favorite_")) { 147 | handleFavoriteAction(actionId, actionArgument); 148 | return; 149 | } 150 | 151 | var closeRequested = model.trigger(index, actionId, actionArgument); 152 | 153 | if (closeRequested) { 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | 160 | function handleFavoriteAction(actionId, actionArgument) { 161 | var favoriteId = actionArgument.favoriteId; 162 | var favoriteModel = actionArgument.favoriteModel; 163 | 164 | console.log(actionId); 165 | 166 | if (favoriteModel === null || favoriteId == null) { 167 | return null; 168 | } 169 | 170 | if (actionId == "_kicker_favorite_remove") { 171 | console.log("Removing from all activities"); 172 | favoriteModel.removeFavorite(favoriteId); 173 | } else if (actionId == "_kicker_favorite_add") { 174 | console.log("Adding to global activity"); 175 | favoriteModel.addFavorite(favoriteId); 176 | } else if (actionId == "_kicker_favorite_remove_from_activity") { 177 | console.log("Removing from a specific activity"); 178 | favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); 179 | 180 | } else if (actionId == "_kicker_favorite_add_to_activity") { 181 | console.log("Adding to another activity"); 182 | favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); 183 | 184 | } else if (actionId == "_kicker_favorite_set_to_activity") { 185 | console.log("Removing the item from the favourites, and re-adding it just to be on a specific activity"); 186 | favoriteModel.setFavoriteOn(favoriteId, actionArgument.favoriteActivity); 187 | 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /package/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014-2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Layouts 1.15 9 | 10 | import org.kde.plasma.components 2.0 as PlasmaComponents 11 | import org.kde.plasma.core 2.0 as PlasmaCore 12 | import org.kde.plasma.plasmoid 2.0 13 | 14 | import org.kde.plasma.private.kicker 0.1 as Kicker 15 | 16 | Item { 17 | id: kicker 18 | 19 | anchors.fill: parent 20 | 21 | signal reset 22 | 23 | property bool isDash: true//plasmoid.pluginName === "org.kde.plasma.kickerdash" 24 | 25 | Plasmoid.switchWidth: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumWidth 26 | Plasmoid.switchHeight: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumHeight 27 | 28 | // this is a bit of a hack to prevent Plasma from spawning a dialog on its own when we're Dash 29 | Plasmoid.preferredRepresentation: isDash ? Plasmoid.fullRepresentation : null 30 | 31 | Plasmoid.compactRepresentation: null 32 | Plasmoid.fullRepresentation: compactRepresentation 33 | 34 | property Component itemListDialogComponent: Qt.createComponent(Qt.resolvedUrl("./ItemListDialog.qml")) 35 | property Item dragSource: null 36 | 37 | property QtObject globalFavorites: rootModel.favoritesModel 38 | property QtObject systemFavorites: rootModel.systemFavoritesModel 39 | 40 | Plasmoid.icon: plasmoid.configuration.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon 41 | 42 | onSystemFavoritesChanged: { 43 | systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; 44 | } 45 | 46 | function action_menuedit() { 47 | processRunner.runMenuEditor(); 48 | } 49 | 50 | function updateSvgMetrics() { 51 | lineSvg.horLineHeight = lineSvg.elementSize("horizontal-line").height; 52 | lineSvg.vertLineWidth = lineSvg.elementSize("vertical-line").width; 53 | } 54 | 55 | Component { 56 | id: compactRepresentation 57 | CompactRepresentation {} 58 | } 59 | 60 | 61 | Kicker.RootModel { 62 | id: rootModel 63 | 64 | autoPopulate: false 65 | 66 | appNameFormat: plasmoid.configuration.appNameFormat 67 | flat: true 68 | sorted: true 69 | showSeparators: false 70 | appletInterface: plasmoid 71 | 72 | showAllApps: true 73 | showAllAppsCategorized: false 74 | showTopLevelItems: true 75 | showRecentApps: false 76 | showRecentDocs: false 77 | showRecentContacts: false 78 | recentOrdering: 0 79 | 80 | Component.onCompleted: { 81 | favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id) 82 | 83 | if (!plasmoid.configuration.favoritesPortedToKAstats) { 84 | if (favoritesModel.count < 1) { 85 | favoritesModel.portOldFavorites(plasmoid.configuration.favoriteApps); 86 | } 87 | plasmoid.configuration.favoritesPortedToKAstats = true; 88 | } 89 | } 90 | } 91 | 92 | Connections { 93 | target: globalFavorites 94 | 95 | function onFavoritesChanged() { 96 | plasmoid.configuration.favoriteApps = target.favorites; 97 | } 98 | } 99 | 100 | Connections { 101 | target: systemFavorites 102 | 103 | function onFavoritesChanged() { 104 | plasmoid.configuration.favoriteSystemActions = target.favorites; 105 | } 106 | } 107 | 108 | Connections { 109 | target: plasmoid.configuration 110 | 111 | function onFavoriteAppsChanged() { 112 | globalFavorites.favorites = plasmoid.configuration.favoriteApps; 113 | } 114 | 115 | function onFavoriteSystemActionsChanged() { 116 | systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; 117 | } 118 | 119 | function onHiddenApplicationsChanged(){ 120 | rootModel.refresh(); // Force refresh on hidden 121 | } 122 | } 123 | 124 | Kicker.RunnerModel { 125 | id: runnerModel 126 | appletInterface: plasmoid 127 | favoritesModel: rootModel.favoritesModel 128 | mergeResults: true 129 | } 130 | 131 | Kicker.DragHelper { 132 | id: dragHelper 133 | 134 | dragIconSize: PlasmaCore.Units.iconSizes.medium 135 | } 136 | 137 | Kicker.ProcessRunner { 138 | id: processRunner 139 | } 140 | 141 | Kicker.WindowSystem { 142 | id: windowSystem 143 | } 144 | 145 | PlasmaCore.FrameSvgItem { 146 | id: highlightItemSvg 147 | 148 | visible: false 149 | 150 | imagePath: "widgets/viewitem" 151 | prefix: "hover" 152 | } 153 | 154 | PlasmaCore.FrameSvgItem { 155 | id: listItemSvg 156 | 157 | visible: false 158 | 159 | imagePath: "widgets/listitem" 160 | prefix: "normal" 161 | } 162 | 163 | PlasmaCore.Svg { 164 | id: arrows 165 | 166 | imagePath: "widgets/arrows" 167 | size: "16x16" 168 | } 169 | 170 | PlasmaCore.Svg { 171 | id: lineSvg 172 | imagePath: "widgets/line" 173 | 174 | property int horLineHeight 175 | property int vertLineWidth 176 | } 177 | 178 | PlasmaComponents.Label { 179 | id: toolTipDelegate 180 | 181 | width: contentWidth 182 | height: undefined 183 | 184 | property Item toolTip 185 | 186 | text: toolTip ? toolTip.text : "" 187 | } 188 | 189 | Timer { 190 | id: justOpenedTimer 191 | 192 | repeat: false 193 | interval: 600 194 | } 195 | 196 | Connections { 197 | target: plasmoid 198 | 199 | function onExpandedChanged(expanded) { 200 | if (expanded) { 201 | windowSystem.monitorWindowVisibility(plasmoid.fullRepresentationItem); 202 | justOpenedTimer.start(); 203 | } else { 204 | kicker.reset(); 205 | } 206 | } 207 | } 208 | 209 | PlasmaCore.DataSource { 210 | id: pmEngine 211 | engine: "powermanagement" 212 | connectedSources: ["PowerDevil", "Sleep States"] 213 | function performOperation(what) { 214 | var service = serviceForSource("PowerDevil") 215 | var operation = service.operationDescription(what) 216 | service.startOperationCall(operation) 217 | } 218 | } 219 | 220 | 221 | function resetDragSource() { 222 | dragSource = null; 223 | } 224 | 225 | function enableHideOnWindowDeactivate() { 226 | plasmoid.hideOnWindowDeactivate = true; 227 | } 228 | 229 | Component.onCompleted: { 230 | if (plasmoid.hasOwnProperty("activationTogglesExpanded")) { 231 | plasmoid.activationTogglesExpanded = !kicker.isDash 232 | } 233 | 234 | windowSystem.focusIn.connect(enableHideOnWindowDeactivate); 235 | plasmoid.hideOnWindowDeactivate = true; 236 | 237 | if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { 238 | plasmoid.setAction("menuedit", i18n("Edit Applications…"), "kmenuedit"); 239 | } 240 | 241 | updateSvgMetrics(); 242 | PlasmaCore.Theme.themeChanged.connect(updateSvgMetrics); 243 | 244 | rootModel.refreshed.connect(reset); 245 | dragHelper.dropped.connect(resetDragSource); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /package/metadata.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Launchpad Plasma 3 | Comment=A configurable grid launcher menu 4 | 5 | Type=Service 6 | Icon=start-here-kde 7 | X-KDE-ServiceTypes=Plasma/Applet 8 | 9 | X-Plasma-API=declarativeappletscript 10 | X-Plasma-MainScript=ui/main.qml 11 | X-Plasma-Provides=org.kde.plasma.launchermenu 12 | 13 | X-KDE-PluginInfo-Author=adhe 14 | X-KDE-PluginInfo-Email=adhemarks@gmail.com 15 | X-KDE-PluginInfo-Name=launchpadPlasma 16 | X-KDE-PluginInfo-Version=2.0 17 | X-KDE-PluginInfo-Category=Application Launchers 18 | X-KDE-PluginInfo-Depends= 19 | X-KDE-PluginInfo-License=GPL v2+ 20 | X-KDE-PluginInfo-EnabledByDefault=true 21 | -------------------------------------------------------------------------------- /package/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Authors": [ 4 | { 5 | "Email": "adhemarks@gmail.com", 6 | "Name": "Adhe" 7 | } 8 | ], 9 | "Category": "Application Launchers", 10 | "Description": "A configurable grid launcher menu", 11 | "Description[x-test]": "xxA configurable grid launcher menuxx", 12 | "EnabledByDefault": true, 13 | "Icon": "start-here-kde", 14 | "Id": "launchpadPlasma", 15 | "License": "GPL-2.0+", 16 | "Name": "Launchpad Plasma", 17 | "Name[x-test]": "xxLaunchpad Plasmaxx", 18 | "ServiceTypes": [ 19 | "Plasma/Applet" 20 | ], 21 | "Version": "2.0", 22 | "Website": "" 23 | }, 24 | "X-Plasma-API": "declarativeappletscript", 25 | "X-Plasma-MainScript": "ui/main.qml", 26 | "X-Plasma-Provides": [ 27 | "org.kde.plasma.launchermenu" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packageDark/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.0 21 | 22 | import org.kde.plasma.configuration 2.0 23 | 24 | ConfigModel { 25 | ConfigCategory { 26 | name: i18n("General") 27 | icon: "kde" 28 | source: "ConfigGeneral.qml" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packageDark/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | start-here-kde 12 | 13 | 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 64 22 | 23 | 24 | 56 25 | 26 | 27 | 76 28 | 29 | 30 | 32 31 | 32 | 33 | 50 34 | 35 | 36 | false 37 | 38 | 39 | true 40 | 41 | 42 | false 43 | 44 | 45 | 7 46 | 47 | 48 | 5 49 | 50 | 51 | true 52 | 53 | 54 | 55 | true 56 | 57 | 58 | 59 | 65 60 | 61 | 62 | 63 | 64 | false 65 | 66 | 67 | 68 | background.jpg 69 | 70 | 71 | 64 72 | 73 | 74 | 75 | true 76 | 77 | 78 | 48 79 | 80 | 81 | 600 82 | 83 | 84 | 0 85 | 86 | 87 | 88 | 89 | 90 | 4 91 | 92 | 93 | 2 94 | 95 | 96 | logout,reboot,shutdown 97 | 98 | 99 | 100 | 101 | 102 | false 103 | 104 | 105 | 106 | Medium 107 | 108 | 109 | 110 | bookmarks,baloosearch 111 | 112 | 113 | 114 | false 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /packageDark/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.components 2.0 as PlasmaComponents 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 !== PlasmaComponents.DialogStatus.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 | PlasmaComponents.ContextMenu { 82 | visualParent: root.visualParent 83 | } 84 | } 85 | 86 | Component { 87 | id: contextSubmenuItemComponent 88 | 89 | PlasmaComponents.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 variant submenu : submenu_ 98 | 99 | PlasmaComponents.ContextMenu { 100 | id: submenu_ 101 | visualParent: submenuItem.action 102 | } 103 | } 104 | } 105 | 106 | Component { 107 | id: contextMenuItemComponent 108 | 109 | PlasmaComponents.MenuItem { 110 | property variant actionItem 111 | 112 | text : actionItem.text ? actionItem.text : "" 113 | enabled : actionItem.type !== "title" && ("enabled" in actionItem ? actionItem.enabled : true) 114 | separator : actionItem.type === "separator" 115 | section : actionItem.type === "title" 116 | icon : actionItem.icon ? actionItem.icon : null 117 | checkable : actionItem.checkable ? actionItem.checkable : false 118 | checked : actionItem.checked ? actionItem.checked : false 119 | 120 | onClicked: { 121 | root.actionClicked(actionItem.actionId, actionItem.actionArgument); 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packageDark/contents/ui/CompactRepresentation.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Layouts 1.15 9 | 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import org.kde.plasma.private.kicker 0.1 as Kicker 13 | 14 | Item { 15 | id: root 16 | 17 | readonly property bool inPanel: (plasmoid.location === PlasmaCore.Types.TopEdge 18 | || plasmoid.location === PlasmaCore.Types.RightEdge 19 | || plasmoid.location === PlasmaCore.Types.BottomEdge 20 | || plasmoid.location === PlasmaCore.Types.LeftEdge) 21 | readonly property bool vertical: (plasmoid.formFactor === PlasmaCore.Types.Vertical) 22 | readonly property bool useCustomButtonImage: (plasmoid.configuration.useCustomButtonImage 23 | && plasmoid.configuration.customButtonImage.length !== 0) 24 | 25 | readonly property Component dashWindowComponent: kicker.isDash ? Qt.createComponent(Qt.resolvedUrl("./DashboardRepresentation.qml"), root) : null 26 | readonly property Kicker.DashboardWindow dashWindow: dashWindowComponent && dashWindowComponent.status === Component.Ready 27 | ? dashWindowComponent.createObject(root, { visualParent: root }) : null 28 | 29 | onWidthChanged: updateSizeHints() 30 | onHeightChanged: updateSizeHints() 31 | 32 | function updateSizeHints() { 33 | if (useCustomButtonImage) { 34 | if (vertical) { 35 | const scaledHeight = Math.floor(parent.width * (buttonIcon.implicitHeight / buttonIcon.implicitWidth)); 36 | root.Layout.minimumHeight = scaledHeight; 37 | root.Layout.maximumHeight = scaledHeight; 38 | root.Layout.minimumWidth = PlasmaCore.Units.iconSizes.small; 39 | root.Layout.maximumWidth = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 40 | } else { 41 | const scaledWidth = Math.floor(parent.height * (buttonIcon.implicitWidth / buttonIcon.implicitHeight)); 42 | root.Layout.minimumWidth = scaledWidth; 43 | root.Layout.maximumWidth = scaledWidth; 44 | root.Layout.minimumHeight = PlasmaCore.Units.iconSizes.small; 45 | root.Layout.maximumHeight = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 46 | } 47 | } else { 48 | root.Layout.minimumWidth = PlasmaCore.Units.iconSizes.small; 49 | root.Layout.maximumWidth = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 50 | root.Layout.minimumHeight = PlasmaCore.Units.iconSizes.small; 51 | root.Layout.maximumHeight = inPanel ? PlasmaCore.Units.iconSizeHints.panel : -1; 52 | } 53 | } 54 | 55 | Connections { 56 | target: PlasmaCore.Units.iconSizeHints 57 | 58 | function onPanelChanged() { 59 | root.updateSizeHints() 60 | } 61 | } 62 | 63 | PlasmaCore.IconItem { 64 | id: buttonIcon 65 | 66 | readonly property double aspectRatio: root.vertical 67 | ? implicitHeight / implicitWidth 68 | : implicitWidth / implicitHeight 69 | 70 | anchors.fill: parent 71 | 72 | active: mouseArea.containsMouse && !justOpenedTimer.running 73 | smooth: true 74 | source: root.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon 75 | 76 | // A custom icon could also be rectangular. However, if a square, custom, icon is given, assume it 77 | // to be an icon and round it to the nearest icon size again to avoid scaling artifacts. 78 | roundToIconSize: !root.useCustomButtonImage || aspectRatio === 1 79 | 80 | onSourceChanged: root.updateSizeHints() 81 | } 82 | 83 | MouseArea 84 | { 85 | id: mouseArea 86 | property bool wasExpanded: false; 87 | 88 | anchors.fill: parent 89 | 90 | hoverEnabled: !root.dashWindow || !root.dashWindow.visible 91 | 92 | onPressed: { 93 | if (!kicker.isDash) { 94 | wasExpanded = plasmoid.expanded 95 | } 96 | } 97 | 98 | onClicked: { 99 | if (kicker.isDash) { 100 | root.dashWindow.toggle(); 101 | justOpenedTimer.start(); 102 | } else { 103 | plasmoid.expanded = !wasExpanded; 104 | } 105 | } 106 | } 107 | 108 | Connections { 109 | target: plasmoid 110 | enabled: kicker.isDash && root.dashWindow !== null 111 | 112 | function onActivated() { 113 | root.dashWindow.toggle(); 114 | justOpenedTimer.start(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ConfigGeneral.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Controls 2.15 9 | 10 | import org.kde.draganddrop 2.0 as DragDrop 11 | import org.kde.kirigami 2.5 as Kirigami 12 | import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons 13 | import org.kde.plasma.core 2.0 as PlasmaCore 14 | import QtQuick.Layouts 1.0 15 | import org.kde.plasma.private.kicker 0.1 as Kicker 16 | 17 | Kirigami.FormLayout { 18 | id: configGeneral 19 | 20 | anchors.left: parent.left 21 | anchors.right: parent.right 22 | 23 | property bool isDash: (plasmoid.pluginName === "org.kde.plasma.kickerdash") 24 | 25 | property string cfg_icon: plasmoid.configuration.icon 26 | property bool cfg_useCustomButtonImage: plasmoid.configuration.useCustomButtonImage 27 | property string cfg_customButtonImage: plasmoid.configuration.customButtonImage 28 | 29 | property alias cfg_backgroundOpacity: backgroundOpacity.value 30 | property alias cfg_sizeApps: sizeApps.currentIndex 31 | property alias cfg_sizeAppsFav: sizeAppsFav.currentIndex 32 | 33 | Button { 34 | id: iconButton 35 | 36 | Kirigami.FormData.label: i18n("Icon:") 37 | 38 | implicitWidth: previewFrame.width + PlasmaCore.Units.smallSpacing * 2 39 | implicitHeight: previewFrame.height + PlasmaCore.Units.smallSpacing * 2 40 | 41 | // Just to provide some visual feedback when dragging; 42 | // cannot have checked without checkable enabled 43 | checkable: true 44 | checked: dropArea.containsAcceptableDrag 45 | 46 | onPressed: iconMenu.opened ? iconMenu.close() : iconMenu.open() 47 | 48 | DragDrop.DropArea { 49 | id: dropArea 50 | 51 | property bool containsAcceptableDrag: false 52 | 53 | anchors.fill: parent 54 | 55 | onDragEnter: { 56 | // Cannot use string operations (e.g. indexOf()) on "url" basic type. 57 | var urlString = event.mimeData.url.toString(); 58 | 59 | // This list is also hardcoded in KIconDialog. 60 | var extensions = [".png", ".xpm", ".svg", ".svgz"]; 61 | containsAcceptableDrag = urlString.indexOf("file:///") === 0 && extensions.some(function (extension) { 62 | return urlString.indexOf(extension) === urlString.length - extension.length; // "endsWith" 63 | }); 64 | 65 | if (!containsAcceptableDrag) { 66 | event.ignore(); 67 | } 68 | } 69 | onDragLeave: containsAcceptableDrag = false 70 | 71 | onDrop: { 72 | if (containsAcceptableDrag) { 73 | // Strip file:// prefix, we already verified in onDragEnter that we have only local URLs. 74 | iconDialog.setCustomButtonImage(event.mimeData.url.toString().substr("file://".length)); 75 | } 76 | containsAcceptableDrag = false; 77 | } 78 | } 79 | 80 | KQuickAddons.IconDialog { 81 | id: iconDialog 82 | 83 | function setCustomButtonImage(image) { 84 | configGeneral.cfg_customButtonImage = image || configGeneral.cfg_icon || "start-here-kde" 85 | configGeneral.cfg_useCustomButtonImage = true; 86 | } 87 | 88 | onIconNameChanged: setCustomButtonImage(iconName); 89 | } 90 | 91 | PlasmaCore.FrameSvgItem { 92 | id: previewFrame 93 | anchors.centerIn: parent 94 | imagePath: plasmoid.location === PlasmaCore.Types.Vertical || plasmoid.location === PlasmaCore.Types.Horizontal 95 | ? "widgets/panel-background" : "widgets/background" 96 | width: PlasmaCore.Units.iconSizes.large + fixedMargins.left + fixedMargins.right 97 | height: PlasmaCore.Units.iconSizes.large + fixedMargins.top + fixedMargins.bottom 98 | 99 | PlasmaCore.IconItem { 100 | anchors.centerIn: parent 101 | width: PlasmaCore.Units.iconSizes.large 102 | height: width 103 | source: configGeneral.cfg_useCustomButtonImage ? configGeneral.cfg_customButtonImage : configGeneral.cfg_icon 104 | } 105 | } 106 | 107 | Menu { 108 | id: iconMenu 109 | 110 | // Appear below the button 111 | y: +parent.height 112 | 113 | onClosed: iconButton.checked = false; 114 | 115 | MenuItem { 116 | text: i18nc("@item:inmenu Open icon chooser dialog", "Choose…") 117 | icon.name: "document-open-folder" 118 | onClicked: iconDialog.open() 119 | } 120 | MenuItem { 121 | text: i18nc("@item:inmenu Reset icon to default", "Clear Icon") 122 | icon.name: "edit-clear" 123 | onClicked: { 124 | configGeneral.cfg_icon = "start-here-kde" 125 | configGeneral.cfg_useCustomButtonImage = false 126 | } 127 | } 128 | } 129 | } 130 | 131 | 132 | Item { 133 | Kirigami.FormData.isSection: true 134 | } 135 | 136 | RowLayout{ 137 | Layout.fillWidth: true 138 | Kirigami.FormData.label: i18n("Background opacity:") 139 | Slider{ 140 | id: backgroundOpacity 141 | from: 0 142 | to: 100 143 | stepSize: 5 144 | implicitWidth: 100 145 | } 146 | Label { 147 | text: backgroundOpacity.value + "% " 148 | } 149 | } 150 | 151 | ComboBox { 152 | id: sizeApps 153 | Kirigami.FormData.label: i18n("Size icons:") 154 | model: [i18n("SmallMedium"), i18n("Medium"), i18n("Large"), i18n("Huge"), i18n("X Huge"), i18n("Enormous") ] 155 | } 156 | 157 | ComboBox { 158 | id: sizeAppsFav 159 | Kirigami.FormData.label: i18n("Size icons favorites:") 160 | model: [i18n("SmallMedium"), i18n("Medium"), i18n("Large"), i18n("Huge"), i18n("X Huge"), i18n("Enormous") ] 161 | } 162 | 163 | RowLayout{ 164 | Button { 165 | text: i18n("Unhide all applications") 166 | onClicked: { 167 | plasmoid.configuration.hiddenApplications = []; 168 | unhideAllAppsPopup.text = i18n("Unhidden!"); 169 | } 170 | } 171 | Label { 172 | id: unhideAllAppsPopup 173 | } 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ItemGridDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.components 2.0 as PlasmaComponents 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import "code/tools.js" as Tools 13 | 14 | Item { 15 | id: item 16 | 17 | width: GridView.view.cellWidth 18 | height: width 19 | 20 | enabled: !model.disabled 21 | 22 | property bool showLabel: true 23 | 24 | property int itemIndex: model.index 25 | property string favoriteId: model.favoriteId !== undefined ? model.favoriteId : "" 26 | property url url: model.url !== undefined ? model.url : "" 27 | property variant icon: model.decoration !== undefined ? model.decoration : "" 28 | property var m: model 29 | property bool hasActionList: ((model.favoriteId !== null) 30 | || (("hasActionList" in model) && (model.hasActionList === true))) 31 | 32 | Accessible.role: Accessible.MenuItem 33 | Accessible.name: model.display 34 | 35 | function openActionMenu(x, y) { 36 | var actionList = hasActionList ? model.actionList : []; 37 | Tools.fillActionMenu(i18n, actionMenu, actionList, GridView.view.model.favoritesModel, model.favoriteId); 38 | actionMenu.visualParent = item; 39 | actionMenu.open(x, y); 40 | } 41 | 42 | function actionTriggered(actionId, actionArgument) { 43 | var close = (Tools.triggerAction(GridView.view.model, model.index, actionId, actionArgument) === true); 44 | 45 | if (close) { 46 | root.toggle(); 47 | } 48 | } 49 | 50 | PlasmaCore.IconItem { 51 | id: icon 52 | //y: item.showLabel ? (2 * highlightItemSvg.margins.top) : undefined 53 | //anchors.verticalCenter: item.showLabel ? undefined : parent.verticalCenter 54 | anchors.horizontalCenter: parent.horizontalCenter 55 | anchors.verticalCenter: parent.verticalCenter 56 | anchors.verticalCenterOffset: item.showLabel ? -(PlasmaCore.Units.gridUnit) : 0 57 | width: iconSize 58 | height: width 59 | colorGroup: PlasmaCore.Theme.ComplementaryColorGroup 60 | animated: false 61 | usesPlasmaTheme: item.GridView.view.usesPlasmaTheme 62 | source: model.decoration 63 | } 64 | 65 | PlasmaComponents.Label { 66 | id: label 67 | 68 | visible: item.showLabel 69 | 70 | anchors { 71 | top: icon.bottom 72 | topMargin: PlasmaCore.Units.smallSpacing 73 | left: parent.left 74 | leftMargin: highlightItemSvg.margins.left 75 | right: parent.right 76 | rightMargin: highlightItemSvg.margins.right 77 | } 78 | 79 | horizontalAlignment: Text.AlignHCenter 80 | 81 | maximumLineCount: 2 82 | elide: Text.ElideMiddle 83 | wrapMode: Text.Wrap 84 | color: root.textColor 85 | text: ("name" in model ? model.name : model.display) 86 | } 87 | 88 | PlasmaCore.ToolTipArea { 89 | id: toolTip 90 | 91 | property string text: model.display 92 | 93 | anchors.fill: parent 94 | active: root.visible && label.truncated 95 | mainItem: toolTipDelegate 96 | 97 | onContainsMouseChanged: item.GridView.view.itemContainsMouseChanged(containsMouse) 98 | } 99 | 100 | Keys.onPressed: event => { 101 | if (event.key === Qt.Key_Menu && hasActionList) { 102 | event.accepted = true; 103 | openActionMenu(item); 104 | } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { 105 | event.accepted = true; 106 | 107 | if ("trigger" in GridView.view.model) { 108 | GridView.view.model.trigger(index, "", null); 109 | root.toggle(); 110 | } 111 | 112 | itemGrid.itemActivated(index, "", null); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ItemGridView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.draganddrop 2.0 10 | import org.kde.kquickcontrolsaddons 2.0 11 | import org.kde.plasma.components 2.0 as PlasmaComponents 12 | import org.kde.plasma.core 2.0 as PlasmaCore 13 | import org.kde.plasma.extras 2.0 as PlasmaExtras 14 | import org.kde.plasma.components 3.0 as PlasmaComponents3 15 | 16 | FocusScope { 17 | id: itemGrid 18 | 19 | signal keyNavLeft 20 | signal keyNavRight 21 | signal keyNavUp 22 | signal keyNavDown 23 | 24 | signal itemActivated(int index, string actionId, string argument) 25 | signal lostFocus() 26 | 27 | property bool dragEnabled: true 28 | property bool dropEnabled: false 29 | property bool showLabels: true 30 | property alias usesPlasmaTheme: gridView.usesPlasmaTheme 31 | property bool forceFocusIndex0: false 32 | 33 | property alias currentIndex: gridView.currentIndex 34 | property alias currentItem: gridView.currentItem 35 | property alias contentItem: gridView.contentItem 36 | property alias count: gridView.count 37 | property alias model: gridView.model 38 | 39 | property alias cellWidth: gridView.cellWidth 40 | property alias cellHeight: gridView.cellHeight 41 | property alias iconSize: gridView.iconSize 42 | 43 | 44 | 45 | onDropEnabledChanged: { 46 | if (!dropEnabled && "dropPlaceHolderIndex" in model) { 47 | model.dropPlaceHolderIndex = -1; 48 | } 49 | } 50 | 51 | onFocusChanged: { 52 | if (!focus && !root.keyEventProxy.activeFocus) { 53 | currentIndex = -1; 54 | } 55 | if(!focus) 56 | lostFocus() 57 | } 58 | 59 | function currentRow() { 60 | if (currentIndex === -1) { 61 | return -1; 62 | } 63 | 64 | return Math.floor(currentIndex / Math.floor(width / itemGrid.cellWidth)); 65 | } 66 | 67 | function currentCol() { 68 | if (currentIndex === -1) { 69 | return -1; 70 | } 71 | 72 | return currentIndex - (currentRow() * Math.floor(width / itemGrid.cellWidth)); 73 | } 74 | 75 | function lastRow() { 76 | var columns = Math.floor(width / itemGrid.cellWidth); 77 | return Math.ceil(count / columns) - 1; 78 | } 79 | 80 | function tryActivate(row, col) { 81 | if (count) { 82 | var columns = Math.floor(width / itemGrid.cellWidth); 83 | var rows = Math.ceil(count / columns); 84 | row = Math.min(row, rows - 1); 85 | col = Math.min(col, columns - 1); 86 | currentIndex = Math.min(row ? ((Math.max(1, row) * columns) + col) 87 | : col, 88 | count - 1); 89 | focus = true; 90 | } 91 | } 92 | 93 | function forceLayout() { 94 | gridView.forceLayout(); 95 | } 96 | 97 | ActionMenu { 98 | id: actionMenu 99 | 100 | onActionClicked: { 101 | visualParent.actionTriggered(actionId, actionArgument); 102 | } 103 | } 104 | 105 | DropArea { 106 | id: dropArea 107 | 108 | //anchors.fill: parent 109 | width: itemGrid.width - PlasmaCore.Units.largeSpacing // TODO cover scrollbar 110 | height: itemGrid.height 111 | 112 | 113 | onDragMove: { 114 | if (!itemGrid.dropEnabled || gridView.animating || !kicker.dragSource) { 115 | return; 116 | } 117 | 118 | var x = Math.max(0, event.x - (width % itemGrid.cellWidth)); 119 | var cPos = mapToItem(gridView.contentItem, x, event.y); 120 | var item = gridView.itemAt(cPos.x, cPos.y); 121 | 122 | if (item) { 123 | if (kicker.dragSource.parent === gridView.contentItem) { 124 | if (item !== kicker.dragSource) { 125 | item.GridView.view.model.moveRow(dragSource.itemIndex, item.itemIndex); 126 | } 127 | } else if (kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model 128 | && !itemGrid.model.isFavorite(kicker.dragSource.favoriteId)) { 129 | var hasPlaceholder = (itemGrid.model.dropPlaceholderIndex !== -1); 130 | 131 | itemGrid.model.dropPlaceholderIndex = item.itemIndex; 132 | 133 | if (!hasPlaceholder) { 134 | gridView.currentIndex = (item.itemIndex - 1); 135 | } 136 | } 137 | } else if (kicker.dragSource.parent !== gridView.contentItem 138 | && kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model 139 | && !itemGrid.model.isFavorite(kicker.dragSource.favoriteId)) { 140 | var hasPlaceholder = (itemGrid.model.dropPlaceholderIndex !== -1); 141 | 142 | itemGrid.model.dropPlaceholderIndex = hasPlaceholder ? itemGrid.model.count - 1 : itemGrid.model.count; 143 | 144 | if (!hasPlaceholder) { 145 | gridView.currentIndex = (itemGrid.model.count - 1); 146 | } 147 | } else { 148 | itemGrid.model.dropPlaceholderIndex = -1; 149 | gridView.currentIndex = -1; 150 | } 151 | } 152 | 153 | onDragLeave: { 154 | if ("dropPlaceholderIndex" in itemGrid.model) { 155 | itemGrid.model.dropPlaceholderIndex = -1; 156 | gridView.currentIndex = -1; 157 | } 158 | } 159 | 160 | onDrop: { 161 | if (kicker.dragSource && kicker.dragSource.parent !== gridView.contentItem && kicker.dragSource.GridView.view.model.favoritesModel === itemGrid.model) { 162 | itemGrid.model.addFavorite(kicker.dragSource.favoriteId, itemGrid.model.dropPlaceholderIndex); 163 | gridView.currentIndex = -1; 164 | } 165 | } 166 | 167 | Timer { 168 | id: resetAnimationDurationTimer 169 | 170 | interval: 120 171 | repeat: false 172 | 173 | onTriggered: { 174 | gridView.animationDuration = interval - 20; 175 | } 176 | } 177 | 178 | PlasmaComponents3.ScrollView { 179 | id: scrollArea 180 | 181 | width: itemGrid.width 182 | height: itemGrid.height 183 | 184 | focus: true 185 | PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff 186 | 187 | 188 | GridView { 189 | id: gridView 190 | 191 | anchors.fill: parent 192 | signal itemContainsMouseChanged(bool containsMouse) 193 | 194 | property bool usesPlasmaTheme: false 195 | property int iconSize: PlasmaCore.Units.iconSizes.huge 196 | property bool animating: false 197 | property int animationDuration: itemGrid.dropEnabled ? resetAnimationDurationTimer.interval : 0 198 | focus: true 199 | 200 | currentIndex: -1 201 | 202 | move: Transition { 203 | enabled: itemGrid.dropEnabled 204 | 205 | SequentialAnimation { 206 | PropertyAction { target: gridView; property: "animating"; value: true } 207 | 208 | NumberAnimation { 209 | duration: gridView.animationDuration 210 | properties: "x, y" 211 | easing.type: Easing.OutQuad 212 | } 213 | 214 | PropertyAction { target: gridView; property: "animating"; value: false } 215 | } 216 | } 217 | 218 | moveDisplaced: Transition { 219 | enabled: itemGrid.dropEnabled 220 | 221 | SequentialAnimation { 222 | PropertyAction { target: gridView; property: "animating"; value: true } 223 | 224 | NumberAnimation { 225 | duration: gridView.animationDuration 226 | properties: "x, y" 227 | easing.type: Easing.OutQuad 228 | } 229 | 230 | PropertyAction { target: gridView; property: "animating"; value: false } 231 | } 232 | } 233 | 234 | keyNavigationWraps: false 235 | boundsBehavior: Flickable.StopAtBounds 236 | 237 | delegate: ItemGridDelegate { 238 | showLabel: itemGrid.showLabels 239 | } 240 | 241 | highlight: PlasmaComponents.Highlight { 242 | 243 | } 244 | 245 | highlightFollowsCurrentItem: true 246 | highlightMoveDuration: 0 247 | 248 | onCurrentIndexChanged: { 249 | if (currentIndex !== -1) { 250 | hoverArea.hoverEnabled = false 251 | focus = true; 252 | } 253 | } 254 | 255 | onCountChanged: { 256 | if(itemGrid.forceFocusIndex0){ 257 | if(count > 0){ 258 | forceLayout() 259 | currentIndex = 0; 260 | itemGrid.focus = true 261 | }else{ 262 | currentIndex = -1; 263 | itemGrid.focus = false 264 | } 265 | } 266 | animationDuration = 0; 267 | //resetAnimationDurationTimer.start(); 268 | } 269 | 270 | onModelChanged: { 271 | currentIndex = -1; 272 | } 273 | 274 | Keys.onLeftPressed: { 275 | if (itemGrid.currentCol() !== 0) { 276 | event.accepted = true; 277 | moveCurrentIndexLeft(); 278 | } else { 279 | itemGrid.keyNavLeft(); 280 | } 281 | } 282 | 283 | Keys.onRightPressed: { 284 | var columns = Math.floor(width / cellWidth); 285 | 286 | if (itemGrid.currentCol() !== columns - 1 && currentIndex !== count -1) { 287 | event.accepted = true; 288 | moveCurrentIndexRight(); 289 | } else { 290 | itemGrid.keyNavRight(); 291 | } 292 | } 293 | 294 | Keys.onUpPressed: { 295 | if (itemGrid.currentRow() !== 0) { 296 | event.accepted = true; 297 | moveCurrentIndexUp(); 298 | positionViewAtIndex(currentIndex, GridView.Contain); 299 | } else { 300 | itemGrid.keyNavUp(); 301 | } 302 | } 303 | 304 | Keys.onDownPressed: { 305 | if(itemGrid.currentRow() === -1 && itemGrid.count > 0 ){ 306 | currentIndex = 0 307 | } 308 | else{ 309 | if (itemGrid.currentRow() < itemGrid.lastRow()) { 310 | // Fix moveCurrentIndexDown()'s lack of proper spatial nav down 311 | // into partial columns. 312 | event.accepted = true; 313 | var columns = Math.floor(width / cellWidth); 314 | var newIndex = currentIndex + columns; 315 | currentIndex = Math.min(newIndex, count - 1); 316 | positionViewAtIndex(currentIndex, GridView.Contain); 317 | } else { 318 | itemGrid.keyNavDown(); 319 | 320 | } 321 | } 322 | } 323 | 324 | onItemContainsMouseChanged: { 325 | if (!containsMouse) { 326 | if (!actionMenu.opened) { 327 | gridView.currentIndex = -1; 328 | } 329 | 330 | hoverArea.pressX = -1; 331 | hoverArea.pressY = -1; 332 | hoverArea.lastX = -1; 333 | hoverArea.lastY = -1; 334 | hoverArea.pressedItem = null; 335 | hoverArea.hoverEnabled = true; 336 | } 337 | } 338 | } 339 | } 340 | 341 | MouseArea { 342 | id: hoverArea 343 | 344 | anchors.fill: parent 345 | 346 | property int pressX: -1 347 | property int pressY: -1 348 | property int lastX: -1 349 | property int lastY: -1 350 | property Item pressedItem: null 351 | 352 | acceptedButtons: Qt.LeftButton | Qt.RightButton 353 | 354 | hoverEnabled: true 355 | 356 | function updatePositionProperties(x, y) { 357 | // Prevent hover event synthesis in QQuickWindow interfering 358 | // with keyboard navigation by ignoring repeated events with 359 | // identical coordinates. As the work done here would be re- 360 | // dundant in any case, these are safe to ignore. 361 | if (lastX === x && lastY === y) { 362 | return; 363 | } 364 | 365 | lastX = x; 366 | lastY = y; 367 | 368 | var cPos = mapToItem(gridView.contentItem, x, y); 369 | var item = gridView.itemAt(cPos.x, cPos.y); 370 | 371 | if (!item) { 372 | gridView.currentIndex = -1; 373 | pressedItem = null; 374 | } else { 375 | gridView.currentIndex = item.itemIndex; 376 | itemGrid.focus = (itemGrid.currentIndex !== -1) 377 | } 378 | 379 | return item; 380 | } 381 | 382 | onPressed: mouse => { 383 | mouse.accepted = true; 384 | 385 | updatePositionProperties(mouse.x, mouse.y); 386 | 387 | pressX = mouse.x; 388 | pressY = mouse.y; 389 | 390 | if (mouse.button === Qt.RightButton) { 391 | if (gridView.currentItem) { 392 | if (gridView.currentItem.hasActionList) { 393 | var mapped = mapToItem(gridView.currentItem, mouse.x, mouse.y); 394 | gridView.currentItem.openActionMenu(mapped.x, mapped.y); 395 | } 396 | }// else { 397 | // var mapped = mapToItem(rootItem, mouse.x, mouse.y); 398 | // contextMenu.open(mapped.x, mapped.y); 399 | //} 400 | } else { 401 | pressedItem = gridView.currentItem; 402 | } 403 | } 404 | 405 | onReleased: mouse => { 406 | mouse.accepted = true; 407 | updatePositionProperties(mouse.x, mouse.y); 408 | 409 | if (!dragHelper.dragging) { 410 | if (pressedItem) { 411 | if ("trigger" in gridView.model) { 412 | gridView.model.trigger(pressedItem.itemIndex, "", null); 413 | root.toggle(); 414 | } 415 | 416 | itemGrid.itemActivated(pressedItem.itemIndex, "", null); 417 | } else if (mouse.button === Qt.LeftButton) { 418 | root.toggle(); 419 | } 420 | } 421 | 422 | pressX = pressY = -1; 423 | pressedItem = null; 424 | } 425 | 426 | onPositionChanged: mouse => { 427 | var item = pressedItem? pressedItem : updatePositionProperties(mouse.x, mouse.y); 428 | 429 | if (gridView.currentIndex !== -1) { 430 | if (itemGrid.dragEnabled && pressX !== -1 && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { 431 | if ("pluginName" in item.m) { 432 | dragHelper.startDrag(kicker, item.url, item.icon, 433 | "text/x-plasmoidservicename", item.m.pluginName); 434 | } else { 435 | dragHelper.startDrag(kicker, item.url, item.icon); 436 | } 437 | 438 | kicker.dragSource = item; 439 | 440 | pressX = -1; 441 | pressY = -1; 442 | } 443 | } 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ItemListDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.components 2.0 as PlasmaComponents 10 | import org.kde.plasma.core 2.0 as PlasmaCore 11 | 12 | import "code/tools.js" as Tools 13 | 14 | Item { 15 | id: item 16 | 17 | height: isSeparator ? separatorHeight : itemHeight 18 | width: ListView.view.width 19 | 20 | // if it's not disabled and is either a leaf node or a node with children 21 | enabled: !isSeparator && !model.disabled && (!isParent || (isParent && hasChildren)) 22 | 23 | signal actionTriggered(string actionId, variant actionArgument) 24 | signal aboutToShowActionMenu(variant actionMenu) 25 | 26 | readonly property real fullTextWidth: Math.ceil(icon.width + label.implicitWidth + arrow.width + row.anchors.leftMargin + row.anchors.rightMargin + row.actualSpacing) 27 | property bool isSeparator: (model.isSeparator === true) 28 | property bool sorted: (model.sorted === true) 29 | property bool hasChildren: (model.hasChildren === true) 30 | property bool hasActionList: ((model.favoriteId !== null) 31 | || (("hasActionList" in model) && (model.hasActionList === true))) 32 | property QtObject childDialog: null 33 | property Item menu: actionMenu 34 | 35 | Accessible.role: isSeparator ? Accessible.Separator: Accessible.MenuItem 36 | Accessible.name: label.text 37 | 38 | onHasChildrenChanged: { 39 | if (!hasChildren && ListView.view.currentItem === item) { 40 | ListView.view.currentIndex = -1; 41 | } 42 | } 43 | 44 | onAboutToShowActionMenu: { 45 | var actionList = item.hasActionList ? model.actionList : []; 46 | Tools.fillActionMenu(i18n, actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId); 47 | } 48 | 49 | onActionTriggered: { 50 | if (Tools.triggerAction(ListView.view.model, model.index, actionId, actionArgument) === true) { 51 | plasmoid.expanded = false; 52 | } 53 | } 54 | 55 | function openActionMenu(visualParent, x, y) { 56 | aboutToShowActionMenu(actionMenu); 57 | actionMenu.visualParent = visualParent; 58 | actionMenu.open(x, y); 59 | } 60 | 61 | ActionMenu { 62 | id: actionMenu 63 | 64 | onActionClicked: { 65 | item.actionTriggered(actionId, actionArgument); 66 | } 67 | } 68 | 69 | MouseArea { 70 | id: mouseArea 71 | 72 | anchors { 73 | left: parent.left 74 | right: parent.right 75 | verticalCenter: parent.verticalCenter 76 | } 77 | 78 | height: parent.height 79 | 80 | property int mouseCol 81 | property bool pressed: false 82 | property int pressX: -1 83 | property int pressY: -1 84 | 85 | hoverEnabled: true 86 | acceptedButtons: Qt.LeftButton | Qt.RightButton 87 | 88 | onPressed: mouse => { 89 | if (mouse.buttons & Qt.RightButton) { 90 | if (item.hasActionList) { 91 | item.openActionMenu(mouseArea, mouse.x, mouse.y); 92 | } 93 | } else { 94 | pressed = true; 95 | pressX = mouse.x; 96 | pressY = mouse.y; 97 | } 98 | } 99 | 100 | onReleased: mouse => { 101 | if (pressed && !item.hasChildren) { 102 | item.ListView.view.model.trigger(index, "", null); 103 | plasmoid.expanded = false; 104 | } 105 | 106 | pressed = false; 107 | pressX = -1; 108 | pressY = -1; 109 | } 110 | 111 | onPositionChanged: mouse => { 112 | if (pressX !== -1 && model.url && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { 113 | dragHelper.startDrag(kicker, model.url, model.decoration); 114 | pressed = false; 115 | pressX = -1; 116 | pressY = -1; 117 | 118 | return; 119 | } 120 | 121 | // FIXME: Correct escape angle calc for right screen edge. 122 | if (justOpenedTimer.running || !item.hasChildren) { 123 | item.ListView.view.currentIndex = index; 124 | } else { 125 | mouseCol = mouse.x; 126 | 127 | if (index === item.ListView.view.currentIndex) { 128 | updateCurrentItem(); 129 | } else if ((index === item.ListView.view.currentIndex - 1) && mouse.y < (itemHeight - 6) 130 | || (index === item.ListView.view.currentIndex + 1) && mouse.y > 5) { 131 | 132 | if ((item.childDialog && item.childDialog.facingLeft) 133 | ? mouse.x > item.ListView.view.eligibleWidth - 5 : mouse.x < item.ListView.view.eligibleWidth + 5) { 134 | updateCurrentItem(); 135 | } 136 | } else if ((item.childDialog && item.childDialog.facingLeft) 137 | ? mouse.x > item.ListView.view.eligibleWidth : mouse.x < item.ListView.view.eligibleWidth) { 138 | updateCurrentItem(); 139 | } 140 | 141 | updateCurrentItemTimer.start(); 142 | } 143 | } 144 | 145 | onContainsMouseChanged: { 146 | if (!containsMouse) { 147 | pressed = false; 148 | pressX = -1; 149 | pressY = -1; 150 | updateCurrentItemTimer.stop(); 151 | } 152 | } 153 | 154 | function updateCurrentItem() { 155 | item.ListView.view.currentIndex = index; 156 | item.ListView.view.eligibleWidth = Math.min(width, mouseCol); 157 | } 158 | 159 | Timer { 160 | id: updateCurrentItemTimer 161 | 162 | interval: 50 163 | repeat: false 164 | 165 | onTriggered: parent.updateCurrentItem() 166 | } 167 | } 168 | 169 | Row { 170 | id: row 171 | 172 | anchors.left: parent.left 173 | anchors.leftMargin: highlightItemSvg.margins.left 174 | anchors.right: parent.right 175 | anchors.rightMargin: highlightItemSvg.margins.right 176 | 177 | height: parent.height 178 | 179 | spacing: PlasmaCore.Units.smallSpacing * 2 180 | readonly property real actualSpacing: ((icon.visible ? 1 : 0) * spacing) + ((arrow.visible ? 1 : 0) * spacing) 181 | 182 | LayoutMirroring.enabled: (Qt.application.layoutDirection === Qt.RightToLeft) 183 | 184 | PlasmaCore.IconItem { 185 | id: icon 186 | 187 | anchors.verticalCenter: parent.verticalCenter 188 | 189 | width: visible ? PlasmaCore.Units.iconSizes.small : 0 190 | height: width 191 | 192 | visible: iconsEnabled 193 | 194 | animated: false 195 | usesPlasmaTheme: false 196 | 197 | source: model.decoration 198 | } 199 | 200 | PlasmaComponents.Label { 201 | id: label 202 | 203 | enabled: !isParent || (isParent && item.hasChildren) 204 | 205 | anchors.verticalCenter: parent.verticalCenter 206 | 207 | width: parent.width - icon.width - arrow.width - parent.actualSpacing 208 | 209 | verticalAlignment: Text.AlignVCenter 210 | 211 | textFormat: Text.PlainText 212 | wrapMode: Text.NoWrap 213 | elide: Text.ElideRight 214 | 215 | text: model.display 216 | } 217 | 218 | PlasmaCore.SvgItem { 219 | id: arrow 220 | 221 | anchors.verticalCenter: parent.verticalCenter 222 | 223 | width: visible ? PlasmaCore.Units.iconSizes.small : 0 224 | height: width 225 | 226 | visible: item.hasChildren 227 | opacity: (item.ListView.view.currentIndex === index) ? 1.0 : 0.4 228 | 229 | svg: arrows 230 | elementId: (Qt.application.layoutDirection === Qt.RightToLeft) ? "left-arrow" : "right-arrow" 231 | } 232 | } 233 | 234 | Component { 235 | id: separatorComponent 236 | 237 | PlasmaCore.SvgItem { 238 | width: parent.width 239 | height: lineSvg.horLineHeight 240 | 241 | svg: lineSvg 242 | elementId: "horizontal-line" 243 | } 244 | } 245 | 246 | Loader { 247 | id: separatorLoader 248 | 249 | anchors.left: parent.left 250 | anchors.leftMargin: highlightItemSvg.margins.left 251 | anchors.right: parent.right 252 | anchors.rightMargin: highlightItemSvg.margins.right 253 | anchors.verticalCenter: parent.verticalCenter 254 | 255 | // Separator positions don't make sense when sorting everything alphabetically 256 | active: item.isSeparator && !item.sorted 257 | 258 | asynchronous: false 259 | sourceComponent: separatorComponent 260 | } 261 | 262 | Keys.onPressed: { 263 | if (event.key === Qt.Key_Menu && item.hasActionList) { 264 | event.accepted = true; 265 | item.openActionMenu(mouseArea); 266 | } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return) && !item.hasChildren) { 267 | if (!item.hasChildren) { 268 | event.accepted = true; 269 | item.ListView.view.model.trigger(index, "", null); 270 | plasmoid.expanded = false; 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ItemListDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.plasma.core 2.0 as PlasmaCore 10 | import org.kde.plasma.plasmoid 2.0 11 | 12 | import org.kde.plasma.private.kicker 0.1 as Kicker 13 | 14 | Kicker.SubMenu { 15 | id: itemDialog 16 | 17 | property alias focusParent: itemListView.focusParent 18 | property alias model: funnelModel.sourceModel 19 | 20 | property bool aboutToBeDestroyed: false 21 | 22 | visible: false 23 | hideOnWindowDeactivate: plasmoid.hideOnWindowDeactivate 24 | location: PlasmaCore.Types.Floating 25 | offset: PlasmaCore.Units.smallSpacing 26 | 27 | onWindowDeactivated: { 28 | if (!aboutToBeDestroyed) { 29 | plasmoid.expanded = false; 30 | } 31 | } 32 | 33 | mainItem: ItemListView { 34 | id: itemListView 35 | 36 | height: { 37 | const m = funnelModel.sourceModel; 38 | 39 | if (m === null || m === undefined) { 40 | // TODO: setting height to 0 triggers a warning in PlasmaQuick::Dialog 41 | return 0; 42 | } 43 | 44 | return Math.min( 45 | // either fit in screen boundaries (cut to the nearest item/separator boundary), ... 46 | __subFloorMultipleOf( 47 | itemDialog.availableScreenRectForItem(itemListView).height 48 | - itemDialog.margins.top 49 | - itemDialog.margins.bottom, 50 | itemHeight 51 | ) + m.separatorCount * (separatorHeight - itemHeight) 52 | , 53 | // ...or fit the content itself -- whichever is shorter. 54 | ((m.count - m.separatorCount) * itemHeight) 55 | + (m.separatorCount * separatorHeight) 56 | ); 57 | } 58 | 59 | // get x rounded down to the multiple of y, minus extra y. 60 | function __subFloorMultipleOf(x : real, y : real) : real { 61 | return (Math.floor(x / y) - 1) * y; 62 | } 63 | 64 | iconsEnabled: true 65 | 66 | dialog: itemDialog 67 | 68 | model: funnelModel 69 | 70 | Kicker.FunnelModel { 71 | id: funnelModel 72 | 73 | Component.onCompleted: { 74 | kicker.reset.connect(funnelModel.reset); 75 | } 76 | 77 | onCountChanged: { 78 | if (sourceModel && count === 0) { 79 | itemDialog.delayedDestroy(); 80 | } 81 | } 82 | 83 | onSourceModelChanged: { 84 | itemListView.currentIndex = -1; 85 | } 86 | } 87 | } 88 | 89 | function delayedDestroy() { 90 | aboutToBeDestroyed = true; 91 | plasmoid.hideOnWindowDeactivate = false; 92 | visible = false; 93 | 94 | Qt.callLater(() => itemDialog.destroy()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packageDark/contents/ui/ItemListView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2013-2014 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | 9 | import org.kde.kquickcontrolsaddons 2.0 10 | import org.kde.plasma.components 2.0 as PlasmaComponents 11 | import org.kde.plasma.core 2.0 as PlasmaCore 12 | import org.kde.plasma.extras 2.0 as PlasmaExtras 13 | 14 | FocusScope { 15 | id: itemList 16 | 17 | property real minimumWidth: PlasmaCore.Units.gridUnit * 14 18 | property real maximumWidth: minimumWidth * 2 19 | 20 | width: minimumWidth 21 | height: listView.contentHeight 22 | 23 | signal exited 24 | signal keyNavigationAtListEnd 25 | signal appendSearchText(string text) 26 | 27 | property Item focusParent: null 28 | property QtObject dialog: null 29 | property QtObject childDialog: null 30 | property bool iconsEnabled: false 31 | property int itemHeight: Math.ceil((Math.max(theme.mSize(theme.defaultFont).height, PlasmaCore.Units.iconSizes.small) 32 | + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 33 | listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 34 | property int separatorHeight: lineSvg.horLineHeight + (2 * PlasmaCore.Units.smallSpacing) 35 | 36 | property alias currentIndex: listView.currentIndex 37 | property alias currentItem: listView.currentItem 38 | property alias keyNavigationWraps: listView.keyNavigationWraps 39 | property alias showChildDialogs: listView.showChildDialogs 40 | property alias model: listView.model 41 | property alias count: listView.count 42 | property alias containsMouse: listener.containsMouse 43 | property alias resetOnExitDelay: resetIndexTimer.interval 44 | 45 | onFocusParentChanged: { 46 | appendSearchText.connect(focusParent.appendSearchText); 47 | } 48 | 49 | Timer { 50 | id: dialogSpawnTimer 51 | 52 | property bool focusOnSpawn: false 53 | 54 | interval: 70 55 | repeat: false 56 | 57 | onTriggered: { 58 | if (!plasmoid.expanded || model === undefined || currentIndex == -1) { 59 | return; 60 | } 61 | 62 | if (itemList.childDialog != null) { 63 | itemList.childDialog.delayedDestroy(); 64 | } 65 | 66 | // Gets reenabled after the dialog spawn causes a focus-in on the dialog window. 67 | plasmoid.hideOnWindowDeactivate = false; 68 | 69 | itemList.childDialog = itemListDialogComponent.createObject(itemList); 70 | itemList.childDialog.focusParent = itemList; 71 | itemList.childDialog.visualParent = listView.currentItem; 72 | itemList.childDialog.model = model.modelForRow(listView.currentIndex); 73 | itemList.childDialog.visible = true; 74 | 75 | windowSystem.forceActive(itemList.childDialog.mainItem); 76 | itemList.childDialog.mainItem.focus = true; 77 | 78 | if (focusOnSpawn) { 79 | itemList.childDialog.mainItem.showChildDialogs = false; 80 | itemList.childDialog.mainItem.currentIndex = 0; 81 | itemList.childDialog.mainItem.showChildDialogs = true; 82 | } 83 | } 84 | } 85 | 86 | Timer { 87 | id: resetIndexTimer 88 | 89 | interval: (dialog != null) ? 50 : 150 90 | repeat: false 91 | 92 | onTriggered: { 93 | if (focus && (!itemList.childDialog || !itemList.childDialog.mainItem.containsMouse)) { 94 | currentIndex = -1; 95 | itemList.exited(); 96 | } 97 | } 98 | } 99 | 100 | MouseEventListener { 101 | id: listener 102 | 103 | anchors.fill: parent 104 | 105 | hoverEnabled: true 106 | 107 | onContainsMouseChanged: { 108 | listView.eligibleWidth = listView.width; 109 | 110 | if (containsMouse) { 111 | resetIndexTimer.stop(); 112 | itemList.forceActiveFocus(); 113 | } else if ((!itemList.childDialog || !dialog) 114 | && (!currentItem || !currentItem.menu.opened)) { 115 | resetIndexTimer.start(); 116 | } 117 | } 118 | 119 | PlasmaExtras.ScrollArea { 120 | anchors.fill: parent 121 | 122 | focus: true 123 | 124 | ListView { 125 | id: listView 126 | 127 | property bool showChildDialogs: true 128 | property int eligibleWidth: width 129 | 130 | currentIndex: -1 131 | 132 | boundsBehavior: Flickable.StopAtBounds 133 | snapMode: ListView.SnapToItem 134 | spacing: 0 135 | keyNavigationWraps: (dialog != null) 136 | 137 | delegate: ItemListDelegate { 138 | onFullTextWidthChanged: { 139 | if (fullTextWidth > itemList.width) itemList.width = Math.min(fullTextWidth, itemList.maximumWidth); 140 | } 141 | } 142 | 143 | highlight: PlasmaComponents.Highlight { 144 | visible: listView.currentItem && !listView.currentItem.isSeparator 145 | } 146 | 147 | highlightMoveDuration: 0 148 | 149 | onCountChanged: { 150 | currentIndex = -1; 151 | } 152 | 153 | onCurrentIndexChanged: { 154 | if (currentIndex != -1) { 155 | if (itemList.childDialog) { 156 | if (currentItem && currentItem.hasChildren) { 157 | itemList.childDialog.mainItem.width = itemList.minimumWidth; 158 | itemList.childDialog.model = model.modelForRow(currentIndex); 159 | itemList.childDialog.visualParent = listView.currentItem; 160 | } else { 161 | itemList.childDialog.delayedDestroy(); 162 | } 163 | 164 | return; 165 | } 166 | 167 | if (currentItem == null || !currentItem.hasChildren || !plasmoid.expanded) { 168 | dialogSpawnTimer.stop(); 169 | 170 | return; 171 | } 172 | 173 | if (showChildDialogs) { 174 | dialogSpawnTimer.focusOnSpawn = false; 175 | dialogSpawnTimer.restart(); 176 | } 177 | } else if (itemList.childDialog != null) { 178 | itemList.childDialog.delayedDestroy(); 179 | itemList.childDialog = null; 180 | } 181 | } 182 | 183 | onCurrentItemChanged: { 184 | if (currentItem) { 185 | currentItem.menu.closed.connect(resetIndexTimer.restart); 186 | } 187 | } 188 | 189 | Keys.onPressed: event => { 190 | if (event.key === Qt.Key_Up) { 191 | event.accepted = true; 192 | 193 | if (!keyNavigationWraps && currentIndex == 0) { 194 | itemList.keyNavigationAtListEnd(); 195 | 196 | return; 197 | } 198 | 199 | showChildDialogs = false; 200 | decrementCurrentIndex(); 201 | 202 | if (currentItem.isSeparator) { 203 | decrementCurrentIndex(); 204 | } 205 | 206 | showChildDialogs = true; 207 | } else if (event.key === Qt.Key_Down) { 208 | event.accepted = true; 209 | 210 | if (!keyNavigationWraps && currentIndex == count - 1) { 211 | itemList.keyNavigationAtListEnd(); 212 | 213 | return; 214 | } 215 | 216 | showChildDialogs = false; 217 | incrementCurrentIndex(); 218 | 219 | if (currentItem.isSeparator) { 220 | incrementCurrentIndex(); 221 | } 222 | 223 | showChildDialogs = true; 224 | } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && itemList.childDialog != null) { 225 | windowSystem.forceActive(itemList.childDialog.mainItem); 226 | itemList.childDialog.mainItem.focus = true; 227 | itemList.childDialog.mainItem.currentIndex = 0; 228 | } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && itemList.childDialog == null 229 | && currentItem != null && currentItem.hasChildren) { 230 | dialogSpawnTimer.focusOnSpawn = true; 231 | dialogSpawnTimer.restart(); 232 | } else if (event.key === Qt.Key_Left && dialog != null) { 233 | dialog.destroy(); 234 | } else if (event.key === Qt.Key_Escape) { 235 | plasmoid.expanded = false; 236 | } else if (event.key === Qt.Key_Tab) { 237 | //do nothing, and skip appending text 238 | } else if (event.text !== "") { 239 | if (/[\x00-\x1F\x7F]/.test(event.text)) { 240 | // We still want to focus it 241 | appendSearchText(""); 242 | } else { 243 | appendSearchText(event.text); 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | Component.onCompleted: { 252 | windowSystem.monitorWindowFocus(itemList); 253 | 254 | if (dialog == null) { 255 | appendSearchText.connect(root.appendSearchText); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /packageDark/contents/ui/MiniButton.qml: -------------------------------------------------------------------------------- 1 | 2 | import QtQuick 2.15 3 | import QtQuick.Controls 2.15 4 | import org.kde.plasma.core 2.0 as PlasmaCore 5 | 6 | 7 | Rectangle{ 8 | id: root 9 | 10 | property alias icon: icon.source 11 | property alias tooltip: tooltip.text 12 | 13 | signal clicked() 14 | 15 | width: PlasmaCore.Units.iconSizes.large 16 | height: width 17 | radius: width*0.5 18 | color: rootMouseArea.containsMouse ? theme.highlightColor : 'transparent' 19 | 20 | PlasmaCore.IconItem { 21 | id: icon 22 | anchors.centerIn: parent 23 | width: PlasmaCore.Units.iconSizes.smallMedium 24 | opacity: 0.8 25 | } 26 | 27 | ToolTip { 28 | id: tooltip 29 | parent: root 30 | visible: rootMouseArea.containsMouse 31 | 32 | } 33 | 34 | MouseArea { 35 | id: rootMouseArea 36 | anchors.fill: parent 37 | hoverEnabled: true 38 | onClicked: root.clicked() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packageDark/contents/ui/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 separator = { "type": "separator" }; 20 | actionList.push(separator); 21 | // actionList = actions.concat(actionList); // this crashes Qt O.o 22 | actionList.push.apply(actionList, actions); 23 | } else { 24 | actionList = actions; 25 | } 26 | } 27 | 28 | actionMenu.actionList = actionList; 29 | } 30 | 31 | function createFavoriteActions(i18n, favoriteModel, favoriteId) { 32 | if (favoriteModel === null || !favoriteModel.enabled || favoriteId == null) { 33 | return null; 34 | } 35 | 36 | 37 | if (favoriteModel.activities === undefined || 38 | favoriteModel.activities.runningActivities.length <= 1) { 39 | var action = {}; 40 | 41 | if (favoriteModel.isFavorite(favoriteId)) { 42 | action.text = i18n("Remove from Favorites"); 43 | action.icon = "bookmark-remove"; 44 | action.actionId = "_kicker_favorite_remove"; 45 | } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { 46 | action.text = i18n("Add to Favorites"); 47 | action.icon = "bookmark-new"; 48 | action.actionId = "_kicker_favorite_add"; 49 | } else { 50 | return null; 51 | } 52 | 53 | action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; 54 | 55 | return [action]; 56 | 57 | } else { 58 | var actions = []; 59 | 60 | var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); 61 | 62 | var activities = favoriteModel.activities.runningActivities; 63 | 64 | // Adding the item to link/unlink to all activities 65 | 66 | var linkedToAllActivities = 67 | !(linkedActivities.indexOf(":global") === -1); 68 | 69 | actions.push({ 70 | text : i18n("On All Activities"), 71 | checkable : true, 72 | 73 | actionId : linkedToAllActivities ? 74 | "_kicker_favorite_remove_from_activity" : 75 | "_kicker_favorite_set_to_activity", 76 | checked : linkedToAllActivities, 77 | 78 | actionArgument : { 79 | favoriteModel: favoriteModel, 80 | favoriteId: favoriteId, 81 | favoriteActivity: "" 82 | } 83 | }); 84 | 85 | 86 | // Adding items for each activity separately 87 | 88 | var addActivityItem = function(activityId, activityName) { 89 | var linkedToThisActivity = 90 | !(linkedActivities.indexOf(activityId) === -1); 91 | 92 | actions.push({ 93 | text : activityName, 94 | checkable : true, 95 | checked : linkedToThisActivity && !linkedToAllActivities, 96 | 97 | actionId : 98 | // If we are on all activities, and the user clicks just one 99 | // specific activity, unlink from everything else 100 | linkedToAllActivities ? "_kicker_favorite_set_to_activity" : 101 | 102 | // If we are linked to the current activity, just unlink from 103 | // that single one 104 | linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : 105 | 106 | // Otherwise, link to this activity, but do not unlink from 107 | // other ones 108 | "_kicker_favorite_add_to_activity", 109 | 110 | actionArgument : { 111 | favoriteModel : favoriteModel, 112 | favoriteId : favoriteId, 113 | favoriteActivity : activityId 114 | } 115 | }); 116 | }; 117 | 118 | // Adding the item to link/unlink to the current activity 119 | 120 | addActivityItem(favoriteModel.activities.currentActivity, i18n("On the Current Activity")); 121 | 122 | actions.push({ 123 | type: "separator", 124 | actionId: "_kicker_favorite_separator" 125 | }); 126 | 127 | // Adding the items for each activity 128 | 129 | activities.forEach(function(activityId) { 130 | addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); 131 | }); 132 | 133 | return [{ 134 | text : i18n("Show in Favorites"), 135 | icon : "favorite", 136 | subActions : actions 137 | }]; 138 | } 139 | } 140 | 141 | function triggerAction(model, index, actionId, actionArgument) { 142 | function startsWith(txt, needle) { 143 | return txt.substr(0, needle.length) === needle; 144 | } 145 | 146 | if (startsWith(actionId, "_kicker_favorite_")) { 147 | handleFavoriteAction(actionId, actionArgument); 148 | return; 149 | } 150 | 151 | var closeRequested = model.trigger(index, actionId, actionArgument); 152 | 153 | if (closeRequested) { 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | 160 | function handleFavoriteAction(actionId, actionArgument) { 161 | var favoriteId = actionArgument.favoriteId; 162 | var favoriteModel = actionArgument.favoriteModel; 163 | 164 | console.log(actionId); 165 | 166 | if (favoriteModel === null || favoriteId == null) { 167 | return null; 168 | } 169 | 170 | if (actionId == "_kicker_favorite_remove") { 171 | console.log("Removing from all activities"); 172 | favoriteModel.removeFavorite(favoriteId); 173 | } else if (actionId == "_kicker_favorite_add") { 174 | console.log("Adding to global activity"); 175 | favoriteModel.addFavorite(favoriteId); 176 | } else if (actionId == "_kicker_favorite_remove_from_activity") { 177 | console.log("Removing from a specific activity"); 178 | favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); 179 | 180 | } else if (actionId == "_kicker_favorite_add_to_activity") { 181 | console.log("Adding to another activity"); 182 | favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); 183 | 184 | } else if (actionId == "_kicker_favorite_set_to_activity") { 185 | console.log("Removing the item from the favourites, and re-adding it just to be on a specific activity"); 186 | favoriteModel.setFavoriteOn(favoriteId, actionArgument.favoriteActivity); 187 | 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /packageDark/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014-2015 Eike Hein 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 2.15 8 | import QtQuick.Layouts 1.15 9 | 10 | import org.kde.plasma.components 2.0 as PlasmaComponents 11 | import org.kde.plasma.core 2.0 as PlasmaCore 12 | import org.kde.plasma.plasmoid 2.0 13 | 14 | import org.kde.plasma.private.kicker 0.1 as Kicker 15 | 16 | Item { 17 | id: kicker 18 | 19 | anchors.fill: parent 20 | 21 | signal reset 22 | 23 | property bool isDash: true//plasmoid.pluginName === "org.kde.plasma.kickerdash" 24 | 25 | Plasmoid.switchWidth: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumWidth 26 | Plasmoid.switchHeight: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumHeight 27 | 28 | // this is a bit of a hack to prevent Plasma from spawning a dialog on its own when we're Dash 29 | Plasmoid.preferredRepresentation: isDash ? Plasmoid.fullRepresentation : null 30 | 31 | Plasmoid.compactRepresentation: null 32 | Plasmoid.fullRepresentation: compactRepresentation 33 | 34 | property Component itemListDialogComponent: Qt.createComponent(Qt.resolvedUrl("./ItemListDialog.qml")) 35 | property Item dragSource: null 36 | 37 | property QtObject globalFavorites: rootModel.favoritesModel 38 | property QtObject systemFavorites: rootModel.systemFavoritesModel 39 | 40 | Plasmoid.icon: plasmoid.configuration.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon 41 | 42 | onSystemFavoritesChanged: { 43 | systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; 44 | } 45 | 46 | function action_menuedit() { 47 | processRunner.runMenuEditor(); 48 | } 49 | 50 | function updateSvgMetrics() { 51 | lineSvg.horLineHeight = lineSvg.elementSize("horizontal-line").height; 52 | lineSvg.vertLineWidth = lineSvg.elementSize("vertical-line").width; 53 | } 54 | 55 | Component { 56 | id: compactRepresentation 57 | CompactRepresentation {} 58 | } 59 | 60 | 61 | Kicker.RootModel { 62 | id: rootModel 63 | 64 | autoPopulate: false 65 | 66 | appNameFormat: plasmoid.configuration.appNameFormat 67 | flat: true 68 | sorted: true 69 | showSeparators: false 70 | appletInterface: plasmoid 71 | 72 | showAllApps: true 73 | showAllAppsCategorized: false 74 | showTopLevelItems: true 75 | showRecentApps: false 76 | showRecentDocs: false 77 | showRecentContacts: false 78 | recentOrdering: 0 79 | 80 | Component.onCompleted: { 81 | favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id) 82 | 83 | if (!plasmoid.configuration.favoritesPortedToKAstats) { 84 | if (favoritesModel.count < 1) { 85 | favoritesModel.portOldFavorites(plasmoid.configuration.favoriteApps); 86 | } 87 | plasmoid.configuration.favoritesPortedToKAstats = true; 88 | } 89 | } 90 | } 91 | 92 | Connections { 93 | target: globalFavorites 94 | 95 | function onFavoritesChanged() { 96 | plasmoid.configuration.favoriteApps = target.favorites; 97 | } 98 | } 99 | 100 | Connections { 101 | target: systemFavorites 102 | 103 | function onFavoritesChanged() { 104 | plasmoid.configuration.favoriteSystemActions = target.favorites; 105 | } 106 | } 107 | 108 | Connections { 109 | target: plasmoid.configuration 110 | 111 | function onFavoriteAppsChanged() { 112 | globalFavorites.favorites = plasmoid.configuration.favoriteApps; 113 | } 114 | 115 | function onFavoriteSystemActionsChanged() { 116 | systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; 117 | } 118 | 119 | function onHiddenApplicationsChanged(){ 120 | rootModel.refresh(); // Force refresh on hidden 121 | } 122 | } 123 | 124 | Kicker.RunnerModel { 125 | id: runnerModel 126 | appletInterface: plasmoid 127 | favoritesModel: rootModel.favoritesModel 128 | mergeResults: true 129 | } 130 | 131 | Kicker.DragHelper { 132 | id: dragHelper 133 | 134 | dragIconSize: PlasmaCore.Units.iconSizes.medium 135 | } 136 | 137 | Kicker.ProcessRunner { 138 | id: processRunner 139 | } 140 | 141 | Kicker.WindowSystem { 142 | id: windowSystem 143 | } 144 | 145 | PlasmaCore.FrameSvgItem { 146 | id: highlightItemSvg 147 | 148 | visible: false 149 | 150 | imagePath: "widgets/viewitem" 151 | prefix: "hover" 152 | } 153 | 154 | PlasmaCore.FrameSvgItem { 155 | id: listItemSvg 156 | 157 | visible: false 158 | 159 | imagePath: "widgets/listitem" 160 | prefix: "normal" 161 | } 162 | 163 | PlasmaCore.Svg { 164 | id: arrows 165 | 166 | imagePath: "widgets/arrows" 167 | size: "16x16" 168 | } 169 | 170 | PlasmaCore.Svg { 171 | id: lineSvg 172 | imagePath: "widgets/line" 173 | 174 | property int horLineHeight 175 | property int vertLineWidth 176 | } 177 | 178 | PlasmaComponents.Label { 179 | id: toolTipDelegate 180 | 181 | width: contentWidth 182 | height: undefined 183 | 184 | property Item toolTip 185 | 186 | text: toolTip ? toolTip.text : "" 187 | } 188 | 189 | Timer { 190 | id: justOpenedTimer 191 | 192 | repeat: false 193 | interval: 600 194 | } 195 | 196 | Connections { 197 | target: plasmoid 198 | 199 | function onExpandedChanged(expanded) { 200 | if (expanded) { 201 | windowSystem.monitorWindowVisibility(plasmoid.fullRepresentationItem); 202 | justOpenedTimer.start(); 203 | } else { 204 | kicker.reset(); 205 | } 206 | } 207 | } 208 | 209 | PlasmaCore.DataSource { 210 | id: pmEngine 211 | engine: "powermanagement" 212 | connectedSources: ["PowerDevil", "Sleep States"] 213 | function performOperation(what) { 214 | var service = serviceForSource("PowerDevil") 215 | var operation = service.operationDescription(what) 216 | service.startOperationCall(operation) 217 | } 218 | } 219 | 220 | 221 | function resetDragSource() { 222 | dragSource = null; 223 | } 224 | 225 | function enableHideOnWindowDeactivate() { 226 | plasmoid.hideOnWindowDeactivate = true; 227 | } 228 | 229 | Component.onCompleted: { 230 | if (plasmoid.hasOwnProperty("activationTogglesExpanded")) { 231 | plasmoid.activationTogglesExpanded = !kicker.isDash 232 | } 233 | 234 | windowSystem.focusIn.connect(enableHideOnWindowDeactivate); 235 | plasmoid.hideOnWindowDeactivate = true; 236 | 237 | if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { 238 | plasmoid.setAction("menuedit", i18n("Edit Applications…"), "kmenuedit"); 239 | } 240 | 241 | updateSvgMetrics(); 242 | PlasmaCore.Theme.themeChanged.connect(updateSvgMetrics); 243 | 244 | rootModel.refreshed.connect(reset); 245 | dragHelper.dropped.connect(resetDragSource); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /packageDark/metadata.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Launchpad Plasma Dark 3 | Comment=A configurable grid launcher menu 4 | 5 | Type=Service 6 | Icon=start-here-kde 7 | X-KDE-ServiceTypes=Plasma/Applet 8 | 9 | X-Plasma-API=declarativeappletscript 10 | X-Plasma-MainScript=ui/main.qml 11 | X-Plasma-Provides=org.kde.plasma.launchermenu 12 | 13 | X-KDE-PluginInfo-Author=adhe 14 | X-KDE-PluginInfo-Email=adhemarks@gmail.com 15 | X-KDE-PluginInfo-Name=launchpadPlasmaDark 16 | X-KDE-PluginInfo-Version=2.0 17 | X-KDE-PluginInfo-Category=Application Launchers 18 | X-KDE-PluginInfo-Depends= 19 | X-KDE-PluginInfo-License=GPL v2+ 20 | X-KDE-PluginInfo-EnabledByDefault=true 21 | -------------------------------------------------------------------------------- /packageDark/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Authors": [ 4 | { 5 | "Email": "adhemarks@gmail.com", 6 | "Name": "Adhe" 7 | } 8 | ], 9 | "Category": "Application Launchers", 10 | "Description": "A configurable grid launcher menu", 11 | "Description[x-test]": "xxA configurable grid launcher menuxx", 12 | "EnabledByDefault": true, 13 | "Icon": "start-here-kde", 14 | "Id": "launchpadPlasmaDark", 15 | "License": "GPL-2.0+", 16 | "Name": "Launchpad Plasma Dark", 17 | "Name[x-test]": "xxLaunchpad Plasma Darkxx", 18 | "ServiceTypes": [ 19 | "Plasma/Applet" 20 | ], 21 | "Version": "2.0", 22 | "Website": "" 23 | }, 24 | "X-Plasma-API": "declarativeappletscript", 25 | "X-Plasma-MainScript": "ui/main.qml", 26 | "X-Plasma-Provides": [ 27 | "org.kde.plasma.launchermenu" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adhec/launchpad-plasma/4952bb8df4da2861f11b785add613f4b4c311ef3/preview.jpg --------------------------------------------------------------------------------