2021-01-03 13:18:29 +03:00
/ *
Copyright ( C ) 2011 Martin Gräßlin < mgraesslin @ kde . org >
Copyright ( C ) 2012 Gregor Taetzner < gregor @ freenet . de >
Copyright ( C ) 2012 Marco Martin < mart @ kde . org >
Copyright ( C ) 2013 2014 David Edmundson < davidedmundson @ kde . org >
Copyright 2014 Sebastian Kügler < sebas @ kde . org >
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License along
with this program ; if not , write to the Free Software Foundation , Inc . ,
51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
* /
import QtQuick 2.3
import org . kde . plasma . plasmoid 2.0
import QtQuick . Layouts 1.1
2024-03-13 02:50:37 +01:00
import org . kde . plasma . plasmoid as PlasmaCore
2024-03-13 00:25:12 +01:00
import org . kde . ksvg as KSvg
2024-03-12 23:46:23 +01:00
import org . kde . plasma . plasma5support as Plasma5Support
2024-03-13 02:50:37 +01:00
import org . kde . kirigami 2.20 as Kirigami
import org . kde . plasma . components 3.0 as PlasmaComponents
import org . kde . plasma . extras 2.0 as PlasmaExtras
2021-01-03 13:18:29 +03:00
import org . kde . kquickcontrolsaddons 2.0
import org . kde . plasma . private . kicker 0.1 as Kicker
Item {
id: root
2024-03-12 23:17:09 +01:00
Layout.minimumWidth: Kirigami . Units . gridUnit * 26
2021-01-03 13:18:29 +03:00
Layout.maximumWidth: Layout . minimumWidth
2024-03-12 23:17:09 +01:00
Layout.minimumHeight: Kirigami . Units . gridUnit * 34
2021-01-03 13:18:29 +03:00
Layout.maximumHeight: Layout . minimumHeight
property string previousState
property bool switchTabsOnHover: plasmoid . configuration . switchTabsOnHover
property Item currentView: mainTabGroup . currentTab . decrementCurrentIndex ? mainTabGroup.currentTab : mainTabGroup . currentTab . item
property KickoffButton firstButton: null
property var configMenuItems
property QtObject globalFavorites: rootModelFavorites
state: "Normal"
onFocusChanged: {
header . input . forceActiveFocus ( ) ;
}
function switchToInitial ( ) {
if ( firstButton != null ) {
root . state = "Normal" ;
mainTabGroup . currentTab = firstButton . tab ;
tabBar . currentTab = firstButton ;
header . query = ""
}
}
Kicker . DragHelper {
id: dragHelper
2024-03-12 23:17:09 +01:00
dragIconSize: Kirigami . Units . iconSizes . medium
2021-01-03 13:18:29 +03:00
onDropped: kickoff . dragSource = null
}
Kicker . AppsModel {
id: rootModel
autoPopulate: false
appletInterface: plasmoid
appNameFormat: plasmoid . configuration . showAppsByName ? 0 : 1
flat: false
sorted: plasmoid . configuration . alphaSort
showSeparators: false
showTopLevelItems: true
favoritesModel: Kicker . KAStatsFavoritesModel {
id: rootModelFavorites
favorites: plasmoid . configuration . favorites
onFavoritesChanged: {
plasmoid . configuration . favorites = favorites ;
}
}
Component.onCompleted: {
favoritesModel . initForClient ( "org.kde.plasma.kickoff.favorites.instance-" + plasmoid . id )
if ( ! plasmoid . configuration . favoritesPortedToKAstats ) {
favoritesModel . portOldFavorites ( plasmoid . configuration . favorites ) ;
plasmoid . configuration . favoritesPortedToKAstats = true ;
}
rootModel . refresh ( ) ;
}
}
2024-03-12 23:46:23 +01:00
Plasma5Support . DataSource {
2021-01-03 13:18:29 +03:00
id: pmSource
engine: "powermanagement"
connectedSources: [ "PowerDevil" ]
}
2024-03-13 00:25:12 +01:00
KSvg . Svg {
2021-01-03 13:18:29 +03:00
id: arrowsSvg
imagePath: "widgets/arrows"
size: "16x16"
}
Header {
id: header
}
Item {
id: mainArea
2024-03-12 23:17:09 +01:00
anchors.topMargin: mainTabGroup . state == "top" ? Kirigami.Units.smallSpacing : 0
2021-01-03 13:18:29 +03:00
2024-03-13 02:50:37 +01:00
Kirigami . Page {
id: mainPage
2021-01-03 13:18:29 +03:00
2024-03-13 02:50:37 +01:00
// Set anchors
anchors.fill: parent
// Create a row for the tab buttons
Kirigami . PageRow {
id: tabRow
// Set row orientation based on plasmoid location
orientation: {
switch ( plasmoid . location ) {
case PlasmaCore.Types.LeftEdge:
case PlasmaCore.Types.RightEdge:
return Qt . Horizontal
default:
return Qt . Vertical
}
}
// Add tab buttons
PlasmaComponents . Button {
text: i18n ( "Favorites" )
onClicked: mainPage . currentIndex = 0
}
PlasmaComponents . Button {
text: i18n ( "Applications" )
onClicked: mainPage . currentIndex = 1
}
PlasmaComponents . Button {
text: i18n ( "Computer" )
onClicked: mainPage . currentIndex = 2
}
PlasmaComponents . Button {
text: i18n ( "Recently Used" )
onClicked: mainPage . currentIndex = 3
}
PlasmaComponents . Button {
text: i18n ( "Often Used" )
onClicked: mainPage . currentIndex = 4
}
PlasmaComponents . Button {
text: i18n ( "Leave" )
onClicked: mainPage . currentIndex = 5
}
PlasmaComponents . Button {
text: i18n ( "Search" )
onClicked: root . state = "Search"
}
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
// Add pages
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: favoritesPage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: mainPage . currentIndex === 0
// Add contents of the Favorites view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: applicationsPage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: mainPage . currentIndex === 1
// Add contents of the Applications view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
id: computerPage
anchors.fill: parent
visible: mainPage . currentIndex === 2
// Add contents of the Computer view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: recentlyUsedPage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: mainPage . currentIndex === 3
// Add contents of the Recently Used view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: oftenUsedPage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: mainPage . currentIndex === 4
// Add contents of the Often Used view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: leavePage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: mainPage . currentIndex === 5
// Add contents of the Leave view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
Kirigami . Page {
2021-01-03 13:18:29 +03:00
id: searchPage
2024-03-13 02:50:37 +01:00
anchors.fill: parent
visible: root . state === "Search"
// Add contents of the Search view here
2021-01-03 13:18:29 +03:00
}
2024-03-13 02:50:37 +01:00
// Handle the plasmoid location states
2021-01-03 13:18:29 +03:00
states: [
State {
name: "left"
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
rotation: 0
2021-01-03 13:18:29 +03:00
}
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
2021-01-03 13:18:29 +03:00
anchors {
2024-03-13 02:50:37 +01:00
left: parent . left
top: parent . top
bottom: parent . bottom
2021-01-03 13:18:29 +03:00
}
}
} ,
State {
name: "top"
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
rotation: - 90
2021-01-03 13:18:29 +03:00
}
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
2021-01-03 13:18:29 +03:00
anchors {
2024-03-13 02:50:37 +01:00
top: parent . top
left: parent . left
right: parent . right
2021-01-03 13:18:29 +03:00
}
}
} ,
State {
name: "right"
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
rotation: 180
2021-01-03 13:18:29 +03:00
}
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
2021-01-03 13:18:29 +03:00
anchors {
2024-03-13 02:50:37 +01:00
right: parent . right
top: parent . top
bottom: parent . bottom
2021-01-03 13:18:29 +03:00
}
}
} ,
State {
name: "bottom"
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
rotation: 90
2021-01-03 13:18:29 +03:00
}
PropertyChanges {
2024-03-13 02:50:37 +01:00
target: tabRow
2021-01-03 13:18:29 +03:00
anchors {
2024-03-13 02:50:37 +01:00
bottom: parent . bottom
left: parent . left
right: parent . right
2021-01-03 13:18:29 +03:00
}
}
}
]
2024-03-13 02:50:37 +01:00
// Set initial plasmoid state
state: {
switch ( plasmoid . location ) {
case PlasmaCore.Types.LeftEdge:
return "left"
case PlasmaCore.Types.TopEdge:
return "top"
case PlasmaCore.Types.RightEdge:
return "right"
case PlasmaCore.Types.BottomEdge:
default:
return "bottom"
}
}
}
2021-01-03 13:18:29 +03:00
}
PlasmaComponents . TabBar {
id: tabBar
property int count: 5 // updated in createButtons()
Behavior on width {
2024-03-12 23:17:09 +01:00
NumberAnimation { duration: Kirigami . Units . longDuration ; easing.type: Easing . InQuad ; }
2021-01-03 13:18:29 +03:00
enabled: plasmoid . expanded
}
Behavior on height {
2024-03-12 23:17:09 +01:00
NumberAnimation { duration: Kirigami . Units . longDuration ; easing.type: Easing . InQuad ; }
2021-01-03 13:18:29 +03:00
enabled: plasmoid . expanded
}
tabPosition: {
switch ( plasmoid . location ) {
case PlasmaCore.Types.TopEdge:
return Qt . TopEdge ;
case PlasmaCore.Types.LeftEdge:
return Qt . LeftEdge ;
case PlasmaCore.Types.RightEdge:
return Qt . RightEdge ;
default:
return Qt . BottomEdge ;
}
}
onCurrentTabChanged: header . input . forceActiveFocus ( ) ;
Connections {
target: plasmoid
function onExpandedChanged ( ) {
if ( menuItemsChanged ( ) ) {
createButtons ( ) ;
}
if ( ! plasmoid . expanded ) {
switchToInitial ( ) ;
}
}
}
} // tabBar
2024-03-13 00:25:12 +01:00
KSvg . SvgItem {
2021-01-03 13:18:29 +03:00
id: tabBarSeparator
2024-03-13 00:25:12 +01:00
svg: KSvg . Svg {
2021-01-03 13:18:29 +03:00
id: tabBarSeparatorLine
imagePath: "widgets/line"
}
}
MouseArea {
anchors.fill: tabBar
property var oldPos: null
enabled: root . state !== "Search"
hoverEnabled: root . switchTabsOnHover
onExited: {
// Reset so we switch immediately when MouseArea is entered
// freshly, e.g. from the panel.
oldPos = null ;
clickTimer . stop ( ) ;
}
onPositionChanged: {
// Reject multiple events with the same coordinates that QQuickWindow
// synthesizes.
if ( oldPos === Qt . point ( mouse . x , mouse . y ) ) {
return ;
}
var button = tabBar . layout . childAt ( mouse . x , mouse . y ) ;
if ( ! button || button . objectName !== "KickoffButton" ) {
clickTimer . stop ( ) ;
return ;
}
// Switch immediately when MouseArea was freshly entered, e.g.
// from the panel.
if ( oldPos === null ) {
oldPos = Qt . point ( mouse . x , mouse . y ) ;
clickTimer . stop ( ) ;
button . clicked ( ) ;
return ;
}
var dx = ( mouse . x - oldPos . x ) ;
var dy = ( mouse . y - oldPos . y ) ;
// Check Manhattan length against drag distance to get a decent
// pointer motion vector.
if ( ( Math . abs ( dx ) + Math . abs ( dy ) ) > Qt . styleHints . startDragDistance ) {
if ( tabBar . currentTab !== button ) {
var tabBarPos = mapToItem ( tabBar , oldPos . x , oldPos . y ) ;
oldPos = Qt . point ( mouse . x , mouse . y ) ;
var angleMouseMove = Math . atan2 ( dy , dx ) * 180 / Math . PI ;
var angleToCornerA = 0 ;
var angleToCornerB = 0 ;
switch ( plasmoid . location ) {
case PlasmaCore.Types.TopEdge: {
angleToCornerA = Math . atan2 ( tabBar . height - tabBarPos . y , 0 - tabBarPos . x ) ;
angleToCornerB = Math . atan2 ( tabBar . height - tabBarPos . y , tabBar . width - tabBarPos . x ) ;
break ;
}
case PlasmaCore.Types.LeftEdge: {
angleToCornerA = Math . atan2 ( 0 - tabBarPos . y , tabBar . width - tabBarPos . x ) ;
angleToCornerB = Math . atan2 ( tabBar . height - tabBarPos . y , tabBar . width - tabBarPos . x ) ;
break ;
}
case PlasmaCore.Types.RightEdge: {
angleToCornerA = Math . atan2 ( 0 - tabBarPos . y , 0 - tabBarPos . x ) ;
angleToCornerB = Math . atan2 ( tabBar . height - tabBarPos . y , 0 - tabBarPos . x ) ;
break ;
}
// PlasmaCore.Types.BottomEdge
default: {
angleToCornerA = Math . atan2 ( 0 - tabBarPos . y , 0 - tabBarPos . x ) ;
angleToCornerB = Math . atan2 ( 0 - tabBarPos . y , tabBar . width - tabBarPos . x ) ;
}
}
// Degrees are nicer to debug than radians.
angleToCornerA = angleToCornerA * 180 / Math . PI ;
angleToCornerB = angleToCornerB * 180 / Math . PI ;
var lower = Math . min ( angleToCornerA , angleToCornerB ) ;
var upper = Math . max ( angleToCornerA , angleToCornerB ) ;
// If the motion vector is outside the angle range from oldPos to the
// relevant tab bar corners, switch immediately. Otherwise start the
// timer, which gets aborted should the pointer exit the tab bar
// early.
var inRange = ( lower < angleMouseMove == angleMouseMove < upper ) ;
// Mirror-flip.
if ( plasmoid . location === PlasmaCore . Types . RightEdge ? inRange : ! inRange ) {
clickTimer . stop ( ) ;
button . clicked ( ) ;
return ;
} else {
clickTimer . pendingButton = button ;
clickTimer . start ( ) ;
}
} else {
oldPos = Qt . point ( mouse . x , mouse . y ) ;
}
}
}
onClicked: {
clickTimer . stop ( ) ;
var button = tabBar . layout . childAt ( mouse . x , mouse . y ) ;
if ( ! button || button . objectName !== "KickoffButton" ) {
return ;
}
button . clicked ( ) ;
}
Timer {
id: clickTimer
property Item pendingButton: null
interval: 250
onTriggered: {
if ( pendingButton ) {
pendingButton . clicked ( ) ;
}
}
}
}
Keys.forwardTo: [ tabBar . layout ]
Keys.onPressed: {
if ( mainTabGroup . currentTab == applicationsPage ) {
if ( event . key !== Qt . Key_Tab ) {
root . state = "Applications" ;
}
}
switch ( event . key ) {
case Qt.Key_Up: {
currentView . decrementCurrentIndex ( ) ;
event . accepted = true ;
break ;
}
case Qt.Key_Down: {
currentView . incrementCurrentIndex ( ) ;
event . accepted = true ;
break ;
}
case Qt.Key_Left: {
if ( header . input . focus && header . state == "query" ) {
break ;
}
if ( ! currentView . deactivateCurrentIndex ( ) ) {
if ( root . state == "Applications" ) {
mainTabGroup . currentTab = firstButton . tab ;
tabBar . currentTab = firstButton ;
}
root . state = "Normal"
}
event . accepted = true ;
break ;
}
case Qt.Key_Right: {
if ( header . input . focus && header . state == "query" ) {
break ;
}
currentView . activateCurrentIndex ( ) ;
event . accepted = true ;
break ;
}
case Qt.Key_Tab: {
root . state == "Applications" ? root . state = "Normal" : root . state = "Applications" ;
event . accepted = true ;
break ;
}
case Qt.Key_Enter:
case Qt.Key_Return: {
currentView . activateCurrentIndex ( 1 ) ;
event . accepted = true ;
break ;
}
case Qt.Key_Escape: {
if ( header . state != "query" ) {
plasmoid . expanded = false ;
} else {
header . query = "" ;
}
event . accepted = true ;
break ;
}
case Qt.Key_Menu: {
currentView . openContextMenu ( ) ;
event . accepted = true ;
break ;
}
default:
if ( ! header . input . focus ) {
header . input . forceActiveFocus ( ) ;
}
}
}
states: [
State {
name: "Normal"
PropertyChanges {
target: root
Keys.forwardTo: [ tabBar . layout ]
}
PropertyChanges {
target: tabBar
//Set the opacity and NOT the visibility, as visibility is recursive
//and this binding would be executed also on popup show/hide
//as recommended by the docs: https://doc.qt.io/qt-5/qml-qtquick-item.html#visible-prop
//plus, it triggers https://bugreports.qt.io/browse/QTBUG-66907
//in which a mousearea may think it's under the mouse while it isn't
opacity: tabBar . count > 1 ? 1 : 0
}
} ,
State {
name: "Applications"
PropertyChanges {
target: root
Keys.forwardTo: [ root ]
}
PropertyChanges {
target: tabBar
opacity: tabBar . count > 1 ? 1 : 0
}
} ,
State {
name: "Search"
PropertyChanges {
target: tabBar
opacity: 0
}
PropertyChanges {
target: mainTabGroup
currentTab: searchPage
}
PropertyChanges {
target: root
Keys.forwardTo: [ root ]
}
}
] // states
function getButtonDefinition ( name ) {
switch ( name ) {
case "bookmark" :
return { id: "bookmarkButton" , tab: favoritesPage , iconSource: "bookmarks" , text: i18n ( "Favorites" ) } ;
case "application" :
return { id: "applicationButton" , tab: applicationsPage , iconSource: "applications-other" , text: i18n ( "Applications" ) } ;
case "computer" :
return { id: "computerButton" , tab: systemPage , iconSource: pmSource . data [ "PowerDevil" ] && pmSource . data [ "PowerDevil" ] [ "Is Lid Present" ] ? "computer-laptop" : "computer" , text: i18n ( "Computer" ) } ;
case "used" :
return { id: "usedButton" , tab: recentlyUsedPage , iconSource: "view-history" , text: i18n ( "History" ) } ;
case "oftenUsed" :
return { id: "usedButton" , tab: oftenUsedPage , iconSource: "office-chart-pie" , text: i18n ( "Often Used" ) } ;
case "leave" :
return { id: "leaveButton" , tab: leavePage , iconSource: "system-log-out" , text: i18n ( "Leave" ) } ;
}
}
Component {
id: kickoffButton
KickoffButton { }
}
Component.onCompleted: {
createButtons ( ) ;
}
function getEnabled ( configuration ) {
var res = [ ] ;
for ( var i = 0 ; i < configuration . length ; i ++ ) {
var confItemName = configuration [ i ] . substring ( 0 , configuration [ i ] . indexOf ( ":" ) ) ;
var confItemEnabled = configuration [ i ] . substring ( configuration [ i ] . length - 1 ) === "t" ;
if ( confItemEnabled ) {
res . push ( confItemName ) ;
}
}
return res ;
}
function createButtons ( ) {
configMenuItems = plasmoid . configuration . menuItems ;
var menuItems = getEnabled ( plasmoid . configuration . menuItems ) ;
tabBar . count = menuItems . length
// remove old menu items
for ( var i = tabBar . layout . children . length - 1 ; i >= 0 ; i -- ) {
if ( tabBar . layout . children [ i ] . objectName === "KickoffButton" ) {
tabBar . layout . children [ i ] . destroy ( ) ;
}
}
for ( var i = 0 ; i < menuItems . length ; i ++ ) {
var props = getButtonDefinition ( menuItems [ i ] ) ;
var button = kickoffButton . createObject ( tabBar . layout , props ) ;
if ( i === 0 ) {
firstButton = button ;
switchToInitial ( ) ;
}
}
}
function menuItemsChanged ( ) {
if ( configMenuItems . length !== plasmoid . configuration . menuItems . length ) {
return true ;
}
for ( var i = 0 ; i < configMenuItems . length ; i ++ ) {
if ( configMenuItems [ i ] !== plasmoid . configuration . menuItems [ i ] ) {
return true ;
}
}
return false ;
}
}