diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt --- a/applets/CMakeLists.txt +++ b/applets/CMakeLists.txt @@ -3,6 +3,8 @@ add_subdirectory(trash) # tasks and windowlist depend on libs/legacytaskmanager +add_subdirectory(taskmanager) +add_subdirectory(icontasks) add_subdirectory(legacytaskmanager) add_subdirectory(legacyicontasks) plasma_install_package(window-list org.kde.plasma.windowlist) diff --git a/applets/icontasks/CMakeLists.txt b/applets/icontasks/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/applets/icontasks/CMakeLists.txt @@ -0,0 +1,2 @@ +install(FILES metadata.desktop DESTINATION ${SERVICES_INSTALL_DIR} RENAME plasma-applet-org.kde.plasma.icontasks.desktop) +install(FILES metadata.desktop DESTINATION ${PLASMA_DATA_INSTALL_DIR}/plasmoids/org.kde.plasma.icontasks ) diff --git a/applets/icontasks/metadata.desktop b/applets/icontasks/metadata.desktop new file mode 100644 --- /dev/null +++ b/applets/icontasks/metadata.desktop @@ -0,0 +1,137 @@ +[Desktop Entry] +Name=Icons-only Task Manager +Name[ar]=مدير مهامّ بأيقونات فقط +Name[ast]=Xestor de xeres de namái iconos +Name[bs]=Ikone-samo Upravitelj Zadacima +Name[ca]=Gestor de tasques només amb icones +Name[ca@valencia]=Gestor de tasques només amb icones +Name[cs]=Správce úloh pouze s ikonami +Name[da]=Opgavelinje kun med ikoner +Name[de]=Fensterleiste nur mit Symbolen +Name[el]=Διαχειριστής εργασιών με εικονίδια μόνο +Name[en_GB]=Icons-only Task Manager +Name[es]=Gestor de tareas con solo iconos +Name[et]=Ainult ikoonidega tegumihaldur +Name[fi]=Kuvake­tehtävienhallinta +Name[fr]=Gestionnaire de tâches par icônes +Name[gl]=Xestor de tarefas baseado en iconas +Name[hu]=Ikonos feladatkezelő +Name[id]=Manajer Tugas Hanya Ikon +Name[is]=Verkefnastjóri einungis með táknmyndum +Name[it]=Gestore dei processi solo icone +Name[ja]=アイコンだけのタスクマネージャ +Name[ko]=아이콘만 있는 작업 관리자 +Name[lt]=Tik ženkliukų užduočių tvarkyklė +Name[mr]=फक्त-चिन्ह कार्य व्यवस्थापक +Name[nb]=Oppgavebehandler med bare ikoner +Name[nds]=Lüttbild-Opgavenpleger +Name[nl]=Takenbeheer met alleen pictogrammen +Name[nn]=Oppgåvehandsamar med berre ikon +Name[pa]=ਕੇਵਲ-ਆਈਕਾਨ ਟਾਸਕ ਮੈਨੇਜਰ +Name[pl]=Przełącznik zadań z ikonami +Name[pt]=Gestor de Tarefas por Ícones +Name[pt_BR]=Gerenciador de tarefas apenas com ícones +Name[ru]=Панель задач (только значки) +Name[sk]=Správca úloh iba s ikonami +Name[sl]=Upravljalnik opravil s samimi ikonami +Name[sr]=менаџер задатака само са иконама +Name[sr@ijekavian]=менаџер задатака само са иконама +Name[sr@ijekavianlatin]=menadžer zadataka samo sa ikonama +Name[sr@latin]=menadžer zadataka samo sa ikonama +Name[sv]=Aktivitetshanterare med bara ikoner +Name[tr]=Sadece-Simge Görev Yöneticisi +Name[uk]=Керування задачами лише за допомогою піктограм +Name[x-test]=xxIcons-only Task Managerxx +Name[zh_CN]=图标任务管理器 +Name[zh_TW]=只有圖示的工作管理員 +Comment=Switch between running applications +Comment[ar]=بدّل بين التطبيقات التي تعمل +Comment[be@latin]=Pieraklučeńnie dziejnych aplikacyjaŭ +Comment[bg]=Превключване между стартирани програми +Comment[bs]=Prebacujte između programa u radu +Comment[ca]=Commuta entre aplicacions en execució +Comment[ca@valencia]=Commuta entre aplicacions en execució +Comment[cs]=Přepínač mezi běžícími aplikacemi +Comment[da]=Skift mellem kørende programmer +Comment[de]=Ermöglicht den wechselnden Zugriff auf laufende Programme. +Comment[el]=Εναλλαγή μεταξύ εκτελούμενων εφαρμογών +Comment[en_GB]=Switch between running applications +Comment[eo]=Komuti inter funkciantaj aplikaĵoj +Comment[es]=Cambiar entre aplicaciones en ejecución +Comment[et]=Lülitumine töötavate rakenduste vahel +Comment[eu]=Aldatu abian dauden aplikazio batetik bestera +Comment[fi]=Vaihda avoinna olevien ohjelmien välillä +Comment[fr]=Passe d'une application à l'autre +Comment[fy]=Wikselje tusken rinnende programma's +Comment[ga]=Athraigh idir feidhmchláir atá ag rith +Comment[gl]=Troca entre programas en execución +Comment[gu]=ચાલતાં કાર્યક્રમો વચ્ચે ફેરબદલી કરો +Comment[he]=מעבר בין יישומים פעילים +Comment[hi]=चल रहे अनुप्रयोगों के बीच स्विच करें +Comment[hne]=चलत अनुपरयोग मं स्विच करव +Comment[hr]=Promjena među pokrenutim aplikacijama +Comment[hsb]=Šaltuje mjez běžacymi aplikacijemi +Comment[hu]=A futó alkalmazások között lehet vele váltani +Comment[ia]=Commuta inter applicationes que on exeque +Comment[id]=Berpindah di antara aplikasi yang berjalan +Comment[is]=Skipta á milli forrita sem eru í gangi +Comment[it]=Passa ad altre applicazioni in esecuzione +Comment[ja]=実行中のアプリケーションを切り替えます +Comment[kk]=Жегілген қолданбаларды ақтару +Comment[km]=ប្ដូរ​រវាង​កម្មវិធី​ដែល​កំពុង​រត់ +Comment[kn]=ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅನ್ವಯಗಳ ನಡುವೆ ಅಂತರಿಸು +Comment[ko]=실행 중인 프로그램을 전환합니다 +Comment[lt]=Perjungimas tarp veikiančių programų +Comment[lv]=Pārslēdzas starp palaistajām programmām +Comment[mk]=Движете се низ активните апликации +Comment[ml]=പ്രവര്‍ത്തിക്കുന്ന പ്രവര്‍ത്തങ്ങള്‍ തമ്മില്‍ മാറുക +Comment[mr]=कार्यरत अनुप्रयोग अंतर्गत बदल करा +Comment[nb]=Bytt mellom kjørende programmer +Comment[nds]=Twischen lopen Programmen wesseln +Comment[nl]=Schakel tussen draaiende programma's +Comment[nn]=Byt mellom program som køyrer +Comment[or]=ଚାଲୁଥିବା ପ୍ରୟୋଗଗୁଡ଼ିକ ମଧ୍ଯରେ ଅଦଳ ବଦଳ କରନ୍ତୁ +Comment[pa]=ਚੱਲਦੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਬਦਲੋ +Comment[pl]=Przełącza pomiędzy uruchomionymi programami +Comment[pt]=Mudar de aplicações em execução +Comment[pt_BR]=Alterna entre os aplicativos em execução +Comment[ro]=Comută printre aplicațiile ce rulează +Comment[ru]=Переключение между запущенными приложениями +Comment[si]=ක්‍රියාත්මක වන යෙදුම් අතරේ මාරුවන්න +Comment[sk]=Prepínanie medzi bežiacimi aplikáciami +Comment[sl]=Preklapljajte med zagnanimi programi +Comment[sr]=Пребацујте између програма у раду +Comment[sr@ijekavian]=Пребацујте између програма у раду +Comment[sr@ijekavianlatin]=Prebacujte između programa u radu +Comment[sr@latin]=Prebacujte između programa u radu +Comment[sv]=Byt mellan program som kör +Comment[ta]=Switch between running applications +Comment[te]=నడుస్తున్న అనువర్తనముల మద్యన మారుము +Comment[tg]=Переключение между рабочими столами +Comment[th]=สลับการทำงานระหว่างโปรแกรมต่าง ๆ +Comment[tr]=Çalışan uygulamalar arasında gezin +Comment[ug]=ئىجرا بولۇۋاتقان پروگراممىلارنى ئالماشتۇر +Comment[uk]=Перемкніть запущені програми +Comment[vi]=Chuyển đổi giữa các ứng dụng đang chạy +Comment[wa]=Passer d' on programe ovrant a èn ôte +Comment[x-test]=xxSwitch between running applicationsxx +Comment[zh_CN]=在运行中的应用程序间切换 +Comment[zh_TW]=在執行中的應用程式間切換 +Type=Service +Icon=preferences-system-windows +X-KDE-ServiceTypes=Plasma/Applet + +X-Plasma-API=declarativeappletscript +X-Plasma-MainScript=ui/main.qml +X-Plasma-Provides=org.kde.plasma.multitasking +X-Plasma-RootPath=org.kde.plasma.taskmanager + +X-KDE-PluginInfo-Author=Eike Hein +X-KDE-PluginInfo-Email=hein@kde.org +X-KDE-PluginInfo-Name=org.kde.plasma.icontasks +X-KDE-PluginInfo-Version=3.0 +X-KDE-PluginInfo-Website=http://userbase.kde.org/Plasma/Tasks +X-KDE-PluginInfo-Category=Windows and Tasks +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL v2+ +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/applets/taskmanager/CMakeLists.txt b/applets/taskmanager/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/applets/taskmanager/CMakeLists.txt @@ -0,0 +1,35 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.taskmanager\") + +plasma_install_package(package org.kde.plasma.taskmanager) + +set(taskmanagerplugin_SRCS + plugin/backend.cpp + plugin/draghelper.cpp + plugin/taskmanagerplugin.cpp + plugin/textlabel.cpp + + plugin/smartlaunchers/smartlauncherbackend.cpp + plugin/smartlaunchers/smartlauncheritem.cpp +) + +install(FILES plugin/qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/taskmanager) + +add_library(taskmanagerplugin SHARED ${taskmanagerplugin_SRCS}) + +# FIXME Cleanup no longer used libs. +target_link_libraries(taskmanagerplugin + Qt5::Core + Qt5::DBus + Qt5::Qml + Qt5::Quick + Qt5::Widgets + KF5::Activities + KF5::ActivitiesStats + KF5::I18n + KF5::KIOCore + KF5::KIOWidgets + KF5::Plasma + KF5::Service + KF5::WindowSystem) + +install(TARGETS taskmanagerplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/taskmanager) diff --git a/applets/taskmanager/Messages.sh b/applets/taskmanager/Messages.sh new file mode 100644 --- /dev/null +++ b/applets/taskmanager/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.taskmanager.pot +rm -f rc.cpp diff --git a/applets/taskmanager/package/contents/code/layout.js b/applets/taskmanager/package/contents/code/layout.js new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/code/layout.js @@ -0,0 +1,202 @@ +/*************************************************************************** + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +function horizontalMargins() { + return taskFrame.margins.left + taskFrame.margins.right; +} + +function verticalMargins() { + return taskFrame.margins.top + taskFrame.margins.bottom; +} + +function adjustMargin(height, margin) { + var available = height - verticalMargins(); + + if (available < units.iconSizes.small) { + return Math.floor((margin * (units.iconSizes.small / available)) / 3); + } + + return margin; +} + +function launcherLayoutTasks() { + return Math.round(tasksModel.launcherCount / Math.floor(preferredMinWidth() / launcherWidth())); +} + +function launcherLayoutWidthDiff() { + return (launcherLayoutTasks() * taskWidth()) - (tasksModel.launcherCount * launcherWidth()); +} + +function logicalTaskCount() { + var count = (tasksModel.count - tasksModel.launcherCount) + launcherLayoutTasks(); + + return Math.max(tasksModel.count ? 1 : 0, count); +} + +function maxStripes() { + var length = tasks.vertical ? taskList.width : taskList.height; + var minimum = tasks.vertical ? preferredMinWidth() : preferredMinHeight(); + + return Math.min(plasmoid.configuration.maxStripes, Math.max(1, Math.floor(length / minimum))); +} + +function tasksPerStripe() { + if (plasmoid.configuration.forceStripes) { + return Math.ceil(logicalTaskCount() / maxStripes()); + } else { + var length = tasks.vertical ? taskList.height : taskList.width; + var minimum = tasks.vertical ? preferredMinHeight() : preferredMinWidth(); + + return Math.floor(length / minimum); + } +} + +function calculateStripes() { + var stripes = plasmoid.configuration.forceStripes ? plasmoid.configuration.maxStripes : Math.min(plasmoid.configuration.maxStripes, Math.ceil(logicalTaskCount() / tasksPerStripe())); + + return Math.min(stripes, maxStripes()); +} + +function full() { + return (maxStripes() == calculateStripes()); +} + +function optimumCapacity(width, height) { + var length = tasks.vertical ? height : width; + var maximum = tasks.vertical ? preferredMaxHeight() : preferredMaxWidth(); + + return (Math.ceil(length / maximum) * maxStripes()); +} + +function layoutWidth() { + if (plasmoid.configuration.forceStripes && !tasks.vertical) { + return Math.min(tasks.width, Math.max(preferredMaxWidth(), tasksPerStripe() * preferredMaxWidth())); + } else { + return tasks.width; + } +} + +function layoutHeight() { + if (plasmoid.configuration.forceStripes && tasks.vertical) { + return Math.min(tasks.height, Math.max(preferredMaxHeight(), tasksPerStripe() * preferredMaxHeight())); + } else { + return tasks.height; + } +} + +function preferredMinWidth() { + var width = launcherWidth(); + + if (!tasks.vertical && !tasks.iconsOnly) { + width += (units.smallSpacing * 2) + (theme.mSize(theme.defaultFont).width * 12); + } + + return width; +} + +function preferredMaxWidth() { + if (tasks.iconsOnly) { + if (tasks.vertical) { + return tasks.width + verticalMargins(); + } else { + return tasks.height + horizontalMargins(); + } + } else { + return Math.floor(preferredMinWidth() * 1.6); + } +} + +function preferredMinHeight() { + // TODO FIXME UPSTREAM: Port to proper font metrics for descenders once we have access to them. + return theme.mSize(theme.defaultFont).height + 4; +} + +function preferredMaxHeight() { + return verticalMargins() + Math.min(units.iconSizes.small * 3, theme.mSize(theme.defaultFont).height * 3); +} + +function taskWidth() { + if (tasks.vertical) { + return Math.floor(taskList.width / calculateStripes()); + } else { + if (full() && Math.max(1, logicalTaskCount()) > tasksPerStripe()) { + return Math.floor(taskList.width / Math.ceil(logicalTaskCount() / maxStripes())); + } else { + return Math.min(preferredMaxWidth(), Math.floor(taskList.width / Math.min(logicalTaskCount(), tasksPerStripe()))); + } + } +} + +function taskHeight() { + if (tasks.vertical) { + if (full() && Math.max(1, logicalTaskCount()) > tasksPerStripe()) { + return Math.floor(taskList.height / Math.ceil(logicalTaskCount() / maxStripes())); + } else { + return Math.min(preferredMaxHeight(), Math.floor(taskList.height / Math.min(logicalTaskCount(), tasksPerStripe()))); + } + } else { + return Math.floor(taskList.height / calculateStripes()); + } +} + +function launcherWidth() { + var baseWidth = tasks.vertical ? preferredMinHeight() : Math.min(tasks.height, units.iconSizes.small * 3); + + return (baseWidth + horizontalMargins()) + - (adjustMargin(baseWidth, taskFrame.margins.top) + adjustMargin(baseWidth, taskFrame.margins.bottom)); +} + +function layout(container) { + var item; + var stripes = calculateStripes(); + var taskCount = tasksModel.count - tasksModel.launcherCount; + var width = taskWidth(); + var adjustedWidth = width; + var height = taskHeight(); + + if (!tasks.vertical && stripes == 1 && taskCount) + { + var shrink = ((tasksModel.count - tasksModel.launcherCount) * preferredMaxWidth()) + + (tasksModel.launcherCount * launcherWidth()) > taskList.width; + width = Math.min(shrink ? width + Math.floor(launcherLayoutWidthDiff() / taskCount) : width, + preferredMaxWidth()); + } + + for (var i = 0; i < container.count; ++i) { + item = container.itemAt(i); + + if (!item) { + continue; + } + + adjustedWidth = width; + + if (!tasks.vertical && !tasks.iconsOnly) { + if (item.isLauncher) { + adjustedWidth = launcherWidth(); + } else if (stripes > 1 && i == tasksModel.launcherCount) { + adjustedWidth += launcherLayoutWidthDiff(); + } + } + + item.width = adjustedWidth; + item.height = height; + item.visible = true; + } +} diff --git a/applets/taskmanager/package/contents/code/tools.js b/applets/taskmanager/package/contents/code/tools.js new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/code/tools.js @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2012-2016 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +function wheelActivateNextPrevTask(wheelDelta, eventDelta) { + // magic number 120 for common "one click" + // See: http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop + wheelDelta += eventDelta; + var increment = 0; + while (wheelDelta >= 120) { + wheelDelta -= 120; + increment++; + } + while (wheelDelta <= -120) { + wheelDelta += 120; + increment--; + } + while (increment != 0) { + activateNextPrevTask(increment < 0) + increment += (increment < 0) ? 1 : -1; + } + + return wheelDelta; +} + +function activateNextPrevTask(next) { + // FIXME TODO: Unnecessarily convoluted and costly; optimize. + + var taskIndexList = []; + var activeTaskIndex = tasksModel.activeTask(); + + for (var i = 0; i < taskList.children.length - 1; ++i) { + var task = taskList.children[i]; + var modelIndex = task.modelIndex(i); + + if (!task.isLauncher && !task.isStartup) { + if (task.isGroupParent) { + for (var j = 0; j < tasksModel.rowCount(modelIndex); ++j) { + taskIndexList.push(tasksModel.makeModelIndex(i, j)); + } + } else { + taskIndexList.push(modelIndex); + } + } + } + + if (!taskIndexList.length) { + return; + } + + var target = taskIndexList[0]; + + for (var i = 0; i < taskIndexList.length; ++i) { + if (taskIndexList[i] === activeTaskIndex) + { + if (next && i < (taskIndexList.length - 1)) { + target = taskIndexList[i + 1]; + } else if (!next) { + if (i) { + target = taskIndexList[i - 1]; + } else { + target = taskIndexList[taskIndexList.length - 1]; + } + } + + break; + } + } + + tasksModel.requestActivate(target); +} + +function insertIndexAt(above, x, y) { + if (above) { + return above.itemIndex; + } else { + var distance = tasks.vertical ? x : y; + var step = tasks.vertical ? LayoutManager.taskWidth() : LayoutManager.taskHeight(); + var stripe = Math.ceil(distance / step); + + if (stripe === LayoutManager.calculateStripes()) { + return tasksModel.count - 1; + } else { + return stripe * LayoutManager.tasksPerStripe(); + } + } +} + +function publishIconGeometries(taskItems) { + for (var i = 0; i < taskItems.length - 1; ++i) { + var task = taskItems[i]; + + if (!task.isLauncher && !task.isStartup) { + tasksModel.requestPublishDelegateGeometry(tasksModel.makeModelIndex(task.itemIndex), + backend.globalRect(task), task); + } + } +} + +function taskPrefix(prefix) { + var effectivePrefix; + + switch (plasmoid.location) { + case PlasmaCore.Types.LeftEdge: + effectivePrefix = "west-" + prefix; + break; + case PlasmaCore.Types.TopEdge: + effectivePrefix = "north-" + prefix; + break; + case PlasmaCore.Types.RightEdge: + effectivePrefix = "east-" + prefix; + break; + default: + effectivePrefix = "south-" + prefix; + } + if (!frame.hasElementPrefix(effectivePrefix)) { + return prefix; + } + return effectivePrefix; + +} diff --git a/applets/taskmanager/package/contents/config/config.qml b/applets/taskmanager/package/contents/config/config.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/config/config.qml @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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.0 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18n("General") + icon: "preferences-system-windows" + source: "ConfigGeneral.qml" + } +} diff --git a/applets/taskmanager/package/contents/config/main.xml b/applets/taskmanager/package/contents/config/main.xml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/config/main.xml @@ -0,0 +1,69 @@ + + + + + + + false + + + false + + + true + + + false + + + 1 + + + true + + + + + + + + + 2 + + + 2 + 1 + + + false + + + true + + + true + + + false + + + + + + + + + + + 0 + + + true + + + + + diff --git a/applets/taskmanager/package/contents/ui/ConfigGeneral.qml b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml @@ -0,0 +1,206 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 + +import org.kde.plasma.core 2.0 as PlasmaCore + +Item { + width: childrenRect.width + height: childrenRect.height + + property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) + + property alias cfg_forceStripes: forceStripes.checked + property alias cfg_showToolTips: showToolTips.checked + property alias cfg_wheelEnabled: wheelEnabled.checked + property alias cfg_highlightWindows: highlightWindows.checked + property alias cfg_smartLaunchersEnabled: smartLaunchers.checked + property alias cfg_maxStripes: maxStripes.value + property alias cfg_groupingStrategy: groupingStrategy.currentIndex + property alias cfg_middleClickAction: middleClickAction.currentIndex + property alias cfg_onlyGroupWhenFull: onlyGroupWhenFull.checked + property alias cfg_sortingStrategy: sortingStrategy.currentIndex + property alias cfg_showOnlyCurrentScreen: showOnlyCurrentScreen.checked + property alias cfg_showOnlyCurrentDesktop: showOnlyCurrentDesktop.checked + property alias cfg_showOnlyCurrentActivity: showOnlyCurrentActivity.checked + property alias cfg_showOnlyMinimized: showOnlyMinimized.checked + + ColumnLayout { + GroupBox { + Layout.fillWidth: true + + title: i18n("Arrangement") + flat: true + + GridLayout { + columns: 2 + Layout.fillWidth: true + + Label { + text: vertical ? i18n("Maximum columns:") : i18n("Maximum rows:") + } + + SpinBox { + id: maxStripes + minimumValue: 1 + } + + CheckBox { + id: forceStripes + Layout.column: 1 + Layout.row: 1 + text: vertical ? i18n("Always arrange tasks in rows of as many columns") : i18n("Always arrange tasks in columns of as many rows") + enabled: maxStripes.value > 1 + } + } + } + + GroupBox { + Layout.fillWidth: true + + title: i18n("Behavior") + flat: true + + ColumnLayout { + Layout.fillWidth: true + + CheckBox { + id: showToolTips + text: i18n("Show tooltips") + } + + CheckBox { + id: wheelEnabled + text: i18n("Cycle through tasks with mouse wheel") + } + + CheckBox { + id: highlightWindows + text: i18n("Highlight windows") + } + + CheckBox { + id: smartLaunchers + Layout.fillWidth: true + text: i18n("Show progress and status information in task buttons") + } + + RowLayout { + Label { + text: i18n("On middle-click:") + } + + ComboBox { + id: middleClickAction + Layout.fillWidth: true + model: [i18nc("The click action", "None"), i18n("Close Window or Group"), i18n("New Instance")] + } + } + } + } + + GroupBox { + Layout.fillWidth: true + + title: i18n("Grouping and Sorting") + flat: true + + visible: (plasmoid.pluginName != "org.kde.plasma.icontasks") + + ColumnLayout { + GridLayout { + columns: 3 + Label { + Layout.fillWidth: true + text: i18n("Sorting:") + horizontalAlignment: Text.AlignRight + } + + ComboBox { + id: sortingStrategy + Layout.fillWidth: true + model: [i18n("Do Not Sort"), i18n("Manually"), i18n("Alphabetically"), i18n("By Desktop"), i18n("By Activity")] + } + + Label { + Layout.fillWidth: true + Layout.row: 1 + Layout.column: 0 + text: i18n("Grouping:") + horizontalAlignment: Text.AlignRight + } + + ComboBox { + id: groupingStrategy + Layout.row: 1 + Layout.column: 1 + Layout.fillWidth: true + model: [i18n("Do Not Group"), i18n("By Program Name")] + } + CheckBox { + id: onlyGroupWhenFull + Layout.column: 1 + Layout.row: 2 + Layout.columnSpan: 2 + text: i18n("Only when the task manager is full") + enabled: groupingStrategy.currentIndex > 0 + } + } + + } + } + + GroupBox { + Layout.fillWidth: true + + title: i18n("Filters") + flat: true + + ColumnLayout { + Layout.fillWidth: true + + CheckBox { + id: showOnlyCurrentScreen + text: i18n("Show only tasks from the current screen") + } + + CheckBox { + id: showOnlyCurrentDesktop + text: i18n("Show only tasks from the current desktop") + } + + CheckBox { + id: showOnlyCurrentActivity + text: i18n("Show only tasks from the current activity") + } + + CheckBox { + id: showOnlyMinimized + + visible: (plasmoid.pluginName != "org.kde.plasma.icontasks") + + text: i18n("Show only tasks that are minimized") + } + } + } + } +} diff --git a/applets/taskmanager/package/contents/ui/ContextMenu.qml b/applets/taskmanager/package/contents/ui/ContextMenu.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/ContextMenu.qml @@ -0,0 +1,350 @@ +/*************************************************************************** + * Copyright (C) 2012-2016 by Eike Hein * + * * + * 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.0 + +import org.kde.plasma.plasmoid 2.0 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents + +PlasmaComponents.ContextMenu { + id: menu + + placement: { + if (plasmoid.location == PlasmaCore.Types.LeftEdge) { + return PlasmaCore.Types.RightPosedTopAlignedPopup; + } else if (plasmoid.location == PlasmaCore.Types.TopEdge) { + return PlasmaCore.Types.BottomPosedLeftAlignedPopup; + } else { + return PlasmaCore.Types.TopPosedLeftAlignedPopup; + } + } + + minimumWidth: visualParent.width + + onStatusChanged: { + if (visualParent && visualParent.launcherUrl != null && status == PlasmaComponents.DialogStatus.Open) { + launcherToggleAction.checked = (tasksModel.launcherPosition(visualParent.launcherUrl) != -1); + } else if (status == PlasmaComponents.DialogStatus.Closed) { + menu.destroy(); + } + } + + function show() { + loadDynamicLaunchActions(visualParent.launcherUrl); + openRelative(); + } + + function newMenuItem(parent) { + return Qt.createQmlObject( + "import org.kde.plasma.components 2.0 as PlasmaComponents;" + + "PlasmaComponents.MenuItem {}", + parent); + } + + function newSeparator(parent) { + return Qt.createQmlObject( + "import org.kde.plasma.components 2.0 as PlasmaComponents;" + + "PlasmaComponents.MenuItem { separator: true }", + parent); + } + + function loadDynamicLaunchActions(launcherUrl) { + var actionList = backend.jumpListActions(launcherUrl, menu); + + for (var i = 0; i < actionList.length; ++i) { + var item = newMenuItem(menu); + item.action = actionList[i]; + menu.addMenuItem(item, virtualDesktopsMenuItem); + } + + if (actionList.length > 0) { + menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); + } + + var actionList = backend.recentDocumentActions(launcherUrl, menu); + + for (var i = 0; i < actionList.length; ++i) { + var item = newMenuItem(menu); + item.action = actionList[i]; + menu.addMenuItem(item, virtualDesktopsMenuItem); + } + + if (actionList.length > 0) { + menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); + } + } + + PlasmaComponents.MenuItem { + id: virtualDesktopsMenuItem + + visible: (visualParent && !visualParent.isLauncher + && !visualParent.isStartup && visualParent.isVirtualDesktopChangeable) + + enabled: visible + + text: i18n("Move To Desktop") + + Connections { + target: virtualDesktopInfo + + onNumberOfDesktopsChanged: virtualDesktopsMenu.refresh() + onDesktopNamesChanged: virtualDesktopsMenu.refresh() + } + + PlasmaComponents.ContextMenu { + id: virtualDesktopsMenu + + visualParent: virtualDesktopsMenuItem.action + + function refresh() { + clearMenuItems(); + + var menuItem = menu.newMenuItem(virtualDesktopsMenu); + menuItem.text = i18n("Move To Current Desktop"); + menuItem.enabled = Qt.binding(function() { + return menu.visualParent && menu.visualParent.virtualDesktop != virtualDesktopInfo.currentDesktop; + }); + menuItem.clicked.connect(function() { + tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), 0); + }); + + menuItem = menu.newMenuItem(virtualDesktopsMenu); + menuItem.text = i18n("All Desktops"); + menuItem.checkable = true; + menuItem.checked = Qt.binding(function() { + return menu.visualParent && menu.visualParent.isOnAllVirtualDesktops; + }); + menuItem.clicked.connect(function() { + tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), 0); + }); + backend.setActionGroup(menuItem.action); + + menu.newSeparator(virtualDesktopsMenu); + + for (var i = 0; i < virtualDesktopInfo.desktopNames.length; ++i) { + menuItem = menu.newMenuItem(virtualDesktopsMenu); + menuItem.text = i18n("%1 Desktop %2", i + 1, virtualDesktopInfo.desktopNames[i]); + menuItem.checkable = true; + menuItem.checked = Qt.binding((function(i) { + return function() { return menu.visualParent && menu.visualParent.virtualDesktop == (i + 1) }; + })(i)); + menuItem.clicked.connect((function(i) { + return function() { return tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), i + 1); }; + })(i)); + backend.setActionGroup(menuItem.action); + } + + menu.newSeparator(virtualDesktopsMenu); + + menuItem = menu.newMenuItem(virtualDesktopsMenu); + menuItem.text = i18n("New Desktop"); + menuItem.clicked.connect(function() { + tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), virtualDesktopInfo.numberOfDesktops + 1) + }); + } + + Component.onCompleted: refresh() + } + } + + PlasmaComponents.MenuItem { + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visualParent && visualParent.isMinimizable + + checkable: true + checked: visualParent && visualParent.isMinimized + + text: i18n("Minimize") + + onClicked: tasksModel.requestToggleMinimized(visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visualParent && visualParent.isMaximizable + + checkable: true + checked: visualParent && visualParent.isMaximized + + text: i18n("Maximize") + + onClicked: tasksModel.requestToggleMaximized(visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visualParent && visualParent.launcherUrl != null + + text: i18n("Start New Instance") + icon: "system-run" + + onClicked: tasksModel.requestNewInstance(visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + id: launcherToggleAction + + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visualParent && visualParent.launcherUrl != null + + checkable: true + + text: i18n("Show A Launcher When Not Running") + + onClicked: { + if (tasksModel.launcherPosition(visualParent.launcherUrl) != -1) { + tasksModel.requestRemoveLauncher(visualParent.launcherUrl); + } else { + tasksModel.requestAddLauncher(visualParent.launcherUrl); + } + } + } + + PlasmaComponents.MenuItem { + visible: (visualParent && visualParent.isLauncher) + + text: i18n("Remove Launcher") + + onClicked: tasksModel.requestRemoveLauncher(visualParent.launcherUrl); + } + + + PlasmaComponents.MenuItem { + id: moreActionsMenuItem + + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visible + + text: i18n("More Actions") + + PlasmaComponents.ContextMenu { + visualParent: moreActionsMenuItem.action + + PlasmaComponents.MenuItem { + enabled: menu.visualParent && menu.visualParent.isMovable + + text: i18n("Move") + icon: "transform-move" + + onClicked: tasksModel.requestMove(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + enabled: menu.visualParent && menu.visualParent.isResizable + + text: i18n("Resize") + + onClicked: tasksModel.requestResize(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + checkable: true + checked: menu.visualParent && menu.visualParent.isKeepAbove + + text: i18n("Keep Above Others") + icon: "go-up" + + onClicked: tasksModel.requestToggleKeepAbove(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + checkable: true + checked: menu.visualParent && menu.visualParent.isKeepBelow + + text: i18n("Keep Below Others") + icon: "go-down" + + onClicked: tasksModel.requestToggleKeepBelow(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + enabled: menu.visualParent && menu.visualParent.isFullScreenable + + checkable: true + checked: menu.visualParent && menu.visualParent.isFullScreen + + text: i18n("Fullscreen") + icon: "view-fullscreen" + + onClicked: tasksModel.requestToggleFullScreen(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + enabled: menu.visualParent && menu.visualParent.isShadeable + + checkable: true + checked: menu.visualParent && menu.visualParent.isShaded + + text: i18n("Shade") + + onClicked: tasksModel.requestToggleShaded(menu.visualParent.modelIndex()) + } + + PlasmaComponents.MenuItem { + separator: true + } + + PlasmaComponents.MenuItem { + visible: (plasmoid.configuration.groupingStrategy != 0) && menu.visualParent.isWindow + + checkable: true + checked: menu.visualParent && menu.visualParent.isGroupable + + text: i18n("Allow this program to be grouped") + + onClicked: tasksModel.requestToggleGrouping(menu.visualParent.modelIndex()) + } + } + } + + PlasmaComponents.MenuItem { + property QtObject configureAction: null + + enabled: configureAction && configureAction.enabled + + text: configureAction ? configureAction.text : "" + icon: configureAction ? configureAction.icon : "" + + onClicked: configureAction.trigger() + + Component.onCompleted: configureAction = plasmoid.action("configure") + } + + PlasmaComponents.MenuItem { + separator: true + } + + PlasmaComponents.MenuItem { + visible: (visualParent && !visualParent.isLauncher && !visualParent.isStartup) + + enabled: visualParent && visualParent.isClosable + + text: i18n("Close") + icon: "window-close" + + onClicked: tasksModel.requestClose(visualParent.modelIndex()) + } +} diff --git a/applets/taskmanager/package/contents/ui/GroupDialog.qml b/applets/taskmanager/package/contents/ui/GroupDialog.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/GroupDialog.qml @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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.0 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.draganddrop 2.0 + +import "../code/layout.js" as LayoutManager + +PlasmaCore.Dialog { + visible: false + + type: PlasmaCore.Dialog.PopupMenu + flags: Qt.WindowStaysOnTopHint + hideOnWindowDeactivate: true + location: plasmoid.location + + mainItem: Item { + MouseHandler { + id: mouseHandler + + anchors.fill: parent + + target: taskList + } + + TaskList { + id: taskList + + anchors.fill: parent + + Repeater { + id: groupRepeater + + onCountChanged: updateSize() + } + } + } + + data: [ + VisualDataModel { + id: groupFilter + + delegate: Task { + visible: true + inPopup: true + } + } + ] + + onVisualParentChanged: { + if (!visualParent) { + visible = false; + } + } + + onVisibleChanged: { + if (visible && visualParent) { + groupFilter.model = tasksModel; + groupFilter.rootIndex = groupFilter.modelIndex(visualParent.itemIndex); + groupRepeater.model = groupFilter; + } else { + visualParent = null; + groupRepeater.model = undefined; + groupFilter.model = undefined; + groupFilter.rootIndex = undefined; + } + } + + function updateSize() { + if (!visible || !visualParent) { + return; + } + + if (!groupRepeater.count) { + visible = false; + } else { + var task; + var maxWidth = 0; + + for (var i = 0; i < taskList.children.length - 1; ++i) { + task = taskList.children[i]; + + if (task.textWidth > maxWidth) { + maxWidth = task.textWidth; + } + + task.textWidthChanged.connect(updateSize); + } + + maxWidth += LayoutManager.horizontalMargins() + units.iconSizes.small + 6; + + // TODO: Properly derive limits from work area size (screen size sans struts). + mainItem.width = Math.min(maxWidth, (tasks.vertical ? 640 - tasks.width : Math.max(tasks.width, 640)) - 20); + mainItem.height = groupRepeater.count * (LayoutManager.verticalMargins() + units.iconSizes.small); + } + } +} diff --git a/applets/taskmanager/package/contents/ui/GroupExpanderOverlay.qml b/applets/taskmanager/package/contents/ui/GroupExpanderOverlay.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/GroupExpanderOverlay.qml @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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.0 + +import org.kde.plasma.core 2.0 as PlasmaCore + +PlasmaCore.SvgItem { + id: arrow + + anchors { + bottom: arrow.parent.bottom + horizontalCenter: iconBox.horizontalCenter + } + + visible: parent.isGroupParent + + states: [ + State { + name: "top" + when: plasmoid.location == PlasmaCore.Types.TopEdge + AnchorChanges { + target: arrow + anchors.top: arrow.parent.top + anchors.left: undefined + anchors.right: undefined + anchors.bottom: undefined + anchors.horizontalCenter: iconBox.horizontalCenter + anchors.verticalCenter: undefined + } + }, + State { + name: "left" + when: plasmoid.location == PlasmaCore.Types.LeftEdge + AnchorChanges { + target: arrow + anchors.top: undefined + anchors.left: arrow.parent.left + anchors.right: undefined + anchors.bottom: undefined + anchors.horizontalCenter: undefined + anchors.verticalCenter: iconBox.verticalCenter + } + }, + State { + name: "right" + when: plasmoid.location == PlasmaCore.Types.RightEdge + AnchorChanges { + target: arrow + anchors.top: undefined + anchors.left: undefined + anchors.right: arrow.parent.right + anchors.bottom: undefined + anchors.horizontalCenter: undefined + anchors.verticalCenter: iconBox.verticalCenter + } + } + ] + + implicitWidth: Math.min(naturalSize.width, iconBox.width) + implicitHeight: Math.min(naturalSize.height, iconBox.width) + + svg: taskSvg + elementId: elementForLocation() + + function elementForLocation() { + switch (plasmoid.location) { + case PlasmaCore.Types.LeftEdge: + return "group-expander-left"; + case PlasmaCore.Types.TopEdge: + return "group-expander-top"; + case PlasmaCore.Types.RightEdge: + return "group-expander-right"; + case PlasmaCore.Types.BottomEdge: + default: + return "group-expander-bottom"; + } + } +} diff --git a/applets/taskmanager/package/contents/ui/MouseHandler.qml b/applets/taskmanager/package/contents/ui/MouseHandler.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/MouseHandler.qml @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (C) 2012-2016 by Eike Hein * + * * + * 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.0 + +import org.kde.draganddrop 2.0 + +import org.kde.taskmanager 0.1 as TaskManager + +import "../code/layout.js" as LayoutManager +import "../code/tools.js" as TaskTools + +Item { + signal urlDropped(url url) + + property Item target + + DropArea { + id: dropHandler + + anchors.fill: parent + + preventStealing: true; + + property Item hoveredItem + + onDragMove: { + if (target.animating) { + return; + } + + var above = target.childAt(event.x, event.y); + + if (tasksModel.sortMode == TaskManager.TasksModel.SortManual && tasks.dragSource) { + var insertAt = TaskTools.insertIndexAt(above, event.x, event.y); + + if (!groupDialog.visible && tasks.dragSource != above && tasks.dragSource.itemIndex != insertAt) { + tasksModel.move(tasks.dragSource.itemIndex, insertAt); + } + } else if (!tasks.dragSource && above && hoveredItem != above) { + hoveredItem = above; + activationTimer.restart(); + } else if (!above) { + hoveredItem = null; + activationTimer.stop(); + } + } + + onDragLeave: { + hoveredItem = null; + activationTimer.stop(); + } + + onDrop: { + // Reject internal drops. + if (event.mimeData.formats.indexOf("application/x-orgkdeplasmataskmanager_taskbuttonitem") >= 0) { + return; + } + + if (event.mimeData.hasUrls) { + parent.urlDropped(event.mimeData.url); + } + } + + Timer { + id: activationTimer + + interval: 250 + repeat: false + + onTriggered: { + if (parent.hoveredItem.isGroupParent) { + groupDialog.visualParent = parent.hoveredItem; + groupDialog.visible = true; + } else if (!parent.hoveredItem.isLauncher) { + tasksModel.requestActivate(parent.hoveredItem.modelIndex()); + } + } + } + } + + MouseArea { + id: wheelHandler + + anchors.fill: parent + property int wheelDelta: 0; + enabled: plasmoid.configuration.wheelEnabled + + onWheel: wheelDelta = TaskTools.wheelActivateNextPrevTask(wheelDelta, wheel.angleDelta.y); + } +} diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/Task.qml @@ -0,0 +1,474 @@ +/*************************************************************************** + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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.0 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.draganddrop 2.0 + +import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet + +import "../code/layout.js" as LayoutManager +import "../code/tools.js" as TaskTools + +MouseArea { + id: task + + width: groupDialog.mainItem.width + height: Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.small) + LayoutManager.verticalMargins() + + visible: false + + LayoutMirroring.enabled: (Qt.application.layoutDirection == Qt.RightToLeft) + LayoutMirroring.childrenInherit: (Qt.application.layoutDirection == Qt.RightToLeft) + + property int itemIndex: index + property bool inPopup: false + property bool initialGeometryExported: false + property bool isGroupParent: model.IsGroupParent === true + property bool isActive: model.IsActive === true + property bool isWindow: model.IsWindow === true + property bool isLauncher: model.IsLauncher === true + property bool isStartup: model.IsStartup === true + property bool isGroupable: model.IsGroupable === true + property bool demandsAttention: model.IsDemandingAttention === true + property int textWidth: label.implicitWidth + property bool pressed: false + property int pressX: -1 + property int pressY: -1 + property Item busyIndicator + property QtObject contextMenu: null + property int wheelDelta: 0 + + // FIXME Clean up all these props. + property variant launcherUrl: model.LauncherUrl != undefined ? model.LauncherUrl : false + + property bool isClosable: model.IsClosable === true + property bool isMovable: model.IsMovable === true + property bool isResizable: model.IsResizable === true + + property bool isMaximizable: model.IsMaximizable === true + property bool isMaximized: model.IsMaximized === true + + property bool isMinimizable: model.IsMinimizable === true + property bool isMinimized: model.IsMinimized === true + + property bool isKeepAbove: model.IsKeepAbove === true + property bool isKeepBelow: model.IsKeepBelow === true + + property bool isFullScreenable: model.IsFullScreenable === true + property bool isFullScreen: model.IsFullScreen === true + + property bool isShadeable: model.IsShadeable === true + property bool isShaded: model.IsShaded === true + + property bool isVirtualDesktopChangeable: model.IsVirtualDesktopChangeable === true + property int virtualDesktop: model.VirtualDesktop != undefined ? model.VirtualDesktop : -1 + property int isOnAllVirtualDesktops: model.IsOnAllVirtualDesktops === true + + readonly property bool smartLauncherEnabled: plasmoid.configuration.smartLaunchersEnabled && !inPopup && !isStartup + property QtObject smartLauncherItem: null + + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MidButton + hoverEnabled: true + + onVisibleChanged: { + if (visible && isWindow && itemIndex == 0) { + tasksModel.requestPublishDelegateGeometry(modelIndex(), backend.globalRect(task), task); + initialGeometryExported = true; + } + } + + onXChanged: { + if (!initialGeometryExported) { + tasksModel.requestPublishDelegateGeometry(modelIndex(), backend.globalRect(task), task); + initialGeometryExported = true; + } + } + + onItemIndexChanged: { + if (!inPopup && !tasks.vertical && LayoutManager.calculateStripes() > 1) { + tasks.requestLayout(); + } + } + + onContainsMouseChanged: { + if (!containsMouse) { + pressed = false; + } + + tasks.windowsHovered(model.LegacyWinIdList, containsMouse); + } + + onPressed: { + if (mouse.button == Qt.LeftButton || mouse.button == Qt.MidButton) { + pressed = true; + pressX = mouse.x; + pressY = mouse.y; + } else if (mouse.button == Qt.RightButton) { + if (plasmoid.configuration.showToolTips) { + toolTip.hideToolTip(); + } + + contextMenu = tasks.contextMenuComponent.createObject(task); + contextMenu.visualParent = task; + contextMenu.show(); + } + } + + onReleased: { + if (pressed) { + if (mouse.button == Qt.MidButton) { + if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.NewInstance) { + tasksModel.requestNewInstance(modelIndex()); + } else if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.Close) { + tasksModel.requestClose(modelIndex()); + } + } else if (mouse.button == Qt.LeftButton) { + if (mouse.modifiers & Qt.ShiftModifier) { + tasksModel.requestNewInstance(index); + } else if (isGroupParent) { + if ((iconsOnly || mouse.modifiers == Qt.ControlModifier) && backend.canPresentWindows()) { + toolTip.hideToolTip(); + tasks.presentWindows(model.LegacyWinIdList); + } else if (groupDialog.visible) { + groupDialog.visible = false; + } else { + groupDialog.visualParent = task; + groupDialog.visible = true; + } + } else { + if (model.IsMinimized) { + var i = modelIndex(); + tasksModel.requestToggleMinimized(i); + tasksModel.requestActivate(i); + } else if (model.IsActive) { + tasksModel.requestToggleMinimized(modelIndex()); + } else { + tasksModel.requestActivate(modelIndex()); + } + } + } + } + + pressed = false; + pressX = -1; + pressY = -1; + } + + onPositionChanged: { + // mouse.button is always 0 here, hence checking with mouse.buttons + if (pressX != -1 && mouse.buttons == Qt.LeftButton && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { + tasks.dragSource = task; + dragHelper.startDrag(task, model.MimeType, model.MimeData, + model.LauncherUrl, model.decoration); + pressX = -1; + pressY = -1; + + return; + } + } + + onWheel: { + if (plasmoid.configuration.wheelEnabled) { + wheelDelta = TaskTools.wheelActivateNextPrevTask(wheelDelta, wheel.angleDelta.y); + } + } + + onSmartLauncherEnabledChanged: { + if (smartLauncherEnabled && !smartLauncherItem) { + var smartLauncher = Qt.createQmlObject(" + import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet; + TaskManagerApplet.SmartLauncherItem { }", task); + + smartLauncher.launcherUrl = Qt.binding(function() { return model.LauncherUrl; }); + + smartLauncherItem = smartLauncher; + } + } + + function modelIndex() { + return (inPopup ? tasksModel.makeModelIndex(groupDialog.visualParent.itemIndex, index) + : tasksModel.makeModelIndex(index)); + } + + PlasmaCore.FrameSvgItem { + id: frame + + anchors { + fill: parent + + topMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0 + bottomMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0 + leftMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0 + rightMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0 + } + + imagePath: "widgets/tasks" + property string basePrefix: "normal" + prefix: TaskTools.taskPrefix("normal") + onRepaintNeeded: prefix = TaskTools.taskPrefix(basePrefix); + onBasePrefixChanged: prefix = TaskTools.taskPrefix(basePrefix); + + PlasmaCore.ToolTipArea { + id: toolTip + + anchors.fill: parent + + active: !inPopup && !groupDialog.visible && plasmoid.configuration.showToolTips + interactive: true + location: plasmoid.location + + mainItem: toolTipDelegate + + onContainsMouseChanged: { + if (containsMouse) { + toolTipDelegate.parentIndex = itemIndex; + + toolTipDelegate.windows = Qt.binding(function() { + return model.LegacyWinIdList; + }); + toolTipDelegate.mainText = Qt.binding(function() { + return model.display; + }); + toolTipDelegate.icon = Qt.binding(function() { + return model.decoration; + }); + toolTipDelegate.subText = Qt.binding(function() { + return model.IsLauncher ? model.GenericName : toolTip.generateSubText(model); + }); + toolTipDelegate.launcherUrl = Qt.binding(function() { + return model.LauncherUrl; + }); + } + } + + function generateSubText(task) { + var subTextEntries = new Array(); + + if (!plasmoid.configuration.showOnlyCurrentDesktop + && virtualDesktopInfo.numberOfDesktops > 1 + && !model.IsOnAllVirtualDesktops + && model.VirtualDesktop != -1 + && model.VirtualDesktop != undefined) { + subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[model.VirtualDesktop - 1])); + } + + if (model.Activities == undefined) { + return subTextEntries.join("\n"); + } + + if (model.Activities.length == 0 && activityInfo.numberOfRunningActivities > 1) { + subTextEntries.push(i18nc("Which virtual desktop a window is currently on", + "Available on all activities")); + } else if (model.Activities.length > 0) { + var activityNames = new Array(); + + for (var i = 0; i < model.Activities.length; i++) { + var activity = model.Activities[i]; + + if (plasmoid.configuration.showOnlyCurrentActivity) { + if (activity != activityInfo.currentActivity) { + activityNames.push(activityInfo.activityName(model.Activities[i])); + } + } else if (activity != activityInfo.currentActivity) { + activityNames.push(activityInfo.activityName(model.Activities[i])); + } + } + + if (plasmoid.configuration.showOnlyCurrentActivity) { + if (activityNames.length > 0) { + subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", + "Also available on %1", activityNames.join(", "))); + } + } else if (activityNames.length > 0) { + subTextEntries.push(i18nc("Which activities a window is currently on", + "Available on %1", activityNames.join(", "))); + } + } + + return subTextEntries.join("\n"); + } + } + } + + Loader { + anchors.fill: frame + asynchronous: true + source: "TaskProgressOverlay.qml" + active: plasmoid.configuration.smartLaunchersEnabled && task.smartLauncherItem && task.smartLauncherItem.progressVisible + } + + Item { + id: iconBox + + anchors { + left: parent.left + leftMargin: tasks.vertical && !label.visible ? adjustMargin(true, parent.width, taskFrame.margins.left) : taskFrame.margins.left + top: parent.top + topMargin: adjustMargin(false, parent.height, taskFrame.margins.top); + bottom: parent.bottom + bottomMargin: adjustMargin(false, parent.height, taskFrame.margins.bottom); + right: (tasks.vertical && !label.visible && !inPopup) ? parent.right : undefined + rightMargin: tasks.vertical && !label.visible ? adjustMargin(true, parent.width, taskFrame.margins.right) : undefined + } + + function adjustMargin(vert, size, margin) { + if (!size) { + return margin; + } + + var margins = vert ? LayoutManager.horizontalMargins() : LayoutManager.verticalMargins(); + + if ((size - margins) < units.iconSizes.small) { + return Math.ceil((margin * (units.iconSizes.small / size)) / 2); + } + + return margin; + } + + width: inPopup ? units.iconSizes.small : Math.min(height, parent.width - LayoutManager.horizontalMargins()) + + PlasmaCore.IconItem { + id: icon + + anchors.fill: parent + + active: task.containsMouse || (task.contextMenu && task.contextMenu.status == PlasmaComponents.DialogStatus.Open) + enabled: true + usesPlasmaTheme: false + + source: model.decoration + } + + Loader { + anchors.fill: icon + asynchronous: true + source: "TaskBadgeOverlay.qml" + active: plasmoid.configuration.smartLaunchersEnabled && height >= units.iconSizes.small + && icon.visible && task.smartLauncherItem && task.smartLauncherItem.countVisible + } + + PlasmaComponents.BusyIndicator { + id: busyIndicator + + anchors.fill: parent + + visible: model.IsStartup === true + + running: model.IsStartup === true + } + + states: [ + // Using a state transition avoids a binding loop between label.visible and + // the text label margin, which derives from the icon width. + State { + name: "standalone" + when: !label.visible + + AnchorChanges { + target: iconBox + anchors.left: undefined + anchors.horizontalCenter: parent.horizontalCenter + } + + PropertyChanges { + target: iconBox + anchors.leftMargin: 0 + } + } + ] + } + + TaskManagerApplet.TextLabel { + id: label + + anchors { + fill: parent + leftMargin: taskFrame.margins.left + iconBox.width + units.smallSpacing + topMargin: taskFrame.margins.top + rightMargin: taskFrame.margins.right + bottomMargin: taskFrame.margins.bottom + } + + visible: (inPopup || !iconsOnly && !model.IsLauncher && (parent.width - LayoutManager.horizontalMargins()) >= (theme.mSize(theme.defaultFont).width * 7)) + + enabled: true + + text: (!inPopup && iconsOnly) ? "" : model.display + color: theme.textColor + elide: !inPopup + } + + states: [ + State { + name: "launcher" + when: model.IsLauncher + + PropertyChanges { + target: frame + basePrefix: "" + } + }, + State { + name: "hovered" + when: containsMouse || (contextMenu.status == PlasmaComponents.DialogStatus.Open && contextMenu.visualParent == task) + + PropertyChanges { + target: frame + basePrefix: "hover" + } + }, + State { + name: "attention" + when: model.IsDemandingAttention || (task.smartLauncherItem && task.smartLauncherItem.urgent) + + PropertyChanges { + target: frame + basePrefix: "attention" + } + }, + State { + name: "minimized" + when: model.IsMinimized && !(groupDialog.visible && groupDialog.target == task) + + PropertyChanges { + target: frame + basePrefix: "minimized" + } + }, + State { + name: "active" + when: model.IsActive || groupDialog.visible && groupDialog.target == task + + PropertyChanges { + target: frame + basePrefix: "focus" + } + } + ] + + Component.onCompleted: { + if (!inPopup) { + var component = Qt.createComponent("GroupExpanderOverlay.qml"); + component.createObject(task); + } + } +} diff --git a/applets/taskmanager/package/contents/ui/TaskBadgeOverlay.qml b/applets/taskmanager/package/contents/ui/TaskBadgeOverlay.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/TaskBadgeOverlay.qml @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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.4 + +import org.kde.plasma.components 2.0 as PlasmaComponents + +Item { + readonly property int iconWidthDelta: (icon.width - icon.paintedWidth) / 2 + + Item { + id: badgeMask + anchors.fill: parent + + Rectangle { + readonly property int offset: Math.round(Math.max(units.smallSpacing / 2, badgeMask.width / 32)) + x: Qt.application.layoutDirection === Qt.RightToLeft ? -offset + iconWidthDelta : parent.width - width + offset - iconWidthDelta + y: -offset + width: badgeRect.width + offset * 2 + height: badgeRect.height + offset * 2 + radius: width + } + } + + ShaderEffect { + anchors.fill: parent + property var source: ShaderEffectSource { + sourceItem: icon + hideSource: true + } + property var mask: ShaderEffectSource { + sourceItem: badgeMask + hideSource: true + live: false + } + + onWidthChanged: mask.scheduleUpdate() + onHeightChanged: mask.scheduleUpdate() + + supportsAtlasTextures: true + + fragmentShader: " + varying highp vec2 qt_TexCoord0; + uniform highp float qt_Opacity; + uniform lowp sampler2D source; + uniform lowp sampler2D mask; + void main() { + gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0 - (texture2D(mask, qt_TexCoord0.st).a)) * qt_Opacity; + } + " + } + + Rectangle { + id: badgeRect + x: Qt.application.layoutDirection === Qt.RightToLeft ? iconWidthDelta : parent.width - width - iconWidthDelta + width: height + height: Math.round(parent.height * 0.4) + color: theme.highlightColor + radius: width + + PlasmaComponents.Label { + anchors.centerIn: parent + width: height + height: Math.round(parent.height) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + fontSizeMode: Text.Fit + font.pointSize: 1024 + minimumPointSize: 5 + color: theme.backgroundColor + text: task.smartLauncherItem.count + } + } +} diff --git a/applets/taskmanager/package/contents/ui/TaskList.qml b/applets/taskmanager/package/contents/ui/TaskList.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/TaskList.qml @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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.0 + +Flow { + property bool animating: false + + layoutDirection: Qt.application.layoutDirection + + property int rows: Math.floor(height / children[0].height) + property int columns: Math.floor(width / children[0].width) + + move: Transition { + SequentialAnimation { + PropertyAction { target: taskList; property: "animating"; value: true } + + NumberAnimation { + properties: "x, y" + easing.type: Easing.OutQuad + } + + PropertyAction { target: taskList; property: "animating"; value: false } + } + } +} diff --git a/applets/taskmanager/package/contents/ui/TaskProgressOverlay.qml b/applets/taskmanager/package/contents/ui/TaskProgressOverlay.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/TaskProgressOverlay.qml @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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.0 + +import org.kde.plasma.core 2.0 as PlasmaCore + +import "../code/tools.js" as TaskTools + +Item { + id: background + + Item { + id: progress + anchors { + top: parent.top + left: parent.left + bottom: parent.bottom + } + + width: parent.width * (task.smartLauncherItem.progress / 100) + clip: true + + PlasmaCore.FrameSvgItem { + id: progressFrame + width: background.width + height: background.height + + imagePath: "widgets/tasks" + prefix: calculatePrefix() + onRepaintNeeded: prefix = calculatePrefix() + + function calculatePrefix() { + var prefix = TaskTools.taskPrefix("progress") + if (!frame.hasElementPrefix(prefix)) { + prefix = TaskTools.taskPrefix("hover") + } + return prefix + } + } + } +} diff --git a/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml b/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml @@ -0,0 +1,380 @@ +/* +* Copyright 2013 by Sebastian Kügler +* Copyright 2014 by Martin Gräßlin +* Copyright 2016 by Kai Uwe Broulik +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, 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 Library General Public License for more details +* +* You should have received a copy of the GNU Library General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. +*/ + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons + +Column { + id: tooltipContentItem + + property Item toolTip + property var parentIndex + property var windows + property string mainText + property string subText + property variant icon + property url launcherUrl + + readonly property int thumbnailWidth: units.gridUnit * 15 + readonly property int thumbnailHeight: units.gridUnit * 10 + + property int preferredTextWidth: theme.mSize(theme.defaultFont).width * 30 + property int _s: units.largeSpacing / 2 + + Layout.minimumWidth: Math.max(thumbnailWidth, windowRow.width, appLabelRow.width) + _s + Layout.minimumHeight: childrenRect.height + Layout.maximumWidth: Layout.minimumWidth + Layout.maximumHeight: Layout.minimumHeight + + spacing: _s + + states: State { + when: mpris2Source.hasPlayer + + PropertyChanges { + target: thumbnailSourceItem + opacity: 0 // cannot set visible to false or else WindowThumbnail won't provide thumbnail + } + PropertyChanges { + target: playerControlsOpacityMask + visible: true + source: thumbnailSourceItem + maskSource: playerControlsShadowMask + } + PropertyChanges { + target: playerControlsRow + visible: mpris2Source.hasPlayer + } + } + + PlasmaCore.DataSource { + id: mpris2Source + readonly property string current: { + var split = launcherUrl.toString().split('/') + var appName = split[split.length - 1].replace(".desktop", "") + return appName + } + readonly property bool hasPlayer: sources.indexOf(current) > -1 + + readonly property bool playing: hasPlayer && data[current].PlaybackStatus === "Playing" + readonly property bool canControl: hasPlayer && data[current].CanControl + readonly property bool canGoBack: hasPlayer && data[current].CanGoPrevious + readonly property bool canGoNext: hasPlayer && data[current].CanGoNext + readonly property bool canRaise: hasPlayer && data[current].CanRaise + + readonly property var currentMetadata: hasPlayer ? data[current].Metadata : ({}) + + readonly property string track: { + var xesamTitle = currentMetadata["xesam:title"] + if (xesamTitle) { + return xesamTitle + } + // if no track title is given, print out the file name + var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" + if (!xesamUrl) { + return "" + } + var lastSlashPos = xesamUrl.lastIndexOf('/') + if (lastSlashPos < 0) { + return "" + } + var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) + return decodeURIComponent(lastUrlPart) + } + readonly property string artist: currentMetadata["xesam:artist"] || "" + readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" + + function goPrevious() { + startOperation("Previous") + } + + function goNext() { + startOperation("Next") + } + + function playPause() { + startOperation("PlayPause") + } + + function raise() { + startOperation("Raise") + } + + function startOperation(op) { + var service = mpris2Source.serviceForSource(current) + var operation = service.operationDescription(op) + return service.startOperationCall(operation) + } + + engine: "mpris2" + connectedSources: current + } + + Item { + id: thumbnailContainer + width: Math.max(parent.width, windowRow.width) + height: albumArtImage.available ? albumArtImage.height : + raisePlayerArea.visible ? raisePlayerArea.height : + windowRow.height + + Item { + id: thumbnailSourceItem + anchors.fill: parent + + PlasmaExtras.ScrollArea { + id: scrollArea + anchors.horizontalCenter: parent.horizontalCenter + width: Math.max(windowRow.width, thumbnailWidth) + height: parent.height + + visible: !albumArtImage.available + + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Component.onCompleted: { + flickableItem.interactive = Qt.binding(function() { + return contentItem.width > viewport.width; + }); + } + + Row { + id: windowRow + width: childrenRect.width + height: childrenRect.height + spacing: units.largeSpacing + + Repeater { + model: plasmoid.configuration.showToolTips && !albumArtImage.available ? windows : null + + PlasmaCore.WindowThumbnail { + id: windowThumbnail + + y: -_s + + width: thumbnailWidth + height: thumbnailHeight + + winId: modelData + + ToolTipWindowMouseArea { + anchors.fill: parent + modelIndex: tasksModel.makeModelIndex(parentIndex, index) + winId: modelData + thumbnailItem: parent + } + } + } + } + } + + Image { + id: albumArtImage + // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load + readonly property bool available: status === Image.Ready || status === Image.Loading + + anchors.centerIn: parent + width: parent.width + height: thumbnailHeight + sourceSize: Qt.size(thumbnailWidth, thumbnailHeight) + asynchronous: true + source: mpris2Source.albumArt + fillMode: Image.PreserveAspectCrop + visible: available + + ToolTipWindowMouseArea { + anchors.fill: parent + modelIndex: tasksModel.makeModelIndex(parentIndex) + winId: windows != undefined ? (windows[0] || 0) : 0 + } + } + + MouseArea { + id: raisePlayerArea + anchors.centerIn: parent + width: thumbnailWidth + height: thumbnailHeight + + // if there's no window associated with this task, we might still be able to raise the player + visible: windows == undefined || !windows[0] && mpris2Source.canRaise + onClicked: mpris2Source.raise() + + PlasmaCore.IconItem { + anchors.fill: parent + source: icon + animated: false + usesPlasmaTheme: false + visible: !albumArtImage.available + } + } + } + + Item { + id: playerControlsShadowMask + anchors.fill: thumbnailSourceItem + visible: false // OpacityMask would render it + + Rectangle { + width: parent.width + height: parent.height - playerControlsRow.height + } + + Rectangle { + anchors.bottom: parent.bottom + width: parent.width + height: playerControlsRow.height + opacity: 0.2 + } + } + + OpacityMask { + id: playerControlsOpacityMask + anchors.fill: thumbnailSourceItem + visible: false + } + + // prevent accidental click-through when a control is disabled + MouseArea { + anchors.fill: playerControlsRow + enabled: playerControlsRow.visible + } + + RowLayout { + id: playerControlsRow + anchors { + horizontalCenter: parent.horizontalCenter + bottom: thumbnailSourceItem.bottom + } + width: thumbnailWidth + spacing: 0 + enabled: mpris2Source.canControl + visible: false + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 4 + wrapMode: Text.NoWrap + elide: Text.ElideRight + text: mpris2Source.track || "" + } + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 5 + wrapMode: Text.NoWrap + elide: Text.ElideRight + text: mpris2Source.artist || "" + } + } + + PlasmaComponents.ToolButton { + enabled: mpris2Source.canGoBack + iconName: "media-skip-backward" + tooltip: i18nc("Go to previous song", "Previous") + Accessible.name: tooltip + onClicked: mpris2Source.goPrevious() + } + + PlasmaComponents.ToolButton { + Layout.fillHeight: true + Layout.preferredWidth: height // make this button bigger + iconName: mpris2Source.playing ? "media-playback-pause" : "media-playback-start" + tooltip: mpris2Source.playing ? i18nc("Pause player", "Pause") : i18nc("Start player", "Play") + Accessible.name: tooltip + onClicked: mpris2Source.playPause() + } + + PlasmaComponents.ToolButton { + enabled: mpris2Source.canGoNext + iconName: "media-skip-forward" + tooltip: i18nc("Go to next song", "Next") + Accessible.name: tooltip + onClicked: mpris2Source.goNext() + } + } + } + + Row { + id: appLabelRow + width: childrenRect.width + _s + height: childrenRect.height + units.largeSpacing + spacing: units.largeSpacing + + Item { + id: imageContainer + width: tooltipIcon.width + height: tooltipIcon.height + y: _s + + PlasmaCore.IconItem { + id: tooltipIcon + x: _s + width: units.iconSizes.desktop + height: width + animated: false + usesPlasmaTheme: false + source: icon + } + } + + Column { + id: mainColumn + y: _s + + //This instance is purely for metrics + PlasmaExtras.Heading { + id: tooltipMaintextPlaceholder + visible: false + level: 3 + text: mainText + textFormat: Text.PlainText + } + PlasmaExtras.Heading { + id: tooltipMaintext + level: 3 + width: Math.min(tooltipMaintextPlaceholder.width, preferredTextWidth) + //width: 400 + elide: Text.ElideRight + text: mainText + textFormat: Text.PlainText + } + PlasmaComponents.Label { + id: tooltipSubtext + width: tooltipContentItem.preferredTextWidth + wrapMode: Text.WordWrap + text: subText + textFormat: Text.PlainText + opacity: 0.5 + visible: text !== "" + } + } + } +} diff --git a/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml @@ -0,0 +1,61 @@ +/* +* Copyright 2013 by Sebastian Kügler +* Copyright 2014 by Martin Gräßlin +* Copyright 2016 by Kai Uwe Broulik +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, 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 Library General Public License for more details +* +* You should have received a copy of the GNU Library General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. +*/ + +import QtQuick 2.0 + +import org.kde.plasma.components 2.0 as PlasmaComponents + +MouseArea { + property var modelIndex + property int winId // FIXME Legacy + property Item thumbnailItem + + acceptedButtons: Qt.LeftButton + hoverEnabled: true + enabled: winId != 0 + + onClicked: { + tasksModel.requestActivate(modelIndex); + toolTip.hideToolTip(); + } + + onContainsMouseChanged: { + tasks.windowsHovered([winId], containsMouse); + } + + PlasmaComponents.ToolButton { + anchors { + top: parent.top + topMargin: thumbnailItem ? (thumbnailItem.height - thumbnailItem.paintedHeight) / 2 : 0 + right: parent.right + rightMargin: thumbnailItem ? (thumbnailItem.width - thumbnailItem.paintedWidth) / 2 : 0 + } + + iconSource: "window-close" + visible: parent.containsMouse && winId != 0 + tooltip: i18nc("close this window", "Close") + + onClicked: { + tasksModel.requestClose(modelIndex); + toolTip.hideToolTip(); + } + } +} diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/main.qml @@ -0,0 +1,314 @@ +/*************************************************************************** + * Copyright (C) 2012-2016 by Eike Hein * + * * + * 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.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 + +import org.kde.plasma.core 2.0 as PlasmaCore + +import org.kde.taskmanager 0.1 as TaskManager +import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet + +import "../code/layout.js" as LayoutManager +import "../code/tools.js" as TaskTools + +Item { + id: tasks + + anchors.fill: parent + + property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) + property bool iconsOnly: (plasmoid.pluginName == "org.kde.plasma.icontasks") + + property QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml"); + + Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation + + Layout.fillWidth: true + Layout.fillHeight:true + Layout.minimumWidth: tasks.vertical ? 0 : LayoutManager.preferredMinWidth() + Layout.minimumHeight: !tasks.vertical ? 0 : LayoutManager.preferredMinHeight() + +//BEGIN TODO: this is not precise enough: launchers are smaller than full tasks + Layout.preferredWidth: tasks.vertical ? units.gridUnit * 10 : ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes()); + Layout.preferredHeight: tasks.vertical ? ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes()) : units.gridUnit * 2; +//END TODO + + property Item dragSource: null + + signal requestLayout + signal windowsHovered(variant winIds, bool hovered) + signal presentWindows(variant winIds) + + onWidthChanged: { + taskList.width = LayoutManager.layoutWidth(); + + if (plasmoid.configuration.forceStripes) { + taskList.height = LayoutManager.layoutHeight(); + } + } + + onHeightChanged: { + if (plasmoid.configuration.forceStripes) { + taskList.width = LayoutManager.layoutWidth(); + } + + taskList.height = LayoutManager.layoutHeight(); + } + + onDragSourceChanged: { + if (dragSource == null) { + tasksModel.syncLaunchers(); + } + } + + TaskManager.TasksModel { + id: tasksModel + + virtualDesktop: virtualDesktopInfo.currentDesktop + screen: plasmoid.screen + activity: activityInfo.currentActivity + + filterByVirtualDesktop: plasmoid.configuration.showOnlyCurrentDesktop + filterByScreen: plasmoid.configuration.showOnlyCurrentScreen + filterByActivity: plasmoid.configuration.showOnlyCurrentActivity + filterNotMinimized: plasmoid.configuration.showOnlyMinimized + + sortMode: iconsOnly ? TaskManager.TasksModel.SortManual + : sortModeEnumValue(plasmoid.configuration.sortingStrategy) + launchInPlace: iconsOnly + + groupMode: iconsOnly ? TaskManager.TasksModel.GroupApplication + : sortModeEnumValue(plasmoid.configuration.groupingStrategy) + groupingWindowTasksThreshold: (plasmoid.configuration.onlyGroupWhenFull && !iconsOnly + ? LayoutManager.optimumCapacity(width, height) + 1 : -1) + + onLauncherListChanged: { + plasmoid.configuration.launchers = launcherList; + } + + onGroupingAppIdBlacklistChanged: { + plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist; + } + + onGroupingLauncherUrlBlacklistChanged: { + plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist; + } + + function sortModeEnumValue(index) { + switch (index) { + case 0: + return TaskManager.TasksModel.SortDisabled; + case 1: + return TaskManager.TasksModel.SortManual; + case 2: + return TaskManager.TasksModel.SortAlpha; + case 3: + return TaskManager.TasksModel.SortVirtualDesktop; + case 4: + return TaskManager.TasksModel.SortActivity; + default: + return TaskManager.TasksModel.SortDisabled; + } + } + + function groupModeEnumValue(index) { + switch (index) { + case 0: + return TaskManager.TasksModel.GroupDisabled; + case 1: + return TaskManager.TasksModel.GroupApplications; + } + } + + Component.onCompleted: { + launcherList = plasmoid.configuration.launchers; + groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; + groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; + + // Only hook up view only after the above churn is done. + taskRepeater.model = tasksModel; + } + } + + TaskManager.VirtualDesktopInfo { + id: virtualDesktopInfo + } + + TaskManager.ActivityInfo { + id: activityInfo + } + + TaskManagerApplet.Backend { + id: backend + + taskManagerItem: tasks + toolTipItem: toolTipDelegate + highlightWindows: plasmoid.configuration.highlightWindows + + onAddLauncher: { + tasksModel.requestAddLauncher(url); + } + } + + Timer { + id: iconGeometryTimer + + interval: 500 + repeat: false + + onTriggered: { + TaskTools.publishIconGeometries(taskList.children); + } + } + + Binding { + target: plasmoid + property: "status" + value: (tasksModel.anyTaskDemandsAttention + ? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus) + } + + Connections { + target: plasmoid + + onLocationChanged: { + // This is on a timer because the panel may not have + // settled into position yet when the location prop- + // erty updates. + iconGeometryTimer.start(); + } + } + + Connections { + target: plasmoid.configuration + + onLaunchersChanged: tasksModel.launcherList = plasmoid.configuration.launchers + onGroupingAppIdBlacklistChanged: tasksModel.groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; + onGroupingLauncherUrlBlacklistChanged: tasksModel.groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; + } + + TaskManagerApplet.DragHelper { + id: dragHelper + + dragIconSize: units.iconSizes.medium + } + + PlasmaCore.FrameSvgItem { + id: taskFrame + + visible: false; + + imagePath: "widgets/tasks"; + prefix: "normal" + } + + PlasmaCore.Svg { + id: taskSvg + + imagePath: "widgets/tasks" + } + + MouseHandler { + id: mouseHandler + + anchors.fill: parent + + target: taskList + } + + ToolTipDelegate { + id: toolTipDelegate + + visible: false + } + + TaskList { + id: taskList + + anchors { + left: parent.left + top: parent.top + } + + onWidthChanged: LayoutManager.layout(taskRepeater) + onHeightChanged: LayoutManager.layout(taskRepeater) + + flow: { + if (tasks.vertical) { + return plasmoid.configuration.forceStripes ? Flow.LeftToRight : Flow.TopToBottom + } + return plasmoid.configuration.forceStripes ? Flow.TopToBottom : Flow.LeftToRight + } + + onAnimatingChanged: { + if (!animating) { + TaskTools.publishIconGeometries(children); + } + } + + function layout() { + taskList.width = LayoutManager.layoutWidth(); + taskList.height = LayoutManager.layoutHeight(); + LayoutManager.layout(taskRepeater); + } + + Timer { + id: layoutTimer + + interval: 0 + repeat: false + + onTriggered: taskList.layout() + } + + Repeater { + id: taskRepeater + + delegate: Task {} + + onItemAdded: taskList.layout() + onItemRemoved: taskList.layout() + } + } + + GroupDialog { id: groupDialog } + + function hasLauncher(url) { + return tasksModel.launcherPosition(url) != -1; + } + + function addLauncher(url) { + tasksModel.requestAppendLauncher(url); + } + + function resetDragSource() { + dragSource = null; + } + + Component.onCompleted: { + tasks.requestLayout.connect(layoutTimer.restart); + tasks.requestLayout.connect(iconGeometryTimer.restart); + tasks.windowsHovered.connect(backend.windowsHovered); + tasks.presentWindows.connect(backend.presentWindows); + mouseHandler.urlDropped.connect(backend.urlDropped); + dragHelper.dropped.connect(resetDragSource); + } +} diff --git a/applets/taskmanager/package/metadata.desktop b/applets/taskmanager/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/applets/taskmanager/package/metadata.desktop @@ -0,0 +1,173 @@ +[Desktop Entry] +Name=Task Manager +Name[af]=Taakbestuurder +Name[ar]=مدير المهام +Name[ast]=Xestor de xeres +Name[be]=Кіраванне заданнямі +Name[be@latin]=Kiraŭnik zadańniaŭ +Name[bg]=Управление на задачи +Name[bn]=টাস্ক ম্যানেজার +Name[bs]=Menadžer zadataka +Name[ca]=Gestor de tasques +Name[ca@valencia]=Gestor de tasques +Name[cs]=Správce úloh +Name[csb]=Menedżer dzejaniów +Name[da]=Opgavelinje +Name[de]=Fensterleiste +Name[el]=Διαχειριστής εργασιών +Name[en_GB]=Task Manager +Name[eo]=Taskadministrilo +Name[es]=Gestor de tareas +Name[et]=Tegumihaldur +Name[eu]=Ataza-kudeatzailea +Name[fi]=Tehtävienhallinta +Name[fr]=Gestionnaire de tâches +Name[fy]=Taakbehearder +Name[ga]=Bainisteoir na dTascanna +Name[gl]=Xestor de tarefas +Name[gu]=ટાસ્ક વ્યવસ્થાપક +Name[he]=מנהל משימות +Name[hi]=कार्य प्रबंधक +Name[hne]=काम प्रबंधक +Name[hr]=Upravljanje zadacima +Name[hsb]=Rjadowar nadawkow +Name[hu]=Feladatkezelő +Name[ia]=Gerente de carga +Name[id]=Manajer Tugas +Name[is]=Verkefnastjóri +Name[it]=Gestore dei processi +Name[ja]=タスクマネージャ +Name[kk]=Тапсырмалар менеджері +Name[km]=កម្មវិធី​គ្រប់គ្រង​ភារកិច្ច +Name[kn]=ಕಾರ್ಯ ವ್ಯವಸ್ಥಾಪಕ (ಟಾಸ್ಕ್ ಮ್ಯಾನೇಜರ್) +Name[ko]=작업 관리자 +Name[lt]=Užduočių tvarkyklė +Name[lv]=Uzdevumu pārvaldnieks +Name[mk]=Менаџер на апликации +Name[ml]=ടാസ്ക് മാനേജര്‍ +Name[mr]=कार्य व्यवस्थापक +Name[nb]=Oppgavebehandler +Name[nds]=Opgavenpleger +Name[ne]=कार्य प्रबन्धक +Name[nl]=Takenbeheer +Name[nn]=Oppgåvehandsamar +Name[or]=କାର୍ଯ୍ୟ ପରିଚାଳକ +Name[pa]=ਟਾਸਕ ਮੈਨੇਜਰ +Name[pl]=Zarządzanie zadaniami +Name[pt]=Gestor de Tarefas +Name[pt_BR]=Gerenciador de tarefas +Name[ro]=Gestionar de sarcini +Name[ru]=Панель задач +Name[se]=Bargogieđahalli +Name[si]=කාර්‍යය කළමණාකරු +Name[sk]=Správca úloh +Name[sl]=Upravljalnik opravil +Name[sr]=менаџер задатака +Name[sr@ijekavian]=менаџер задатака +Name[sr@ijekavianlatin]=menadžer zadataka +Name[sr@latin]=menadžer zadataka +Name[sv]=Aktivitetshanterare +Name[ta]=பணி மேலாளர் +Name[te]=కర్తవ్య నిర్వాహకి +Name[tg]=Мудири вазифаҳо +Name[th]=จัดการงาน +Name[tr]=Görev Yöneticisi +Name[ug]=ۋەزىپە باشقۇرغۇچ +Name[uk]=Менеджер задач +Name[uz]=Vazifa boshqaruvchisi +Name[uz@cyrillic]=Вазифа бошқарувчиси +Name[wa]=Manaedjeu des bouyes +Name[x-test]=xxTask Managerxx +Name[zh_CN]=任务管理器 +Name[zh_TW]=工作管理員 +Comment=Switch between running applications +Comment[ar]=بدّل بين التطبيقات التي تعمل +Comment[be@latin]=Pieraklučeńnie dziejnych aplikacyjaŭ +Comment[bg]=Превключване между стартирани програми +Comment[bs]=Prebacujte između programa u radu +Comment[ca]=Commuta entre aplicacions en execució +Comment[ca@valencia]=Commuta entre aplicacions en execució +Comment[cs]=Přepínač mezi běžícími aplikacemi +Comment[da]=Skift mellem kørende programmer +Comment[de]=Ermöglicht den wechselnden Zugriff auf laufende Programme. +Comment[el]=Εναλλαγή μεταξύ εκτελούμενων εφαρμογών +Comment[en_GB]=Switch between running applications +Comment[eo]=Komuti inter funkciantaj aplikaĵoj +Comment[es]=Cambiar entre aplicaciones en ejecución +Comment[et]=Lülitumine töötavate rakenduste vahel +Comment[eu]=Aldatu abian dauden aplikazio batetik bestera +Comment[fi]=Vaihda avoinna olevien ohjelmien välillä +Comment[fr]=Passe d'une application à l'autre +Comment[fy]=Wikselje tusken rinnende programma's +Comment[ga]=Athraigh idir feidhmchláir atá ag rith +Comment[gl]=Troca entre programas en execución +Comment[gu]=ચાલતાં કાર્યક્રમો વચ્ચે ફેરબદલી કરો +Comment[he]=מעבר בין יישומים פעילים +Comment[hi]=चल रहे अनुप्रयोगों के बीच स्विच करें +Comment[hne]=चलत अनुपरयोग मं स्विच करव +Comment[hr]=Promjena među pokrenutim aplikacijama +Comment[hsb]=Šaltuje mjez běžacymi aplikacijemi +Comment[hu]=A futó alkalmazások között lehet vele váltani +Comment[ia]=Commuta inter applicationes que on exeque +Comment[id]=Berpindah di antara aplikasi yang berjalan +Comment[is]=Skipta á milli forrita sem eru í gangi +Comment[it]=Passa ad altre applicazioni in esecuzione +Comment[ja]=実行中のアプリケーションを切り替えます +Comment[kk]=Жегілген қолданбаларды ақтару +Comment[km]=ប្ដូរ​រវាង​កម្មវិធី​ដែល​កំពុង​រត់ +Comment[kn]=ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅನ್ವಯಗಳ ನಡುವೆ ಅಂತರಿಸು +Comment[ko]=실행 중인 프로그램을 전환합니다 +Comment[lt]=Perjungimas tarp veikiančių programų +Comment[lv]=Pārslēdzas starp palaistajām programmām +Comment[mk]=Движете се низ активните апликации +Comment[ml]=പ്രവര്‍ത്തിക്കുന്ന പ്രവര്‍ത്തങ്ങള്‍ തമ്മില്‍ മാറുക +Comment[mr]=कार्यरत अनुप्रयोग अंतर्गत बदल करा +Comment[nb]=Bytt mellom kjørende programmer +Comment[nds]=Twischen lopen Programmen wesseln +Comment[nl]=Schakel tussen draaiende programma's +Comment[nn]=Byt mellom program som køyrer +Comment[or]=ଚାଲୁଥିବା ପ୍ରୟୋଗଗୁଡ଼ିକ ମଧ୍ଯରେ ଅଦଳ ବଦଳ କରନ୍ତୁ +Comment[pa]=ਚੱਲਦੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਬਦਲੋ +Comment[pl]=Przełącza pomiędzy uruchomionymi programami +Comment[pt]=Mudar de aplicações em execução +Comment[pt_BR]=Alterna entre os aplicativos em execução +Comment[ro]=Comută printre aplicațiile ce rulează +Comment[ru]=Переключение между запущенными приложениями +Comment[si]=ක්‍රියාත්මක වන යෙදුම් අතරේ මාරුවන්න +Comment[sk]=Prepínanie medzi bežiacimi aplikáciami +Comment[sl]=Preklapljajte med zagnanimi programi +Comment[sr]=Пребацујте између програма у раду +Comment[sr@ijekavian]=Пребацујте између програма у раду +Comment[sr@ijekavianlatin]=Prebacujte između programa u radu +Comment[sr@latin]=Prebacujte između programa u radu +Comment[sv]=Byt mellan program som kör +Comment[ta]=Switch between running applications +Comment[te]=నడుస్తున్న అనువర్తనముల మద్యన మారుము +Comment[tg]=Переключение между рабочими столами +Comment[th]=สลับการทำงานระหว่างโปรแกรมต่าง ๆ +Comment[tr]=Çalışan uygulamalar arasında gezin +Comment[ug]=ئىجرا بولۇۋاتقان پروگراممىلارنى ئالماشتۇر +Comment[uk]=Перемкніть запущені програми +Comment[vi]=Chuyển đổi giữa các ứng dụng đang chạy +Comment[wa]=Passer d' on programe ovrant a èn ôte +Comment[x-test]=xxSwitch between running applicationsxx +Comment[zh_CN]=在运行中的应用程序间切换 +Comment[zh_TW]=在執行中的應用程式間切換 +Type=Service +Icon=preferences-system-windows +X-KDE-ServiceTypes=Plasma/Applet + +X-Plasma-API=declarativeappletscript +X-Plasma-MainScript=ui/main.qml +X-Plasma-Provides=org.kde.plasma.multitasking + +X-KDE-PluginInfo-Author=Eike Hein +X-KDE-PluginInfo-Email=hein@kde.org +X-KDE-PluginInfo-Name=org.kde.plasma.taskmanager +X-KDE-PluginInfo-Version=4.0 +X-KDE-PluginInfo-Website=http://userbase.kde.org/Plasma/Tasks +X-KDE-PluginInfo-Category=Windows and Tasks +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL v2+ +X-KDE-PluginInfo-EnabledByDefault=true + diff --git a/applets/taskmanager/plugin/backend.h b/applets/taskmanager/plugin/backend.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/backend.h @@ -0,0 +1,102 @@ +/*************************************************************************** + * Copyright (C) 2013-2016 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#ifndef BACKEND_H +#define BACKEND_H + +#include +#include + +#include + +class QAction; +class QActionGroup; +class QQuickItem; +class QQuickWindow; + +namespace KActivities { + class Consumer; +} + +class Backend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem* taskManagerItem READ taskManagerItem WRITE setTaskManagerItem NOTIFY taskManagerItemChanged) + Q_PROPERTY(QQuickItem* toolTipItem READ toolTipItem WRITE setToolTipItem NOTIFY toolTipItemChanged) + Q_PROPERTY(bool highlightWindows READ highlightWindows WRITE setHighlightWindows NOTIFY highlightWindowsChanged) + + public: + enum MiddleClickAction { + None = 0, + Close, + NewInstance + }; + + Q_ENUM(MiddleClickAction); + + Backend(QObject *parent = 0); + ~Backend(); + + QQuickItem *taskManagerItem() const; + void setTaskManagerItem(QQuickItem *item); + + QQuickItem *toolTipItem() const; + void setToolTipItem(QQuickItem *item); + + bool highlightWindows() const; + void setHighlightWindows(bool highlight); + + Q_INVOKABLE QVariantList jumpListActions(const QUrl &launcherUrl, QObject *parent); + Q_INVOKABLE QVariantList recentDocumentActions(const QUrl &launcherUrl, QObject *parent); + Q_INVOKABLE void setActionGroup(QAction *action) const; + + Q_INVOKABLE QRect globalRect(QQuickItem *item) const; + + Q_INVOKABLE bool canPresentWindows() const; + + public Q_SLOTS: + void presentWindows(const QVariant &winIds); + void windowsHovered(const QVariant &winIds, bool hovered); + void urlDropped(const QUrl &url) const; + + Q_SIGNALS: + void taskManagerItemChanged() const; + void toolTipItemChanged() const; + void highlightWindowsChanged() const; + void addLauncher(const QUrl &url) const; + + private Q_SLOTS: + void toolTipWindowChanged(QQuickWindow *window); + void handleJumpListAction() const; + void handleRecentDocumentAction() const; + + private: + void updateWindowHighlight(); + + QQuickItem *m_taskManagerItem; + QQuickItem *m_toolTipItem; + WId m_panelWinId; + bool m_highlightWindows; + QList m_windowsToHighlight; + QActionGroup *m_actionGroup; + KActivities::Consumer *m_activitiesConsumer; +}; + +#endif diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/backend.cpp @@ -0,0 +1,394 @@ +/*************************************************************************** + * Copyright (C) 2012-2016 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#include "backend.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +Backend::Backend(QObject* parent) : QObject(parent) + , m_taskManagerItem(0) + , m_toolTipItem(0) + , m_panelWinId(0) + , m_highlightWindows(false) + , m_actionGroup(new QActionGroup(this)) +{ +} + +Backend::~Backend() +{ +} + +QQuickItem *Backend::taskManagerItem() const +{ + return m_taskManagerItem; +} + +void Backend::setTaskManagerItem(QQuickItem* item) +{ + if (item != m_taskManagerItem) { + m_taskManagerItem = item; + + emit taskManagerItemChanged(); + } +} + +QQuickItem *Backend::toolTipItem() const +{ + return m_toolTipItem; +} + +void Backend::setToolTipItem(QQuickItem *item) +{ + if (item != m_toolTipItem) { + m_toolTipItem = item; + + connect(item, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(toolTipWindowChanged(QQuickWindow*))); + + emit toolTipItemChanged(); + } +} + +bool Backend::highlightWindows() const +{ + return m_highlightWindows; +} + +void Backend::setHighlightWindows(bool highlight) +{ + if (highlight != m_highlightWindows) { + m_highlightWindows = highlight; + + updateWindowHighlight(); + + emit highlightWindowsChanged(); + } +} + +QVariantList Backend::jumpListActions(const QUrl &launcherUrl, QObject *parent) +{ + QVariantList actions; + + if (!parent || !launcherUrl.isValid() || !launcherUrl.isLocalFile() + || !KDesktopFile::isDesktopFile(launcherUrl.toLocalFile())) { + return actions; + } + + KDesktopFile desktopFile(launcherUrl.toLocalFile()); + + const QStringList &jumpListActions = desktopFile.readActions(); + + int count = 0; + + foreach (const QString &actionName, jumpListActions) { + const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); + + if (!actionGroup.isValid() || !actionGroup.exists()) { + continue; + } + + const QString &name = actionGroup.readEntry(QStringLiteral("Name")); + const QString &exec = actionGroup.readEntry(QStringLiteral("Exec")); + if (name.isEmpty() || exec.isEmpty()) { + continue; + } + + QAction *action = new QAction(this); + action->setText(name); + action->setIcon(QIcon::fromTheme(actionGroup.readEntry("Icon"))); + action->setProperty("exec", exec); + // so we can show the proper application name and icon when it launches + action->setProperty("applicationName", desktopFile.readName()); + action->setProperty("applicationIcon", desktopFile.readIcon()); + connect(action, &QAction::triggered, this, &Backend::handleJumpListAction); + + actions << QVariant::fromValue(action); + + ++count; + } + + return actions; +} + +QVariantList Backend::recentDocumentActions(const QUrl &launcherUrl, QObject *parent) +{ + QVariantList actions; + + if (!parent || !launcherUrl.isValid() || !launcherUrl.isLocalFile() + || !KDesktopFile::isDesktopFile(launcherUrl.toLocalFile())) { + return actions; + } + + QString desktopName = launcherUrl.fileName(); + QString storageId = desktopName; + + if (storageId.startsWith(QLatin1String("org.kde."))) { + storageId = storageId.right(storageId.length() - 8); + } + + if (storageId.endsWith(QLatin1String(".desktop"))) { + storageId = storageId.left(storageId.length() - 8); + } + + auto query = UsedResources + | RecentlyUsedFirst + | Agent(storageId) + | Type::any() + | Activity::current() + | Url::file(); + + ResultSet results(query); + + ResultSet::const_iterator resultIt = results.begin(); + + int actionCount = 0; + + while (actionCount < 5 && resultIt != results.end()) { + const QString resource = (*resultIt).resource(); + const QUrl url(resource); + + if (!url.isValid()) { + continue; + } + + const KFileItem fileItem(url); + + if (!fileItem.isFile()) { + continue; + } + + QAction *action = new QAction(this); + action->setText(url.fileName()); + action->setIcon(QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme("unknown"))); + action->setProperty("agent", storageId); + action->setProperty("entryPath", launcherUrl); + action->setData(resource); + connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); + + actions << QVariant::fromValue(action); + + ++resultIt; + ++actionCount; + } + + if (actionCount > 0) { + QAction *action = new QAction(this); + action->setText(i18n("Forget Recent Documents")); + action->setProperty("agent", storageId); + connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); + + actions << QVariant::fromValue(action); + } + + return actions; +} + +void Backend::toolTipWindowChanged(QQuickWindow *window) +{ + Q_UNUSED(window) + + updateWindowHighlight(); +} + +void Backend::handleJumpListAction() const +{ + const QAction *action = qobject_cast(sender()); + + if (!action) { + return; + } + + KRun::run(action->property("exec").toString(), {}, nullptr, + action->property("applicationName").toString(), + action->property("applicationIcon").toString()); +} + +void Backend::handleRecentDocumentAction() const +{ + const QAction *action = qobject_cast(sender()); + + if (!action) { + return; + } + + const QString agent = action->property("agent").toString(); + + if (agent.isEmpty()) { + return; + } + + const QString desktopPath = action->property("entryPath").toUrl().toLocalFile(); + const QString resource = action->data().toString(); + + if (desktopPath.isEmpty() || resource.isEmpty()) { + auto query = UsedResources + | Agent(agent) + | Type::any() + | Activity::current() + | Url::file(); + + KAStats::forgetResources(query); + + return; + } + + KService::Ptr service = KService::serviceByDesktopPath(desktopPath); + + qDebug() << service; + + if (!service) { + return; + } + + KRun::runService(*service, QList() << QUrl(resource), QApplication::activeWindow()); +} + +void Backend::setActionGroup(QAction *action) const +{ + if (action) { + action->setActionGroup(m_actionGroup); + } +} + +QRect Backend::globalRect(QQuickItem *item) const +{ + if (!item || !item->window()) { + return QRect(); + } + + QRect iconRect(item->x(), item->y(), item->width(), item->height()); + iconRect.moveTopLeft(item->parentItem()->mapToScene(iconRect.topLeft()).toPoint()); + iconRect.moveTopLeft(item->window()->mapToGlobal(iconRect.topLeft())); + + return iconRect; +} + +bool Backend::canPresentWindows() const +{ + return (KWindowSystem::compositingActive() && KWindowEffects::isEffectAvailable(KWindowEffects::PresentWindowsGroup)); +} + +void Backend::presentWindows(const QVariant &_winIds) +{ + QList winIds; + + const QVariantList &_winIdsList = _winIds.toList(); + + foreach(const QVariant &_winId, _winIdsList) { + bool ok = false; + qlonglong winId = _winId.toLongLong(&ok); + + if (ok) { + winIds.append(winId); + } + } + + if (!winIds.count()) { + return; + } + + if (m_windowsToHighlight.count()) { + m_windowsToHighlight.clear(); + updateWindowHighlight(); + } + + KWindowEffects::presentWindows(m_taskManagerItem->window()->winId(), winIds); +} + +void Backend::windowsHovered(const QVariant &_winIds, bool hovered) +{ + m_windowsToHighlight.clear(); + + if (hovered) { + const QVariantList &winIds = _winIds.toList(); + + foreach(const QVariant &_winId, winIds) { + bool ok = false; + qlonglong winId = _winId.toLongLong(&ok); + + if (ok) { + m_windowsToHighlight.append(winId); + } + } + } + + updateWindowHighlight(); +} + +void Backend::urlDropped(const QUrl &url) const +{ + if (!url.isValid() || !url.isLocalFile()) { + return; + } + + KDesktopFile desktopFile(url.toLocalFile()); + + if (desktopFile.hasApplicationType()) { + emit addLauncher(url); + } +} + +void Backend::updateWindowHighlight() +{ + if (!m_highlightWindows) { + if (m_panelWinId) { + KWindowEffects::highlightWindows(m_panelWinId, QList()); + + m_panelWinId = 0; + } + + return; + } + + if (m_taskManagerItem && m_taskManagerItem->window()) { + m_panelWinId = m_taskManagerItem->window()->winId(); + } else { + return; + } + + QList windows = m_windowsToHighlight; + + if (windows.count() && m_toolTipItem && m_toolTipItem->window()) { + windows.append(m_toolTipItem->window()->winId()); + } + + KWindowEffects::highlightWindows(m_panelWinId, windows); +} diff --git a/applets/taskmanager/plugin/blur.cpp b/applets/taskmanager/plugin/blur.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/blur.cpp @@ -0,0 +1,154 @@ +#ifndef BLUR_CPP +#define BLUR_CPP + +/* + * Copyright 2007 Jani Huhtanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * 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 Library 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. + */ + +#include + +// Exponential blur, Jani Huhtanen, 2006 +// +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha); + +template +static inline void blurrow(QImage &im, int line, int alpha); + +template +static inline void blurcol(QImage &im, int col, int alpha); + +/* +* expblur(QImage &img, int radius) +* +* In-place blur of image 'img' with kernel +* of approximate radius 'radius'. +* +* Blurs with two sided exponential impulse +* response. +* +* aprec = precision of alpha parameter +* in fixed-point format 0.aprec +* +* zprec = precision of state parameters +* zR,zG,zB and zA in fp format 8.zprec +*/ +template +void expblur(QImage &img, int radius) +{ + if (radius < 1) { + return; + } + + img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + /* Calculate the alpha such that 90% of + the kernel is within the radius. + (Kernel extends to infinity) + */ + int alpha = (int)((1 << aprec) * (1.0f - std::exp(-2.3f / (radius + 1.f)))); + + int height = img.height(); + int width = img.width(); + for (int row=0; row(img, row, alpha); + } + + for (int col=0; col(img, col, alpha); + } + return; +} + +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + int R, G, B, A; + R = *bptr; + G = *(bptr + 1); + B = *(bptr + 2); + A = *(bptr + 3); + + zR += (alpha * ((R << zprec) - zR)) >> aprec; + zG += (alpha * ((G << zprec) - zG)) >> aprec; + zB += (alpha * ((B << zprec) - zB)) >> aprec; + zA += (alpha * ((A << zprec) - zA)) >> aprec; + + *bptr = zR >> zprec; + *(bptr+1) = zG >> zprec; + *(bptr+2) = zB >> zprec; + *(bptr+3) = zA >> zprec; +} + +template +static inline void blurrow(QImage &im, int line, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.scanLine(line); + int width = im.width(); + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=1; index((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } + for (int index=width-2; index>=0; index--) { + blurinner((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } +} + +template +static inline void blurcol(QImage &im, int col, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.bits(); + ptr += col; + int height = im.height(); + int width = im.width(); + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=width; index<(height-1)*width; index+=width) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } + + for (int index=(height-2)*width; index>=0; index-=width) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } +} + +template +inline const T &qClamp(const T &x, const T &low, const T &high) +{ + if (x < low) { + return low; + } else if (x > high) { + return high; + } else { + return x; + } +} + +#endif diff --git a/applets/taskmanager/plugin/draghelper.h b/applets/taskmanager/plugin/draghelper.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/draghelper.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#ifndef DRAGHELPER_H +#define DRAGHELPER_H + +#include + +class QIcon; +class QQuickItem; +class QUrl; + +class DragHelper : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int dragIconSize READ dragIconSize WRITE setDragIconSize NOTIFY dragIconSizeChanged) + + public: + DragHelper(QObject *parent = 0); + ~DragHelper(); + + int dragIconSize() const; + void setDragIconSize(int size); + + Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; + Q_INVOKABLE void startDrag(QQuickItem* item, const QString &mimeType, + const QVariant &mimeData, const QUrl &url, const QIcon &icon); + + Q_SIGNALS: + void dragIconSizeChanged() const; + void dropped() const; + + private Q_SLOTS: + void startDragInternal(QQuickItem* item, const QString &mimeType, + const QVariant &mimeData, const QUrl &url, const QIcon &icon) const; + + private: + int m_dragIconSize; +}; + +#endif diff --git a/applets/taskmanager/plugin/draghelper.cpp b/applets/taskmanager/plugin/draghelper.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/draghelper.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#include "draghelper.h" + +#include +#include +#include +#include +#include +#include +#include + +DragHelper::DragHelper(QObject* parent) : QObject(parent) +, m_dragIconSize(32) +{ +} + +DragHelper::~DragHelper() +{ +} + +int DragHelper::dragIconSize() const +{ + return m_dragIconSize; +} + +void DragHelper::setDragIconSize(int size) +{ + if (m_dragIconSize != size) { + m_dragIconSize = size; + + emit dragIconSizeChanged(); + } +} + +bool DragHelper::isDrag(int oldX, int oldY, int newX, int newY) const +{ + return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= QApplication::startDragDistance()); +} + +void DragHelper::startDrag(QQuickItem *item, const QString &mimeType, + const QVariant &mimeData, const QUrl &url, const QIcon &icon) +{ + QMetaObject::invokeMethod(this, "startDragInternal", Qt::QueuedConnection, + Q_ARG(QQuickItem*, item), + Q_ARG(QString, mimeType), + Q_ARG(QVariant, mimeData), + Q_ARG(QUrl, url), + Q_ARG(QIcon, icon)); +} + +void DragHelper::startDragInternal(QQuickItem *item, const QString &mimeType, + const QVariant &mimeData, const QUrl &url, const QIcon &icon) const +{ + QPointer grabber = item; + + QList urlList; + urlList.append(url); + + QMimeData *dragData = new QMimeData(); + dragData->setData(mimeType, mimeData.toByteArray()); + dragData->setData("application/x-orgkdeplasmataskmanager_taskbuttonitem", mimeData.toByteArray()); + dragData->setUrls(urlList); + + QDrag *drag = new QDrag(static_cast(parent())); + drag->setMimeData(dragData); + drag->setPixmap(icon.pixmap(QSize(m_dragIconSize, m_dragIconSize))); + + grabber->grabMouse(); + + drag->exec(); + + if (grabber) { + grabber->ungrabMouse(); + } + + emit dropped(); +} + diff --git a/applets/taskmanager/plugin/qmldir b/applets/taskmanager/plugin/qmldir new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/qmldir @@ -0,0 +1,2 @@ +module org.kde.plasma.private.taskmanager +plugin taskmanagerplugin diff --git a/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.h b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.h @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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 . * + ***************************************************************************/ + +#ifndef SMARTLAUNCHER_BACKEND_H +#define SMARTLAUNCHER_BACKEND_H + +#include +#include +#include +#include + +#include + +class QDBusServiceWatcher; +class QString; + +namespace Plasma { +class DataEngineConsumer; +} + +namespace SmartLauncher { + +struct Entry +{ + int count = 0; + bool countVisible = false; + int progress = 0; + bool progressVisible = false; + bool urgent = false; +}; + +class Backend : public QObject, protected QDBusContext +{ + Q_OBJECT + +public: + explicit Backend(QObject *parent = nullptr); + virtual ~Backend(); + + bool available() const; + bool hasLauncher(const QString &storageId) const; + + int count(const QString &uri) const; + bool countVisible(const QString &uri) const; + int progress(const QString &uri) const; + bool progressVisible(const QString &uri) const; + bool urgent(const QString &uri) const; + + QHash unityMappingRules() const; + +public slots: + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +signals: + void countChanged(const QString &uri, int count); + void countVisibleChanged(const QString &uri, bool countVisible); + void progressChanged(const QString &uri, int progress); + void progressVisibleChanged(const QString &uri, bool progressVisible); + void urgentChanged(const QString &uri, bool urgent); + + void launcherRemoved(const QString &uri); + +private slots: + void update(const QString &uri, const QMap &properties); + +private: + bool setupUnity(); + bool setupApplicationJobs(); + + void onServiceUnregistered(const QString &service); + + template + void updateLauncherProperty(const QString &storageId, // our KService storage id + const QVariantMap &properties, // the map of properties we're given by DBus + const QString &property, // the property we're looking for + T *entryMember, // the member variable we're going to write our result in + // the change signal that will be emitted if the property has changed + void (Backend::*changeSignal)(const QString &, T)) + { + auto foundProperty = properties.constFind(property); + if (foundProperty != properties.constEnd()) { + T newValue = foundProperty->value(); + + if (newValue != *entryMember) { + *entryMember = newValue; + emit ((this)->*changeSignal)(storageId, newValue); + } + } + } + + void onApplicationJobAdded(const QString &source); + void onApplicationJobRemoved(const QString &source); + void updateApplicationJobPercent(const QString &storageId, Entry *entry); + + // Unity Launchers + QDBusServiceWatcher *m_watcher; + QHash m_dbusServiceToLauncherUrl; + QHash m_launcherUrlToStorageId; + // these rules can be configured in the taskmanagerrulesrc in the "Unity Launcher Mapping" section + // key is the actual desktop file name of the application (some-broken-app-beta.desktop) + // vaue is how it actually announces itself on the Unity API (some-broken-app.desktop) + QHash m_unityMappingRules; + + // Application Jobs + Plasma::DataEngineConsumer *m_dataEngineConsumer; + Plasma::DataEngine *m_dataEngine; + QHash m_dataSourceToStorageId; // + QHash m_storageIdToJobs; // > + QHash m_jobProgress; // + + QHash m_launchers; + + bool m_available = false; + +}; + +} // namespace SmartLauncher + +#endif // SMARTLAUNCHER_BACKEND_H diff --git a/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp @@ -0,0 +1,345 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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 . * + ***************************************************************************/ + +#include "smartlauncherbackend.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace SmartLauncher; + +Backend::Backend(QObject *parent) + : QObject(parent) + , m_watcher(new QDBusServiceWatcher(this)) + , m_dataEngineConsumer(new Plasma::DataEngineConsumer) + , m_dataEngine(m_dataEngineConsumer->dataEngine(QStringLiteral("applicationjobs"))) +{ + m_available = setupUnity(); + m_available = setupApplicationJobs() || m_available; +} + +Backend::~Backend() +{ + delete m_dataEngineConsumer; +} + +bool Backend::setupUnity() +{ + auto sessionBus = QDBusConnection::sessionBus(); + + if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) { + qWarning() << "Failed to register unity service"; + return false; + } + + if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) { + qWarning() << "Failed to register unity object"; + return false; + } + + if (!sessionBus.connect({}, {}, QStringLiteral("com.canonical.Unity.LauncherEntry"), + QStringLiteral("Update"), this, SLOT(update(QString,QMap)))) { + qWarning() << "failed to register Update signal"; + return false; + } + + KConfig cfg(QStringLiteral("taskmanagerrulesrc")); + KConfigGroup grp(&cfg, QStringLiteral("Unity Launcher Mapping")); + + foreach (const QString &key, grp.keyList()) { + const QString &value = grp.readEntry(key, QString()); + if (value.isEmpty()) { + continue; + } + + m_unityMappingRules.insert(key, value); + } + + return true; +} + +bool Backend::setupApplicationJobs() +{ + if (!m_dataEngine->isValid()) { + qWarning() << "Failed to setup application jobs, data engine is not valid"; + return false; + } + + const QStringList &sources = m_dataEngine->sources(); + for (const QString &source : sources) { + onApplicationJobAdded(source); + } + + connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &Backend::onApplicationJobAdded); + connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &Backend::onApplicationJobRemoved); + + return true; +} + +bool Backend::available() const +{ + return m_available; +} + +bool Backend::hasLauncher(const QString &storageId) const +{ + return m_launchers.contains(storageId); +} + +int Backend::count(const QString &uri) const +{ + return m_launchers.value(uri).count; +} + +bool Backend::countVisible(const QString &uri) const +{ + return m_launchers.value(uri).countVisible; +} + +int Backend::progress(const QString &uri) const +{ + return m_launchers.value(uri).progress; +} + +bool Backend::progressVisible(const QString &uri) const +{ + return m_launchers.value(uri).progressVisible; +} + +bool Backend::urgent(const QString &uri) const +{ + return m_launchers.value(uri).urgent; +} + +QHash Backend::unityMappingRules() const +{ + return m_unityMappingRules; +} + +void Backend::update(const QString &uri, const QMap &properties) +{ + Q_ASSERT(calledFromDBus()); + + QString storageId; + + auto foundStorageId = m_launcherUrlToStorageId.constFind(uri); + if (foundStorageId == m_launcherUrlToStorageId.constEnd()) { // we don't know this one, register + KService::Ptr service = KService::serviceByStorageId(uri); + if (!service) { + qWarning() << "Failed to find service for Unity Launcher" << uri; + return; + } + + storageId = service->storageId(); + m_launcherUrlToStorageId.insert(uri, storageId); + + m_dbusServiceToLauncherUrl.insert(message().service(), uri); + m_watcher->addWatchedService(message().service()); + } else { + storageId = *foundStorageId; + } + + auto foundEntry = m_launchers.find(storageId); + if (foundEntry == m_launchers.end()) { // we don't have it yet, create a new Entry + Entry entry; + foundEntry = m_launchers.insert(storageId, entry); + } + + auto propertiesEnd = properties.constEnd(); + + auto foundCount = properties.constFind(QStringLiteral("count")); + if (foundCount != propertiesEnd) { + qint64 newCount = foundCount->toLongLong(); + // 2 billion unread emails ought to be enough for anybody + if (newCount < std::numeric_limits::max()) { + int saneCount = static_cast(newCount); + if (saneCount != foundEntry->count) { + foundEntry->count = saneCount; + emit countChanged(storageId, saneCount); + } + } + } + + updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::countChanged); + updateLauncherProperty(storageId, properties, QStringLiteral("count-visible"), &foundEntry->countVisible, &Backend::countVisibleChanged); + + // the API gives us progress as 0..1 double but we'll use percent to avoid unneccessary + // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method + auto foundProgress = properties.constFind(QStringLiteral("progress")); + if (foundProgress != propertiesEnd) { + int newProgress = qRound(foundProgress->toDouble() * 100); + if (newProgress != foundEntry->progress) { + foundEntry->progress = newProgress; + emit progressChanged(storageId, newProgress); + } + } + + updateLauncherProperty(storageId, properties, QStringLiteral("progress-visible"), &foundEntry->progressVisible, &Backend::progressVisibleChanged); + updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgentChanged); +} + +void Backend::onServiceUnregistered(const QString &service) +{ + const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service); + if (launcherUrl.isEmpty()) { + return; + } + + const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl); + if (storageId.isEmpty()) { + return; + } + + m_launchers.remove(storageId); + emit launcherRemoved(storageId); +} + +void Backend::onApplicationJobAdded(const QString &source) +{ + m_dataEngine->connectSource(source, this); +} + +void Backend::onApplicationJobRemoved(const QString &source) +{ + m_dataEngine->disconnectSource(source, this); + + const QString &storageId = m_dataSourceToStorageId.take(source); + if (storageId.isEmpty()) { + return; + } + + // remove job, calculate new percentage, or remove launcher if gone altogether + auto &jobs = m_storageIdToJobs[storageId]; + jobs.removeOne(source); + if (jobs.isEmpty()) { + m_storageIdToJobs.remove(storageId); + } + + m_jobProgress.remove(source); + + auto foundEntry = m_launchers.find(storageId); + if (foundEntry == m_launchers.end()) { + qWarning() << "Cannot remove application job" << source << "as we don't know" << storageId; + return; + } + + updateApplicationJobPercent(storageId, &*foundEntry); + + if (!foundEntry->progressVisible && !foundEntry->progress) { + // no progress anymore whatsoever, remove entire launcher + m_launchers.remove(storageId); + emit launcherRemoved(storageId); + } +} + +void Backend::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + QString storageId; + + auto foundStorageId = m_dataSourceToStorageId.constFind(sourceName); + if (foundStorageId == m_dataSourceToStorageId.constEnd()) { // we don't know this one, register + QString appName = data.value(QStringLiteral("appName")).toString(); + if (appName.isEmpty()) { + qWarning() << "Application jobs got update for" << sourceName << "without app name"; + return; + } + + KService::Ptr service = KService::serviceByStorageId(appName); + if (!service) { + appName.prepend(QStringLiteral("org.kde.")); + // HACK try to find a service with org.kde. notation + service = KService::serviceByStorageId(appName); + if (!service) { + qWarning() << "Could not find service for job" << sourceName << "with app name" << appName; + return; + } + } + + storageId = service->storageId(); + m_dataSourceToStorageId.insert(sourceName, storageId); + } else { + storageId = *foundStorageId; + } + + auto foundEntry = m_launchers.find(storageId); + if (foundEntry == m_launchers.end()) { // we don't have it yet, create new Entry + Entry entry; + foundEntry = m_launchers.insert(storageId, entry); + } + + int percent = data.value(QStringLiteral("percentage"), 0).toInt(); + + // setup everything and calculate new percentage + auto &jobs = m_storageIdToJobs[storageId]; + if (!jobs.contains(sourceName)) { + jobs.append(sourceName); + } + + m_jobProgress.insert(sourceName, percent); // insert() overrides if exist + + updateApplicationJobPercent(storageId, &*foundEntry); +} + +void Backend::updateApplicationJobPercent(const QString &storageId, Entry *entry) +{ + // basically get all jobs for the given storageId and calculate an average progress + + const auto &jobs = m_storageIdToJobs.value(storageId); + qreal jobCount = jobs.count(); + + int totalProgress = 0; + for (const QString &job : jobs) { + totalProgress += m_jobProgress.value(job, 0); + } + + int progress = 0; + if (jobCount > 0) { + progress = qRound(totalProgress / jobCount); + } + + bool visible = (jobCount > 0); + + if (entry->count != jobCount) { + entry->count = jobCount; + emit countChanged(storageId, jobCount); + } + + if (entry->countVisible != visible) { + entry->countVisible = visible; + emit countVisibleChanged(storageId, visible); + } + + if (entry->progress != progress) { + entry->progress = progress; + emit progressChanged(storageId, progress); + } + + if (entry->progressVisible != visible) { + entry->progressVisible = visible; + emit progressVisibleChanged(storageId, visible); + } +} diff --git a/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.h b/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.h @@ -0,0 +1,104 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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 . * + ***************************************************************************/ + +#ifndef SMARTLAUNCHER_ITEM_H +#define SMARTLAUNCHER_ITEM_H + +#include +#include +#include +#include + +#include "smartlauncherbackend.h" + +namespace SmartLauncher { + +class Item : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl launcherUrl READ launcherUrl WRITE setLauncherUrl NOTIFY launcherUrlChanged) + + Q_PROPERTY(bool available READ available NOTIFY availableChanged) + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool countVisible READ countVisible NOTIFY countVisibleChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + Q_PROPERTY(bool progressVisible READ progressVisible NOTIFY progressVisibleChanged) + Q_PROPERTY(bool urgent READ urgent NOTIFY urgentChanged) + +public: + explicit Item(QObject *parent = nullptr); + virtual ~Item() = default; + + QUrl launcherUrl() const; + void setLauncherUrl(const QUrl &launcherUrl); + + bool available() const; + + int count() const; + bool countVisible() const; + int progress() const; + bool progressVisible() const; + bool urgent() const; + +signals: + void launcherUrlChanged(const QUrl &launcherUrl); + + void availableChanged(bool available); + + void countChanged(int count); + void countVisibleChanged(bool countVisible); + void progressChanged(int progress); + void progressVisibleChanged(bool progressVisible); + void urgentChanged(bool urgent); + +private: + void init(); + + void populate(); + void clear(); + + void setCount(int count); + void setCountVisible(bool countVisible); + void setProgress(int progress); + void setProgressVisible(bool progressVisible); + void setUrgent(bool urgent); + + static QWeakPointer s_backend; + + QSharedPointer m_backendPtr; + + QUrl m_launcherUrl; + QString m_storageId; + + bool m_available = false; + bool m_inited = false; + + int m_count = 0; + bool m_countVisible = false; + int m_progress = 0; + bool m_progressVisible = false; + bool m_urgent = false; + +}; + +} // namespace SmartLauncher + +#endif // SMARTLAUNCHER_ITEM_H diff --git a/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.cpp b/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/smartlaunchers/smartlauncheritem.cpp @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2016 Kai Uwe Broulik * + * * + * 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 . * + ***************************************************************************/ + +#include "smartlauncheritem.h" + +#include + +using namespace SmartLauncher; + +Item::Item(QObject *parent) : QObject(parent) +{ + m_backendPtr = s_backend.toStrongRef(); + if (!m_backendPtr) { + QSharedPointer backendSharedPtr(new Backend); + s_backend = backendSharedPtr; + m_backendPtr = s_backend.toStrongRef(); + } +} + +QWeakPointer Item::s_backend; + +void Item::init() +{ + if (m_inited || m_storageId.isEmpty() || !m_backendPtr || !m_backendPtr->available()) { + return; + } + + connect(m_backendPtr.data(), &Backend::launcherRemoved, this, [this](const QString &uri) { + if (m_storageId == uri) { + clear(); + } + }); + + connect(m_backendPtr.data(), &Backend::countChanged, this, [this](const QString &uri, int count) { + if (m_storageId == uri) { + setCount(count); + } + }); + + connect(m_backendPtr.data(), &Backend::countVisibleChanged, this, [this](const QString &uri, bool countVisible) { + if (m_storageId == uri) { + setCountVisible(countVisible); + } + }); + + connect(m_backendPtr.data(), &Backend::progressChanged, this, [this](const QString &uri, int progress) { + if (m_storageId == uri) { + setProgress(progress); + } + }); + + connect(m_backendPtr.data(), &Backend::progressVisibleChanged, this, [this](const QString &uri, bool progressVisible) { + if (m_storageId == uri) { + setProgressVisible(progressVisible); + } + }); + + connect(m_backendPtr.data(), &Backend::urgentChanged, this, [this](const QString &uri, bool urgent) { + if (m_storageId == uri) { + setUrgent(urgent); + } + }); + + m_available = true; + emit availableChanged(m_available); +} + +void Item::populate() +{ + if (!m_backendPtr || !m_backendPtr->available() || m_storageId.isEmpty()) { + return; + } + + if (!m_backendPtr->hasLauncher(m_storageId)) { + return; + } + + setCount(m_backendPtr->count(m_storageId)); + setCountVisible(m_backendPtr->countVisible(m_storageId)); + setProgress(m_backendPtr->progress(m_storageId)); + setProgressVisible(m_backendPtr->progressVisible(m_storageId)); + setUrgent(m_backendPtr->urgent(m_storageId)); +} + +void Item::clear() +{ + setCount(0); + setCountVisible(false); + setProgress(0); + setProgressVisible(false); + setUrgent(false); +} + +QUrl Item::launcherUrl() const +{ + return m_launcherUrl; +} + +void Item::setLauncherUrl(const QUrl &launcherUrl) +{ + if (launcherUrl != m_launcherUrl) { + m_launcherUrl = launcherUrl; + emit launcherUrlChanged(launcherUrl); + + KService::Ptr service = KService::serviceByStorageId(launcherUrl.toString()); // can we do better? + if (service) { + m_storageId = service->storageId(); + } + + if (m_backendPtr) { + // check if we have a mapping to a different desktop file + const QString &overrideStorageId = m_backendPtr->unityMappingRules().value(m_storageId); + if (!overrideStorageId.isEmpty()) { + m_storageId = overrideStorageId; + } + } + + init(); + populate(); + } +} + +bool Item::available() const +{ + return m_available; +} + +int Item::count() const +{ + return m_count; +} + +void Item::setCount(int count) +{ + if (m_count != count) { + m_count = count; + emit countChanged(count); + } +} + +bool Item::countVisible() const +{ + return m_countVisible; +} + +void Item::setCountVisible(bool countVisible) +{ + if (m_countVisible != countVisible) { + m_countVisible = countVisible; + emit countVisibleChanged(countVisible); + } +} + +int Item::progress() const +{ + return m_progress; +} + +void Item::setProgress(int progress) +{ + if (m_progress != progress) { + m_progress = progress; + emit progressChanged(progress); + } +} + +bool Item::progressVisible() const +{ + return m_progressVisible; +} + +void Item::setProgressVisible(bool progressVisible) +{ + if (m_progressVisible != progressVisible) { + m_progressVisible = progressVisible; + emit progressVisibleChanged(progressVisible); + } +} + +bool Item::urgent() const +{ + return m_urgent; +} + +void Item::setUrgent(bool urgent) +{ + if (m_urgent != urgent) { + m_urgent = urgent; + emit urgentChanged(urgent); + } +} diff --git a/applets/taskmanager/plugin/taskmanagerplugin.h b/applets/taskmanager/plugin/taskmanagerplugin.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/taskmanagerplugin.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#ifndef TASKMANAGERPLUGIN_H +#define TASKMANAGERPLUGIN_H + +#include +#include + +class TaskManagerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + + public: + virtual void registerTypes(const char *uri); +}; + +#endif // TASKMANAGERPLUGIN_H diff --git a/applets/taskmanager/plugin/taskmanagerplugin.cpp b/applets/taskmanager/plugin/taskmanagerplugin.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/taskmanagerplugin.cpp @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (C) 2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +/* + Copyright (C) 2011 Martin Gräßlin + + 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. +*/ + +#include "taskmanagerplugin.h" +#include "backend.h" +#include "draghelper.h" +#include "textlabel.h" + +#include "smartlaunchers/smartlauncheritem.h" + +#include +#include + +void TaskManagerPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.taskmanager")); + qmlRegisterType(uri, 0, 1, "Backend"); + qmlRegisterType(uri, 0, 1, "DragHelper"); + qmlRegisterType(uri, 0, 1, "TextLabel"); + + qmlRegisterType(uri, 0, 1, "SmartLauncherItem"); +} + diff --git a/applets/taskmanager/plugin/textlabel.h b/applets/taskmanager/plugin/textlabel.h new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/textlabel.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2007 by Robert Knight * + * Copyright (C) 2008 by Alexis Ménard * + * Copyright (C) 2008 by Marco Martin * + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#ifndef TEXTLABEL_H +#define TEXTLABEL_H + +#include +#include +#include +#include +#include + +class TextLabel : public QQuickPaintedItem +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(bool elide READ elide WRITE setElide) + + public: + TextLabel(QQuickItem *parent = 0); + ~TextLabel(); + + bool enabled() const; + void setEnabled(bool enabled); + + QString text() const; + void setText(const QString& text); + + QColor color() const; + void setColor(const QColor& color); + + bool elide() const; + void setElide(bool elide); + + void paint(QPainter *painter); + + Q_SIGNALS: + void textChanged(const QString& text); + + protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + + private: + void updateImplicitSize(); + QColor textColor() const; + QTextOption textOption() const;; + void layoutText(QTextLayout &layout, const QString &text, const QSize &constraints); + void drawTextLayout(QPainter *painter, const QTextLayout &layout, const QRect &rect); + + bool m_enabled; + QString m_text; + QColor m_color; + bool m_elide; + QTextLayout m_layout; + QPixmap m_cachedShadow; +}; + +#endif diff --git a/applets/taskmanager/plugin/textlabel.cpp b/applets/taskmanager/plugin/textlabel.cpp new file mode 100644 --- /dev/null +++ b/applets/taskmanager/plugin/textlabel.cpp @@ -0,0 +1,278 @@ +/*************************************************************************** + * Copyright (C) 2007 Jani Huhtanen * + * Copyright (C) 2007 by Robert Knight * + * Copyright (C) 2008 by Alexis Ménard * + * Copyright (C) 2008 by Marco Martin * + * Copyright (C) 2012-2013 by Eike Hein * + * * + * 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 . * + ***************************************************************************/ + +#include "textlabel.h" +#include "blur.cpp" + +#include +#include +#include + +TextLabel::TextLabel(QQuickItem* parent) : QQuickPaintedItem(parent), + m_enabled(true), + m_elide(false) +{ +} + +TextLabel::~TextLabel() +{ +} + +bool TextLabel::enabled() const +{ + return m_enabled; +} + +void TextLabel::setEnabled(bool enabled) +{ + if (enabled != m_enabled) { + m_enabled = enabled; + update(); + } +} + +QString TextLabel::text() const +{ + return m_text; +} + +void TextLabel::setText(const QString& text) +{ + if (text != m_text) + { + m_text = text; + m_cachedShadow = QPixmap(); + updateImplicitSize(); + update(); + emit textChanged(text); + } +} + +QColor TextLabel::color() const +{ + return m_color; +} + +void TextLabel::setColor(const QColor& color) +{ + if (color != m_color) + { + m_color = color; + m_cachedShadow = QPixmap(); + update(); + } +} + + +bool TextLabel::elide() const +{ + return m_elide; +} + +void TextLabel::setElide(bool elide) +{ + m_elide = elide; + + updateImplicitSize(); +} + +void TextLabel::updateImplicitSize() +{ + if (m_elide) { + setImplicitWidth(0); + setImplicitHeight(0); + } else { + QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); + + setImplicitWidth(fm.width(m_text)); + setImplicitHeight(fm.height()); + } +} + +void TextLabel::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) +{ + if (newGeometry.size() != oldGeometry.size()) { + m_cachedShadow = QPixmap(); + } +} + +QColor TextLabel::textColor() const +{ + QColor color(m_color); + + if (!m_enabled) { + color.setAlphaF(0.5); + } + + return color; +} + +QTextOption TextLabel::textOption() const +{ + Qt::LayoutDirection direction = QApplication::layoutDirection(); + Qt::Alignment alignment = QStyle::visualAlignment(direction, Qt::AlignLeft | Qt::AlignVCenter); + + QTextOption option; + option.setTextDirection(direction); + option.setAlignment(alignment); + + return option; +} + +void TextLabel::layoutText(QTextLayout &layout, const QString &text, const QSize &constraints) +{ + QFontMetrics metrics(layout.font()); + int leading = metrics.leading(); + int height = 0; + int maxWidth = constraints.width(); + int widthUsed = 0; + int lineSpacing = metrics.lineSpacing(); + QTextLine line; + + layout.setText(text); + + layout.beginLayout(); + while ((line = layout.createLine()).isValid()) { + height += leading; + + // Make the last line that will fit infinitely long. + // drawTextLayout() will handle this by fading the line out + // if it won't fit in the constraints. + if (height + 2 * lineSpacing > constraints.height()) { + line.setPosition(QPoint(0, height)); + break; + } + + line.setLineWidth(maxWidth); + line.setPosition(QPoint(0, height)); + + height += int(line.height()); + widthUsed = int(qMax(qreal(widthUsed), line.naturalTextWidth())); + } + layout.endLayout(); +} + +void TextLabel::drawTextLayout(QPainter *painter, const QTextLayout &layout, const QRect &rect) +{ + if (rect.width() < 1 || rect.height() < 1) { + return; + } + + QPixmap pixmap(rect.size()); + pixmap.fill(Qt::transparent); + + QPainter p(&pixmap); + p.setPen(painter->pen()); + + // Create the alpha gradient for the fade out effect + QLinearGradient alphaGradient(0, 0, 1, 0); + alphaGradient.setCoordinateMode(QGradient::ObjectBoundingMode); + if (layout.textOption().textDirection() == Qt::LeftToRight) { + alphaGradient.setColorAt(0, QColor(0, 0, 0, 255)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 0)); + } else { + alphaGradient.setColorAt(0, QColor(0, 0, 0, 0)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 255)); + } + + QFontMetrics fm(layout.font()); + int textHeight = layout.lineCount() * fm.lineSpacing(); + + QPointF position(0, (rect.height() - textHeight) / 2); + QList fadeRects; + int fadeWidth = 30; + + // Draw each line in the layout + for (int i = 0; i < layout.lineCount(); i++) { + QTextLine line = layout.lineAt(i); + line.draw(&p, position); + + // Add a fade out rect to the list if the line is too long + if (line.naturalTextWidth() > rect.width()) + { + int x = int(qMin(line.naturalTextWidth(), (qreal)pixmap.width())) - fadeWidth; + int y = int(line.position().y() + position.y()); + QRect r = QStyle::visualRect(layout.textOption().textDirection(), pixmap.rect(), + QRect(x, y, fadeWidth, int(line.height()))); + fadeRects.append(r); + } + } + + // Reduce the alpha in each fade out rect using the alpha gradient + if (!fadeRects.isEmpty()) { + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + foreach (const QRect &rect, fadeRects) { + p.fillRect(rect, alphaGradient); + } + } + + p.end(); + + + QColor shadowColor; + if (qGray(textColor().rgb()) > 192) { + shadowColor = Qt::black; + } else { + shadowColor = Qt::white; + } + + if (m_cachedShadow.isNull()) { + QImage shadow = pixmap.toImage(); + expblur<16, 7>(shadow, 1); + QPainter p(&shadow); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(shadow.rect(), shadowColor); + p.end(); + m_cachedShadow = QPixmap(shadow.size()); + m_cachedShadow.fill(Qt::transparent); + QPainter buffPainter(&m_cachedShadow); + buffPainter.drawImage(QPoint(0,0), shadow); + } + + if (shadowColor == Qt::white) { + painter->drawPixmap(rect.topLeft(), m_cachedShadow); + } else { + painter->drawPixmap(rect.topLeft() + QPoint(1, 2), m_cachedShadow); + } + painter->drawPixmap(rect.topLeft(), pixmap); +} + +void TextLabel::paint(QPainter* painter) +{ + painter->setPen(QPen(textColor(), 1.0)); + + if (QFontDatabase::systemFont(QFontDatabase::GeneralFont) != m_layout.font()) { + m_cachedShadow = QPixmap(); + } + + QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); + QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); + if (fm.tightBoundingRect(QLatin1String("M")).height() > boundingRect().toRect().height()) { + font.setPixelSize(boundingRect().toRect().height()); + } + m_layout.setFont(font); + m_layout.setTextOption(textOption()); + + layoutText(m_layout, text(), boundingRect().toRect().size()); + drawTextLayout(painter, m_layout, boundingRect().toRect()); +}