1/* 2 SPDX-FileCopyrightText: 2015 Eike Hein <hein@kde.org> 3 4 SPDX-License-Identifier: GPL-2.0-or-later 5*/ 6 7import QtQuick 2.4 8import QtGraphicalEffects 1.0 9import QtQml 2.15 10 11import org.kde.plasma.core 2.1 as PlasmaCore 12import org.kde.plasma.components 2.0 as PlasmaComponents 13import org.kde.plasma.core 2.0 as PlasmaCore 14import org.kde.plasma.extras 2.0 as PlasmaExtras 15import org.kde.kquickcontrolsaddons 2.0 16import org.kde.kwindowsystem 1.0 17 18import org.kde.plasma.private.shell 2.0 19 20import org.kde.plasma.private.kicker 0.1 as Kicker 21 22import "code/tools.js" as Tools 23 24/* TODO 25 * Reverse middleRow layout + keyboard nav + filter list text alignment in rtl locales. 26 * Keep cursor column when arrow'ing down past non-full trailing rows into a lower grid. 27 * Make DND transitions cleaner by performing an item swap instead of index reinsertion. 28*/ 29 30Kicker.DashboardWindow { 31 id: root 32 33 property bool smallScreen: ((Math.floor(width / PlasmaCore.Units.iconSizes.huge) <= 22) || (Math.floor(height / PlasmaCore.Units.iconSizes.huge) <= 14)) 34 35 property int iconSize: smallScreen ? PlasmaCore.Units.iconSizes.large : PlasmaCore.Units.iconSizes.huge 36 property int cellSize: iconSize + PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).height 37 + (2 * PlasmaCore.Units.smallSpacing) 38 + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 39 highlightItemSvg.margins.left + highlightItemSvg.margins.right)) 40 property int columns: Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize)) 41 property bool searching: (searchField.text != "") 42 property var widgetExplorer: null 43 44 keyEventProxy: searchField 45 backgroundColor: Qt.rgba(0, 0, 0, 0.737) 46 47 onKeyEscapePressed: { 48 if (searching) { 49 searchField.clear(); 50 } else { 51 root.toggle(); 52 } 53 } 54 55 onVisibleChanged: { 56 tabBar.activeTab = 0; 57 reset(); 58 59 if (visible) { 60 preloadAllAppsTimer.restart(); 61 } 62 } 63 64 onSearchingChanged: { 65 if (!searching) { 66 reset(); 67 } else { 68 filterList.currentIndex = -1; 69 70 if (tabBar.activeTab == 1) { 71 widgetExplorer.widgetsModel.filterQuery = ""; 72 widgetExplorer.widgetsModel.filterType = ""; 73 } 74 } 75 } 76 77 function reset() { 78 searchField.clear(); 79 globalFavoritesGrid.currentIndex = -1; 80 systemFavoritesGrid.currentIndex = -1; 81 filterList.currentIndex = 0; 82 funnelModel.sourceModel = rootModel.modelForRow(0); 83 mainGrid.model = (tabBar.activeTab == 0) ? funnelModel : root.widgetExplorer.widgetsModel; 84 mainGrid.currentIndex = -1; 85 filterListScrollArea.focus = true; 86 filterList.model = (tabBar.activeTab == 0) ? rootModel : root.widgetExplorer.filterModel; 87 } 88 89 function updateWidgetExplorer() { 90 if (tabBar.activeTab == 1 /* Widgets */ || tabBar.hoveredTab == 1) { 91 if (!root.widgetExplorer) { 92 root.widgetExplorer = widgetExplorerComponent.createObject(root, { 93 containment: containmentInterface.screenContainment(plasmoid) 94 }); 95 } 96 } else if (root.widgetExplorer) { 97 root.widgetExplorer.destroy(); 98 root.widgetExplorer = null; 99 } 100 } 101 102 mainItem: MouseArea { 103 id: rootItem 104 105 anchors.fill: parent 106 107 acceptedButtons: Qt.LeftButton | Qt.RightButton 108 109 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft 110 LayoutMirroring.childrenInherit: true 111 112 Connections { 113 target: kicker 114 115 function onReset() { 116 if (!root.searching) { 117 filterList.applyFilter(); 118 119 if (tabBar.activeTab == 0) { 120 funnelModel.reset(); 121 } 122 } 123 } 124 125 function onDragSourceChanged() { 126 if (!kicker.dragSource) { 127 // FIXME TODO HACK: Reset all views post-DND to work around 128 // mouse grab bug despite QQuickWindow::mouseGrabberItem==0x0. 129 // Needs a more involved hunt through Qt Quick sources later since 130 // it's not happening with near-identical code in the menu repr. 131 rootModel.refresh(); 132 } else if (tabBar.activeTab == 1) { 133 root.toggle(); 134 containmentInterface.ensureMutable(containmentInterface.screenContainment(plasmoid)); 135 kwindowsystem.showingDesktop = true; 136 } 137 } 138 } 139 140 KWindowSystem { 141 id: kwindowsystem 142 } 143 144 Component { 145 id: widgetExplorerComponent 146 147 WidgetExplorer { showSpecialFilters: false } 148 } 149 150 Connections { 151 target: plasmoid 152 function onUserConfiguringChanged() { 153 if (plasmoid.userConfiguring) { 154 root.hide() 155 } 156 } 157 } 158 159 PlasmaComponents.Menu { 160 id: contextMenu 161 162 PlasmaComponents.MenuItem { 163 action: plasmoid.action("configure") 164 } 165 } 166 167 PlasmaExtras.Heading { 168 id: dummyHeading 169 170 visible: false 171 172 width: 0 173 174 level: 1 175 } 176 177 TextMetrics { 178 id: headingMetrics 179 180 font: dummyHeading.font 181 } 182 183 Kicker.FunnelModel { 184 id: funnelModel 185 186 onSourceModelChanged: { 187 if (mainColumn.visible) { 188 mainGrid.currentIndex = -1; 189 mainGrid.forceLayout(); 190 } 191 } 192 } 193 194 Timer { 195 id: preloadAllAppsTimer 196 197 property bool done: false 198 199 interval: 1000 200 repeat: false 201 202 onTriggered: { 203 if (done || root.searching) { 204 return; 205 } 206 207 for (var i = 0; i < rootModel.count; ++i) { 208 var model = rootModel.modelForRow(i); 209 210 if (model.description === "KICKER_ALL_MODEL") { 211 allAppsGrid.model = model; 212 done = true; 213 break; 214 } 215 } 216 } 217 218 function defer() { 219 if (running && !done) { 220 restart(); 221 } 222 } 223 } 224 225 Kicker.ContainmentInterface { 226 id: containmentInterface 227 } 228 229 DashboardTabBar { 230 id: tabBar 231 232 y: 0 233 234 anchors.horizontalCenter: parent.horizontalCenter 235 236 visible: (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) 237 238 onActiveTabChanged: { 239 root.updateWidgetExplorer(); 240 root.reset(); 241 } 242 243 onHoveredTabChanged: root.updateWidgetExplorer() 244 245 Keys.onDownPressed: { 246 mainColumn.tryActivate(0, 0); 247 } 248 } 249 250 TextEdit { 251 id: searchField 252 253 width: 0 254 height: 0 255 256 visible: false 257 258 persistentSelection: true 259 260 onTextChanged: { 261 if (tabBar.activeTab == 0) { 262 runnerModel.query = searchField.text; 263 } else { 264 root.widgetExplorer.widgetsModel.searchTerm = searchField.text; 265 } 266 } 267 268 function clear() { 269 text = ""; 270 } 271 272 onSelectionStartChanged: Qt.callLater(searchHeading.updateSelection) 273 onSelectionEndChanged: Qt.callLater(searchHeading.updateSelection) 274 } 275 276 TextEdit { 277 id: searchHeading 278 279 anchors { 280 horizontalCenter: parent.horizontalCenter 281 } 282 283 y: (middleRow.anchors.topMargin / 2) - (root.smallScreen ? (height/10) : 0) 284 285 font.pointSize: dummyHeading.font.pointSize * 1.5 286 wrapMode: Text.NoWrap 287 opacity: 1.0 288 289 selectByMouse: false 290 cursorVisible: false 291 292 color: "white" 293 294 text: root.searching ? i18n("Searching for '%1'", searchField.text) : i18n("Type to search…") 295 296 function updateSelection() { 297 if (!searchField.selectedText) { 298 return; 299 } 300 301 var delta = text.lastIndexOf(searchField.text, text.length - 2); 302 searchHeading.select(searchField.selectionStart + delta, searchField.selectionEnd + delta); 303 } 304 } 305 306 PlasmaComponents.ToolButton { 307 id: cancelSearchButton 308 309 anchors { 310 left: searchHeading.right 311 leftMargin: PlasmaCore.Units.largeSpacing 312 verticalCenter: searchHeading.verticalCenter 313 } 314 315 width: PlasmaCore.Units.iconSizes.large 316 height: width 317 318 visible: (searchField.text != "") 319 320 iconName: "dialog-close" 321 flat: false 322 323 onClicked: searchField.clear(); 324 325 Keys.onPressed: event => { 326 if (event.key === Qt.Key_Tab) { 327 event.accepted = true; 328 329 if (runnerModel.count) { 330 mainColumn.tryActivate(0, 0); 331 } else { 332 systemFavoritesGrid.tryActivate(0, 0); 333 } 334 } else if (event.key === Qt.Key_Backtab) { 335 event.accepted = true; 336 337 if (tabBar.visible) { 338 tabBar.focus = true; 339 } else if (globalFavoritesGrid.enabled) { 340 globalFavoritesGrid.tryActivate(0, 0); 341 } else { 342 systemFavoritesGrid.tryActivate(0, 0); 343 } 344 } 345 } 346 } 347 348 Row { 349 id: middleRow 350 351 anchors { 352 top: parent.top 353 topMargin: PlasmaCore.Units.gridUnit * (smallScreen ? 8 : 10) 354 bottom: parent.bottom 355 bottomMargin: (PlasmaCore.Units.gridUnit * 2) 356 horizontalCenter: parent.horizontalCenter 357 } 358 359 width: (root.columns * root.cellSize) + (2 * spacing) 360 361 spacing: PlasmaCore.Units.gridUnit * 2 362 363 Item { 364 id: favoritesColumn 365 366 anchors { 367 top: parent.top 368 bottom: parent.bottom 369 } 370 371 width: (columns * root.cellSize) + PlasmaCore.Units.gridUnit 372 373 property int columns: 3 374 375 PlasmaExtras.Heading { 376 id: favoritesColumnLabel 377 378 enabled: (tabBar.activeTab == 0) 379 380 anchors { 381 top: parent.top 382 } 383 384 x: PlasmaCore.Units.smallSpacing 385 width: parent.width - x 386 387 elide: Text.ElideRight 388 wrapMode: Text.NoWrap 389 390 color: "white" 391 392 level: 1 393 394 text: i18n("Favorites") 395 396 opacity: (enabled ? 1.0 : 0.3) 397 398 Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 399 } 400 401 PlasmaCore.SvgItem { 402 id: favoritesColumnLabelUnderline 403 404 enabled: (tabBar.activeTab == 0) 405 406 anchors { 407 top: favoritesColumnLabel.bottom 408 } 409 410 width: parent.width - PlasmaCore.Units.gridUnit 411 height: lineSvg.horLineHeight 412 413 svg: lineSvg 414 elementId: "horizontal-line" 415 416 opacity: (enabled ? 1.0 : 0.3) 417 418 Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 419 } 420 421 ItemGridView { 422 id: globalFavoritesGrid 423 424 enabled: (tabBar.activeTab == 0) 425 426 anchors { 427 top: favoritesColumnLabelUnderline.bottom 428 topMargin: PlasmaCore.Units.largeSpacing 429 } 430 431 property int rows: (Math.floor((parent.height - favoritesColumnLabel.height 432 - favoritesColumnLabelUnderline.height - PlasmaCore.Units.largeSpacing) / root.cellSize) 433 - systemFavoritesGrid.rows) 434 435 width: parent.width 436 height: rows * root.cellSize 437 438 cellWidth: root.cellSize 439 cellHeight: root.cellSize 440 iconSize: root.iconSize 441 442 model: globalFavorites 443 444 dropEnabled: true 445 usesPlasmaTheme: false 446 447 opacity: (enabled ? 1.0 : 0.3) 448 449 Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 450 451 onCurrentIndexChanged: { 452 preloadAllAppsTimer.defer(); 453 } 454 455 onKeyNavRight: { 456 mainColumn.tryActivate(currentRow(), 0); 457 } 458 459 onKeyNavDown: { 460 systemFavoritesGrid.tryActivate(0, currentCol()); 461 } 462 463 Keys.onPressed: event => { 464 if (event.key === Qt.Key_Tab) { 465 event.accepted = true; 466 467 if (tabBar.visible) { 468 tabBar.focus = true; 469 } else if (root.searching) { 470 cancelSearchButton.focus = true; 471 } else { 472 mainColumn.tryActivate(0, 0); 473 } 474 } else if (event.key === Qt.Key_Backtab) { 475 event.accepted = true; 476 systemFavoritesGrid.tryActivate(0, 0); 477 } 478 } 479 480 Binding { 481 target: globalFavorites 482 property: "iconSize" 483 value: root.iconSize 484 restoreMode: Binding.RestoreBinding 485 } 486 } 487 488 ItemGridView { 489 id: systemFavoritesGrid 490 491 anchors { 492 top: globalFavoritesGrid.bottom 493 } 494 495 property int rows: Math.ceil(count / Math.floor(width / root.cellSize)) 496 497 width: parent.width 498 height: rows * root.cellSize 499 500 cellWidth: root.cellSize 501 cellHeight: root.cellSize 502 iconSize: root.iconSize 503 504 model: systemFavorites 505 506 dropEnabled: true 507 usesPlasmaTheme: true 508 509 onCurrentIndexChanged: { 510 preloadAllAppsTimer.defer(); 511 } 512 513 onKeyNavRight: { 514 mainColumn.tryActivate(globalFavoritesGrid.rows + currentRow(), 0); 515 } 516 517 onKeyNavUp: { 518 globalFavoritesGrid.tryActivate(globalFavoritesGrid.rows - 1, currentCol()); 519 } 520 521 Keys.onPressed: event => { 522 if (event.key === Qt.Key_Tab) { 523 event.accepted = true; 524 525 if (globalFavoritesGrid.enabled) { 526 globalFavoritesGrid.tryActivate(0, 0); 527 } else if (tabBar.visible) { 528 tabBar.focus = true; 529 } else if (root.searching && !runnerModel.count) { 530 cancelSearchButton.focus = true; 531 } else { 532 mainColumn.tryActivate(0, 0); 533 } 534 } else if (event.key === Qt.Key_Backtab) { 535 event.accepted = true; 536 537 if (filterList.enabled) { 538 filterList.forceActiveFocus(); 539 } else if (root.searching && !runnerModel.count) { 540 cancelSearchButton.focus = true; 541 } else { 542 mainColumn.tryActivate(0, 0); 543 } 544 } 545 } 546 } 547 } 548 549 Item { 550 id: mainColumn 551 552 anchors.top: parent.top 553 554 width: (columns * root.cellSize) + PlasmaCore.Units.gridUnit 555 height: Math.floor(parent.height / root.cellSize) * root.cellSize + mainGridContainer.headerHeight 556 557 property int columns: root.columns - favoritesColumn.columns - filterListColumn.columns 558 property Item visibleGrid: mainGrid 559 560 function tryActivate(row, col) { 561 if (visibleGrid) { 562 visibleGrid.tryActivate(row, col); 563 } 564 } 565 566 Item { 567 id: mainGridContainer 568 569 anchors.fill: parent 570 z: (opacity == 1.0) ? 1 : 0 571 572 visible: opacity != 0.0 573 574 property int headerHeight: mainColumnLabel.height + mainColumnLabelUnderline.height + PlasmaCore.Units.largeSpacing 575 576 opacity: { 577 if (tabBar.activeTab == 0 && root.searching) { 578 return 0.0; 579 } 580 581 if (filterList.allApps) { 582 return 0.0; 583 } 584 585 return 1.0; 586 } 587 588 onOpacityChanged: { 589 if (opacity == 1.0) { 590 mainColumn.visibleGrid = mainGrid; 591 } 592 } 593 594 PlasmaExtras.Heading { 595 id: mainColumnLabel 596 597 anchors { 598 top: parent.top 599 } 600 601 x: PlasmaCore.Units.smallSpacing 602 width: parent.width - x 603 604 elide: Text.ElideRight 605 wrapMode: Text.NoWrap 606 opacity: 1.0 607 608 color: "white" 609 610 level: 1 611 612 text: (tabBar.activeTab == 0) ? funnelModel.description : i18n("Widgets") 613 } 614 615 PlasmaCore.SvgItem { 616 id: mainColumnLabelUnderline 617 618 visible: mainGrid.count 619 620 anchors { 621 top: mainColumnLabel.bottom 622 } 623 624 width: parent.width - PlasmaCore.Units.gridUnit 625 height: lineSvg.horLineHeight 626 627 svg: lineSvg 628 elementId: "horizontal-line" 629 } 630 631 ItemGridView { 632 id: mainGrid 633 634 anchors { 635 top: mainColumnLabelUnderline.bottom 636 topMargin: PlasmaCore.Units.largeSpacing 637 } 638 639 width: parent.width 640 height: systemFavoritesGrid.y + systemFavoritesGrid.height - mainGridContainer.headerHeight 641 642 cellWidth: (tabBar.activeTab == 0 ? root.cellSize : root.cellSize * 2) 643 cellHeight: cellWidth 644 iconSize: (tabBar.activeTab == 0 ? root.iconSize : cellWidth - (PlasmaCore.Units.largeSpacing * 2)) 645 646 model: funnelModel 647 648 onCurrentIndexChanged: { 649 preloadAllAppsTimer.defer(); 650 } 651 652 onKeyNavLeft: { 653 if (tabBar.activeTab == 0) { 654 var row = currentRow(); 655 var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; 656 var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; 657 target.tryActivate(targetRow, favoritesColumn.columns - 1); 658 } 659 } 660 661 onKeyNavRight: { 662 filterListScrollArea.focus = true; 663 } 664 665 onKeyNavUp: { 666 if (tabBar.visible) { 667 tabBar.focus = true; 668 } 669 } 670 671 onItemActivated: { 672 if (tabBar.activeTab == 1) { 673 containmentInterface.ensureMutable(containmentInterface.screenContainment(plasmoid)); 674 root.widgetExplorer.addApplet(currentItem.m.pluginName); 675 root.toggle(); 676 kwindowsystem.showingDesktop = true; 677 } 678 } 679 } 680 } 681 682 ItemMultiGridView { 683 id: allAppsGrid 684 685 anchors { 686 top: parent.top 687 } 688 689 z: (opacity == 1.0) ? 1 : 0 690 width: parent.width 691 height: systemFavoritesGrid.y + systemFavoritesGrid.height 692 693 visible: opacity != 0.0 694 695 opacity: filterList.allApps ? 1.0 : 0.0 696 697 onOpacityChanged: { 698 if (opacity == 1.0) { 699 allAppsGrid.flickableItem.contentY = 0; 700 mainColumn.visibleGrid = allAppsGrid; 701 } 702 } 703 704 onKeyNavLeft: { 705 var row = 0; 706 707 for (var i = 0; i < subGridIndex; i++) { 708 row += subGridAt(i).lastRow() + 2; // Header counts as one. 709 } 710 711 row += subGridAt(subGridIndex).currentRow(); 712 713 var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; 714 var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; 715 target.tryActivate(targetRow, favoritesColumn.columns - 1); 716 } 717 718 onKeyNavRight: { 719 filterListScrollArea.focus = true; 720 } 721 } 722 723 ItemMultiGridView { 724 id: runnerGrid 725 726 anchors { 727 top: parent.top 728 } 729 730 z: (opacity == 1.0) ? 1 : 0 731 width: parent.width 732 height: Math.min(implicitHeight, systemFavoritesGrid.y + systemFavoritesGrid.height) 733 734 visible: opacity != 0.0 735 736 model: runnerModel 737 738 grabFocus: true 739 740 opacity: (tabBar.activeTab == 0 && root.searching) ? 1.0 : 0.0 741 742 onOpacityChanged: { 743 if (opacity == 1.0) { 744 mainColumn.visibleGrid = runnerGrid; 745 } 746 } 747 748 onKeyNavLeft: { 749 var row = 0; 750 751 for (var i = 0; i < subGridIndex; i++) { 752 row += subGridAt(i).lastRow() + 2; // Header counts as one. 753 } 754 755 row += subGridAt(subGridIndex).currentRow(); 756 757 var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; 758 var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; 759 target.tryActivate(targetRow, favoritesColumn.columns - 1); 760 } 761 } 762 763 Keys.onPressed: event => { 764 if (event.key === Qt.Key_Tab) { 765 event.accepted = true; 766 767 if (filterList.enabled) { 768 filterList.forceActiveFocus(); 769 } else { 770 systemFavoritesGrid.tryActivate(0, 0); 771 } 772 } else if (event.key === Qt.Key_Backtab) { 773 event.accepted = true; 774 775 if (root.searching) { 776 cancelSearchButton.focus = true; 777 } else if (tabBar.visible) { 778 tabBar.focus = true; 779 } else if (globalFavoritesGrid.enabled) { 780 globalFavoritesGrid.tryActivate(0, 0); 781 } else { 782 systemFavoritesGrid.tryActivate(0, 0); 783 } 784 } 785 } 786 } 787 788 Item { 789 id: filterListColumn 790 791 anchors { 792 top: parent.top 793 topMargin: mainColumnLabelUnderline.y + mainColumnLabelUnderline.height + PlasmaCore.Units.largeSpacing 794 bottom: parent.bottom 795 } 796 797 width: columns * root.cellSize 798 799 property int columns: 3 800 801 PlasmaExtras.ScrollArea { 802 id: filterListScrollArea 803 804 x: root.visible ? 0 : PlasmaCore.Units.gridUnit 805 806 Behavior on x { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 807 808 width: parent.width 809 height: mainGrid.height 810 811 enabled: !root.searching 812 813 property alias currentIndex: filterList.currentIndex 814 815 opacity: root.visible ? (root.searching ? 0.30 : 1.0) : 0.3 816 817 Behavior on opacity { SmoothedAnimation { duration: PlasmaCore.Units.longDuration; velocity: 0.01 } } 818 819 verticalScrollBarPolicy: (opacity == 1.0) ? Qt.ScrollBarAsNeeded : Qt.ScrollBarAlwaysOff 820 821 onEnabledChanged: { 822 if (!enabled) { 823 filterList.currentIndex = -1; 824 } 825 } 826 827 onCurrentIndexChanged: { 828 focus = (currentIndex != -1); 829 } 830 831 ListView { 832 id: filterList 833 834 focus: true 835 836 property bool allApps: false 837 property int eligibleWidth: width 838 property int hItemMargins: Math.max(highlightItemSvg.margins.left + highlightItemSvg.margins.right, 839 listItemSvg.margins.left + listItemSvg.margins.right) 840 841 model: rootModel 842 843 boundsBehavior: Flickable.StopAtBounds 844 snapMode: ListView.SnapToItem 845 spacing: 0 846 keyNavigationWraps: true 847 848 delegate: MouseArea { 849 id: item 850 851 signal actionTriggered(string actionId, variant actionArgument) 852 signal aboutToShowActionMenu(variant actionMenu) 853 854 property var m: model 855 property int textWidth: label.contentWidth 856 property int mouseCol 857 property bool hasActionList: ((model.favoriteId !== null) 858 || (("hasActionList" in model) && (model.hasActionList === true))) 859 property Item menu: actionMenu 860 861 width: parent.width 862 height: Math.ceil((label.paintedHeight 863 + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 864 listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 865 866 Accessible.role: Accessible.MenuItem 867 Accessible.name: model.display 868 869 acceptedButtons: Qt.LeftButton | Qt.RightButton 870 871 hoverEnabled: true 872 873 onContainsMouseChanged: { 874 if (!containsMouse) { 875 updateCurrentItemTimer.stop(); 876 } 877 } 878 879 onPositionChanged: mouse => { // Lazy menu implementation. 880 mouseCol = mouse.x; 881 882 if (justOpenedTimer.running || ListView.view.currentIndex === 0 || index === ListView.view.currentIndex) { 883 updateCurrentItem(); 884 } else if ((index == ListView.view.currentIndex - 1) && mouse.y < (height - 6) 885 || (index == ListView.view.currentIndex + 1) && mouse.y > 5) { 886 887 if (mouse.x > ListView.view.eligibleWidth - 5) { 888 updateCurrentItem(); 889 } 890 } else if (mouse.x > ListView.view.eligibleWidth) { 891 updateCurrentItem(); 892 } 893 894 updateCurrentItemTimer.restart(); 895 } 896 897 onPressed: mouse => { 898 if (mouse.buttons & Qt.RightButton) { 899 if (hasActionList) { 900 openActionMenu(item, mouse.x, mouse.y); 901 } 902 } 903 } 904 905 onClicked: mouse => { 906 if (mouse.button == Qt.LeftButton) { 907 updateCurrentItem(); 908 } 909 } 910 911 onAboutToShowActionMenu: { 912 var actionList = hasActionList ? model.actionList : []; 913 Tools.fillActionMenu(i18n, actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId); 914 } 915 916 onActionTriggered: { 917 if (Tools.triggerAction(ListView.view.model, model.index, actionId, actionArgument) === true) { 918 plasmoid.expanded = false; 919 } 920 } 921 922 function openActionMenu(visualParent, x, y) { 923 aboutToShowActionMenu(actionMenu); 924 actionMenu.visualParent = visualParent; 925 actionMenu.open(x, y); 926 } 927 928 function updateCurrentItem() { 929 ListView.view.currentIndex = index; 930 ListView.view.eligibleWidth = Math.min(width, mouseCol); 931 } 932 933 ActionMenu { 934 id: actionMenu 935 936 onActionClicked: { 937 actionTriggered(actionId, actionArgument); 938 } 939 } 940 941 Timer { 942 id: updateCurrentItemTimer 943 944 interval: 50 945 repeat: false 946 947 onTriggered: parent.updateCurrentItem() 948 } 949 950 PlasmaExtras.Heading { 951 id: label 952 953 anchors { 954 fill: parent 955 leftMargin: highlightItemSvg.margins.left 956 rightMargin: highlightItemSvg.margins.right 957 } 958 959 elide: Text.ElideRight 960 wrapMode: Text.NoWrap 961 opacity: 1.0 962 963 color: "white" 964 965 level: 1 966 967 text: model.display 968 } 969 } 970 971 highlight: PlasmaComponents.Highlight { 972 anchors { 973 top: filterList.currentItem ? filterList.currentItem.top : undefined 974 left: filterList.currentItem ? filterList.currentItem.left : undefined 975 bottom: filterList.currentItem ? filterList.currentItem.bottom : undefined 976 } 977 978 opacity: filterListScrollArea.focus ? 1.0 : 0.7 979 980 width: (highlightItemSvg.margins.left 981 + filterList.currentItem.textWidth 982 + highlightItemSvg.margins.right 983 + PlasmaCore.Units.smallSpacing) 984 985 visible: filterList.currentItem 986 } 987 988 highlightFollowsCurrentItem: false 989 highlightMoveDuration: 0 990 highlightResizeDuration: 0 991 992 onCurrentIndexChanged: applyFilter() 993 994 onCountChanged: { 995 var width = 0; 996 997 for (var i = 0; i < rootModel.count; ++i) { 998 headingMetrics.text = rootModel.labelForRow(i); 999 1000 if (headingMetrics.width > width) { 1001 width = headingMetrics.width; 1002 } 1003 } 1004 1005 filterListColumn.columns = Math.ceil(width / root.cellSize); 1006 filterListScrollArea.width = width + hItemMargins + (PlasmaCore.Units.gridUnit * 2); 1007 } 1008 1009 function applyFilter() { 1010 if (!root.searching && currentIndex >= 0) { 1011 if (tabBar.activeTab == 1) { 1012 root.widgetExplorer.widgetsModel.filterQuery = currentItem.m.filterData; 1013 root.widgetExplorer.widgetsModel.filterType = currentItem.m.filterType; 1014 1015 allApps = false; 1016 funnelModel.sourceModel = model; 1017 1018 return; 1019 } 1020 1021 if (preloadAllAppsTimer.running) { 1022 preloadAllAppsTimer.stop(); 1023 } 1024 1025 var model = rootModel.modelForRow(currentIndex); 1026 1027 if (model.description === "KICKER_ALL_MODEL") { 1028 allAppsGrid.model = model; 1029 allApps = true; 1030 funnelModel.sourceModel = null; 1031 preloadAllAppsTimer.done = true; 1032 } else { 1033 funnelModel.sourceModel = model; 1034 allApps = false; 1035 } 1036 } else { 1037 funnelModel.sourceModel = null; 1038 allApps = false; 1039 } 1040 } 1041 1042 Keys.onPressed: event => { 1043 if (event.key === Qt.Key_Left) { 1044 event.accepted = true; 1045 1046 var currentRow = Math.max(0, Math.ceil(currentItem.y / mainGrid.cellHeight) - 1); 1047 mainColumn.tryActivate(currentRow, mainColumn.columns - 1); 1048 } else if (event.key === Qt.Key_Tab) { 1049 event.accepted = true; 1050 systemFavoritesGrid.tryActivate(0, 0); 1051 } else if (event.key === Qt.Key_Backtab) { 1052 event.accepted = true; 1053 mainColumn.tryActivate(0, 0); 1054 } 1055 } 1056 } 1057 } 1058 } 1059 } 1060 1061 onPressed: mouse => { 1062 if (mouse.button == Qt.RightButton) { 1063 contextMenu.open(mouse.x, mouse.y); 1064 } 1065 } 1066 1067 onClicked: mouse => { 1068 if (mouse.button == Qt.LeftButton) { 1069 root.toggle(); 1070 } 1071 } 1072 } 1073} 1074