Merge branch 'mart/newContainmentShell'

Conflicts:
	CMakeLists.txt
	shell/contents/views/Desktop.qml
This commit is contained in:
Marco Martin 2015-05-12 16:22:17 +02:00
commit 264bff2854
61 changed files with 5230 additions and 699 deletions

View file

@ -23,10 +23,10 @@ include(ECMGenerateHeaders)
include(GenerateExportHeader)
include(FeatureSummary)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Gui Widgets Qml Quick)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Gui Widgets Qml Quick Test)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Plasma Service Declarative)
find_package(KF5 REQUIRED COMPONENTS PlasmaQuick I18n)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Plasma Service Declarative I18n)
find_package(KF5 REQUIRED COMPONENTS PlasmaQuick DBusAddons Notifications)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -44,9 +44,13 @@ install(DIRECTORY compositor/
PATTERN Messages.sh EXCLUDE
PATTERN dummydata EXCLUDE)
kpackage_install_package(phonebook org.kde.phone.phonebook genericqml)
install(FILES phonebook/metadata.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} RENAME org.kde.phone.dialer.desktop)
add_subdirectory(bin)
add_subdirectory(qmlcomponents)
add_subdirectory(services)
add_subdirectory(settingsmodules)
add_subdirectory(applets)
add_subdirectory(containments)
add_subdirectory(dialer)

View file

@ -1,3 +1,5 @@
plasma_install_package(clock org.kde.phone.clock)
plasma_install_package(notifications org.kde.phone.notifications)
add_subdirectory(quicksettings)

View file

@ -18,25 +18,24 @@
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
MouseArea {
id: root
id: notificationItem
height: units.gridUnit * 2
height: units.gridUnit * (expanded ? (actionsLayout.visible ? 6 : 4) : 2) + background.margins.top + background.margins.bottom
width: parent.width
anchors.bottomMargin: 10
drag.axis: Drag.XAxis
drag.target: root
drag.target: notificationItem
property bool expanded: false
property var textGradient: Gradient {
GradientStop { position: 1.0; color: "#FF00000C" }
GradientStop { position: 0.0; color: "#00000C00" }
}
property color textGradientOverlay: "#9900000C"
property string source: model.source
property var actions: model.actions
Behavior on x {
SpringAnimation { spring: 2; damping: 0.2 }
@ -46,112 +45,105 @@ MouseArea {
SpringAnimation { spring: 5; damping: 0.3 }
}
onExpandedChanged: {
if (expanded && body) {
height = units.gridUnit * 4;
} else {
height = units.gridUnit * 2;
}
}
onReleased: {
if (drag.active) {
if (x > width / 4 || x < width / -4) {
//if there is an action, execute the first when swiping left
if (x < 0 && actions) {
var action = actions.get(0)
root.executeAction(source, action.id)
}
notificationsModel.remove(index);
} else {
x = 0;
}
} else if (body) {
} else if (body || actions) {
expanded = !expanded;
}
}
PlasmaCore.FrameSvgItem {
id: background
imagePath: "widgets/background"
anchors {
fill: parent
rightMargin: -notificationItem.width
leftMargin: units.gridUnit
}
colorGroup: PlasmaCore.ColorScope.colorGroup
}
PlasmaComponents.ToolButton {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: units.gridUnit / 2
}
iconSource: "window-close"
flat: false
onClicked: {
notificationsModel.remove(index);
}
}
PlasmaComponents.Label {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: units.gridUnit * 3
}
color: PlasmaCore.ColorScope.textColor
text: model.appName
}
PlasmaComponents.Label {
id: summaryText
anchors {
right: icon.left
verticalCenter: parent.verticalCenter
rightMargin: units.smallSpacing
}
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter
color: PlasmaCore.ColorScope.textColor
text: summary + (notificationItem.expanded ? (body ? "\n" + body : '') :
(body ? '...' : ''))
}
PlasmaCore.IconItem {
id: icon
anchors {
right: notificationItem.right
verticalCenter: parent.verticalCenter
}
width: units.iconSizes.medium
height: width
visible: !root.expanded
anchors.verticalCenter: parent.verticalCenter
x: units.largeSpacing
y: 0
source: appIcon && appIcon.length > 0 ? appIcon : "im-user"
}
Item {
id: rounded
clip: true
height: parent.height
width: height / 2
visible: !root.expanded
RowLayout {
id: actionsLayout
anchors {
left: icon.right
leftMargin: units.largeSpacing
right: summaryText.right
top: summaryText.bottom
topMargin: units.smallSpacing
}
Rectangle {
id: roundedRect
height: parent.height
width: parent.width * 2
radius: height //Math.max(height, units.gridUnit)
anchors {
left: parent.left
top: parent.top
opacity: notificationItem.expanded && notificationItem.actions && notificationItem.actions.count > 0 ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
gradient: root.textGradient
Rectangle {
anchors.fill: parent
radius: height / 2
color: textGradientOverlay
}
Repeater {
model: notificationItem.actions
delegate: PlasmaComponents.Button {
text: model.text
onClicked: {
root.executeAction(notificationItem.source, model.id)
notificationsModel.remove(index);
}
}
}
}
Rectangle {
id: summaryArea
width: parent.width - icon.width - rounded.width - (units.largeSpacing * 2)
height: parent.height
anchors {
left: root.expanded ? root.left : rounded.right
right: root.right
top: parent.top
}
gradient: root.textGradient
Rectangle {
anchors.fill: parent
color: textGradientOverlay
}
PlasmaComponents.Label {
id: summaryText
anchors.fill: parent
clip: true
horizontalAlignment: root.expanded ? Qt.AlignHCenter : Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
color: PlasmaCore.ColorScope.textColor
text: summary + (root.expanded ? (body ? "\n" + body : '') :
(body ? '...' : ''))
}
}
Rectangle {
id: extraArea
width: parent.width
height: parent.width
anchors {
left: summaryArea.right
top: parent.top
bottom: parent.bottom
}
gradient: root.textGradient
Rectangle {
anchors.fill: parent
color: textGradientOverlay
}
}
}
}

View file

@ -25,9 +25,59 @@ import org.kde.plasma.components 2.0 as PlasmaComponents
Item {
id: root
property int notificationId: 0
Layout.minimumHeight: notificationView.contentsHeight
function addNotification(source, data, actions) {
// Do not show duplicated notifications
// Remove notifications that are sent again (odd, but true)
for (var i = 0; i < notificationsModel.count; ++i) {
var tmp = notificationsModel.get(i);
var matches = (tmp.appName == data.appName &&
tmp.summary == data.summary &&
tmp.body == data.body);
var sameSource = tmp.source == source;
if (sameSource && matches) {
return;
}
if (sameSource || matches) {
notificationsModel.remove(i)
break;
}
}
data["id"] = ++notificationId;
data["source"] = source;
if (data["summary"].length < 1) {
data["summary"] = data["body"];
data["body"] = '';
}
data["actions"] = actions;
notificationsModel.insert(0, data);
if (!data["isPersistent"]) {
pendingRemovals.push(notificationId);
pendingTimer.start();
}
}
function executeAction(source, id) {
//try to use the service
if (source.indexOf("notification") !== -1) {
var service = notificationsSource.serviceForSource(source)
var op = service.operationDescription("invokeAction")
op["actionId"] = id
service.startOperationCall(op)
//try to open the id as url
} else if (source.indexOf("Job") !== -1) {
Qt.openUrlExternally(id)
}
}
PlasmaCore.DataSource {
id: notificationsSource
@ -58,7 +108,7 @@ Item {
}
}
homescreen.addNotification(
root.addNotification(
sourceName,
data,
actions);
@ -70,17 +120,26 @@ Item {
id: notificationsModel
ListElement {
source: "call1Source"
appIcon: "call-start"
summary: "Missed call from Joe"
appName: "Phone"
body: "Called at 8:42 from +41 56 373 37 31"
actions: []
}
ListElement {
source: "im1Source"
appIcon: "im-google"
appName: "Message"
summary: "July: Hey! Are you around?"
actions: []
}
ListElement {
source: "im2Source"
appIcon: "im-google"
appName: "Message"
summary: "July: Hello?"
actions: []
}
}

View file

@ -0,0 +1,19 @@
set(quicksettings_SRCS
quicksettings.cpp
)
add_library(plasma_applet_quicksettings MODULE ${quicksettings_SRCS})
kcoreaddons_desktop_to_json(plasma_applet_quicksettings package/metadata.desktop)
target_link_libraries(plasma_applet_quicksettings
Qt5::Gui
KF5::Plasma
KF5::I18n)
install(TARGETS plasma_applet_quicksettings DESTINATION ${PLUGIN_INSTALL_DIR})
#install(FILES plasma-quicksettings-default.desktop DESTINATION ${SERVICES_INSTALL_DIR})
plasma_install_package(package org.kde.phone.quicksettings)

View file

@ -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.phone.quicksettings.pot
rm -f rc.cpp

View file

@ -0,0 +1,75 @@
/*
* Copyright 2015 Marco Martin <notmart@gmail.com>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Item {
id: delegateRoot
implicitWidth: delegate.implicitWidth
implicitHeight: delegate.implicitHeight + (slider.opacity > 0 ? slider.height : 0)
property int screenBrightness
readonly property int maximumScreenBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Screen Brightness"] || 0 : 0
PlasmaCore.DataSource {
id: pmSource
engine: "powermanagement"
connectedSources: ["PowerDevil"]
onDataChanged: {
delegateRoot.screenBrightness = source.data["PowerDevil"]["Screen Brightness"];
}
}
Delegate {
id: delegate
toggled: slider.opacity > 0
function toggle() {
slider.opacity = slider.opacity > 0 ? 0 : 1;
}
}
PlasmaComponents.Slider {
id: slider
anchors.top: delegate.bottom
width: flow.width
opacity: 0
x: -delegateRoot.parent.x
value: screenBrightness
minimumValue: maximumValue > 100 ? 1 : 0
maximumValue: delegateRoot.maximumScreenBrightness
Behavior on opacity {
OpacityAnimator {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
onScreenBrightnessChanged: {
var service = pmSource.serviceForSource("PowerDevil");
var operation = service.operationDescription("setBrightness");
operation.brightness = slider.value;
operation.silent = true
service.startOperationCall(operation);
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2015 Marco Martin <notmart@gmail.com>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
RowLayout {
id: delegateRoot
property bool toggled: model.enabled
spacing: units.smallSpacing
implicitWidth: flow.width / 2 - units.largeSpacing / 2
Rectangle {
Layout.minimumWidth: units.iconSizes.large
Layout.minimumHeight: width
color: toggled ?
Qt.rgba(PlasmaCore.ColorScope.highlightColor.r, PlasmaCore.ColorScope.highlightColor.g, PlasmaCore.ColorScope.highlightColor.b, iconMouseArea.pressed ? 0.5 : 0.3) :
Qt.rgba(PlasmaCore.ColorScope.textColor.r, PlasmaCore.ColorScope.textColor.g, PlasmaCore.ColorScope.textColor.b, iconMouseArea.pressed ? 0.5 : 0.2)
PlasmaCore.IconItem {
colorGroup: PlasmaCore.ColorScope.colorGroup
anchors {
fill: parent
margins: units.smallSpacing
}
source: model.icon
MouseArea {
id: iconMouseArea
anchors.fill: parent
onClicked: {
if (delegateRoot.toggle) {
delegateRoot.toggle();
} else if (model.toggleFunction) {
root[model.toggleFunction]();
} else if (model.settingsCommand) {
plasmoid.nativeInterface.executeCommand(model.settingsCommand);
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Qt.rgba(PlasmaCore.ColorScope.textColor.r, PlasmaCore.ColorScope.textColor.g, PlasmaCore.ColorScope.textColor.b, labelMouseArea.pressed ? 0.5 : 0.2)
PlasmaComponents.Label {
anchors {
fill: parent
margins: units.smallSpacing
}
text: model.text
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
MouseArea {
id: labelMouseArea
anchors.fill: parent
onClicked: {
if (model.settingsCommand) {
plasmoid.nativeInterface.executeCommand(model.settingsCommand);
} else if (model.toggleFunction) {
root[model.toggleFunction]();
}
}
}
}
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright 2015 Marco Martin <notmart@gmail.com>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.plasmoid 2.0
Item {
id: root
function toggleAirplane() {
print("toggle airplane mode")
}
Plasmoid.preferredRepresentation: plasmoid.fullRepresentation
ListModel {
id: settingsModel
ListElement {
text: "Settings"
icon: "configure"
enabled: false
settingsCommand: "active-settings"
toggleFunction: ""
delegate: ""
}
ListElement {
text: "Mobile network"
icon: "network-mobile-80"
enabled: true
settingsCommand: ""
}
ListElement {
text: "Airplane mode"
icon: "flightmode-on"
enabled: false
settingsCommand: ""
toggleFunction: "toggleAirplane"
}
ListElement {
text: "Bluetooth"
icon: "preferences-system-bluetooth"
enabled: false
settingsCommand: ""
}
ListElement {
text: "Wireless"
icon: "network-wireless-on"
enabled: true
settingsCommand: "active-settings -m org.kde.satellite.settings.wifi"
}
ListElement {
text: "Alarms"
icon: "korgac"
enabled: false
settingsCommand: ""
}
ListElement {
text: "Notifications"
icon: "preferences-desktop-notification"
enabled: true
settingsCommand: ""
}
ListElement {
text: "Brightness"
icon: "video-display-brightness"
enabled: false
settingsCommand: "active-settings -m org.kde.active.settings.powermanagement"
delegate: "BrightnessDelegate"
}
ListElement {
text: "Flashlight"
icon: "package_games_puzzle"
enabled: false
settingsCommand: ""
}
ListElement {
text: "Location"
icon: "plasmaapplet-location"
enabled: false
settingsCommand: ""
}
}
Flow {
id: flow
anchors {
fill: parent
margins: units.largeSpacing
}
spacing: units.largeSpacing
Repeater {
model: settingsModel
delegate: Loader {
width: item ? item.implicitWidth : 0
height: item ? item.implicitHeight : 0
source: Qt.resolvedUrl((model.delegate ? model.delegate : "Delegate") + ".qml")
}
}
move: Transition {
NumberAnimation {
duration: units.shortDuration
easing.type: Easing.InOutQuad
properties: "x,y"
}
}
}
}

View file

@ -0,0 +1,22 @@
[Desktop Entry]
Encoding=UTF-8
Name=Quick Settings
Comment=A panel to quickly access most important settings
Icon=systemsettings
Type=Service
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Marco Martin
X-KDE-PluginInfo-Email=mart@kde.org
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=org.kde.phone.quicksettings
X-KDE-Library=plasma_applet_quicksettings
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=plasma.kde.org
X-KDE-ServiceTypes=Plasma/Applet
X-Plasma-API=declarativeappletscript
X-KDE-PluginInfo-Category=Settings
X-Plasma-MainScript=ui/main.qml
X-Plasma-RemoteLocation=

View file

@ -0,0 +1,43 @@
/***************************************************************************
* Copyright (C) 2015 Marco Martin <mart@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "quicksettings.h"
#include <QDebug>
#include <QProcess>
QuickSettings::QuickSettings(QObject *parent, const QVariantList &args)
: Plasma::Applet(parent, args)
{
setHasConfigurationInterface(true);
}
QuickSettings::~QuickSettings()
{
}
void QuickSettings::executeCommand(const QString &command)
{
qWarning()<<"Executing"<<command;
QProcess::startDetached(command);
}
K_EXPORT_PLASMA_APPLET_WITH_JSON(quicksettings, QuickSettings, "metadata.json")
#include "quicksettings.moc"

View file

@ -0,0 +1,43 @@
/***************************************************************************
* Copyright (C) 2015 Marco Martin <mart@kde.org> *
*
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef QUICKSETTINGS_H
#define QUICKSETTINGS_H
#include <Plasma/Applet>
class QuickSettings : public Plasma::Applet
{
Q_OBJECT
public:
QuickSettings( QObject *parent, const QVariantList &args );
~QuickSettings();
public Q_SLOTS:
void executeCommand(const QString &command);
private:
};
#endif

View file

@ -1,2 +1,3 @@
plasma_install_package(homescreen org.kde.phone.homescreen)
plasma_install_package(panel org.kde.phone.panel)

View file

@ -9,6 +9,10 @@
<entry name="AppletOrder" type="String">
<label>encoded order of items</label>
</entry>
<entry name="AppOrder" type="StringList">
<label>order of apps</label>
<default>org.kde.phone.dialer.desktop</default>
</entry>
</group>
</kcfg>

View file

@ -8,150 +8,43 @@ Item {
width: applicationsView.cellWidth
height: width
property int idx: index
property int oldIdx: -1
property var modelData: model
property bool isDropTarget: delegateRoot != dragDelegate && root.reorderingApps && applicationsView.dragData && applicationsView.dragData.ApplicationStorageIdRole == modelData.ApplicationStorageIdRole
Rectangle {
anchors.fill: parent
color: PlasmaCore.ColorScope.textColor
radius: units.gridUnit
opacity: (delegateItem.drag.target != null) ? 0.4 : 0
Behavior on opacity {
NumberAnimation {
opacity: isDropTarget ? 0.3 : 1
PlasmaCore.IconItem {
id: icon
anchors.centerIn: parent
width: parent.height / 2
height: width
source: modelData.ApplicationIconRole
scale: root.reorderingApps && applicationsView.dragData && applicationsView.dragData.ApplicationStorageIdRole != modelData.ApplicationStorageIdRole ? 0.6 : 1
Behavior on scale {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
//animate index change
onIdxChanged: {
if (delegateItem.drag.target != null) {
return;
PlasmaComponents.Label {
id: label
visible: text.length > 0
anchors {
top: icon.bottom
left: icon.left
right: icon.right
}
if (oldIdx < 0) {
oldIdx = idx;
return;
}
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
maximumLineCount: 2
delegateItem.x = ((oldIdx % 4) * GridView.view.cellWidth) - ((idx % 4) * GridView.view.cellWidth);
delegateItem.y = (Math.floor(oldIdx / 4) * GridView.view.cellHeight) - (Math.floor(idx / 4) * GridView.view.cellHeight);
translAnim.running = true;
oldIdx = idx;
}
NumberAnimation {
id: translAnim
duration: units.longDuration
easing.type: Easing.InOutQuad
target: delegateItem
properties: "x,y"
to: 0
}
MouseArea {
id: delegateItem
width: applicationsView.cellWidth
height: width
states: [
State {
when: delegateItem.drag.target != null
ParentChange {
target: delegateItem
parent: delegateRoot.parent
}
PropertyChanges {
target: delegateItem
z: 9999
}
}
]
function updateRow() {
var pos = mapToItem(delegateRoot.parent, 0, 0);
var newRow = (Math.round(delegateRoot.GridView.view.width / delegateRoot.GridView.view.cellWidth) * Math.round(pos.y / delegateRoot.GridView.view.cellHeight) + Math.round(pos.x / delegateRoot.GridView.view.cellWidth));
if (model.ApplicationOriginalRowRole != newRow) {
appListModel.moveItem(model.ApplicationOriginalRowRole, newRow);
}
}
onClicked: {
console.log("Clicked: " + model.ApplicationStorageIdRole)
appListModel.runApplication(model.ApplicationStorageIdRole)
oldX = x
oldY = y
}
onPressAndHold: {
delegateRoot.GridView.view.draggingItem = delegateItem;
delegateItem.drag.target = delegateItem;
root.reorderingApps = true;
}
onReleased: {
delegateRoot.GridView.view.draggingItem = delegateItem;
delegateItem.drag.target = null;
root.reorderingApps = false;
translAnim.running = true;
autoScrollTimer.running = false;
}
onPositionChanged: {
if (!autoScrollTimer.running && delegateItem.drag.target) {
updateRow();
var screenPos = mapToItem(delegateRoot.GridView.view, 0, 0);
if (applicationsView.contentY > 0 && screenPos.y < root.height / 4) {
autoScrollTimer.scrollDown = false;
autoScrollTimer.running = true;
} else if (!applicationsView.atYEnd && screenPos.y > 3 * (root.height / 4)) {
autoScrollTimer.scrollDown = true;
autoScrollTimer.running = true;
} else {
autoScrollTimer.running = false;
}
} else {
autoScrollTimer.running = false;
}
}
PlasmaCore.IconItem {
id: icon
anchors.centerIn: parent
width: parent.height / 2
height: width
source: model.ApplicationIconRole
scale: root.reorderingApps && !delegateItem.drag.target ? 0.6 : 1
Behavior on scale {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
PlasmaComponents.Label {
id: label
visible: text.length > 0
anchors {
top: icon.bottom
left: icon.left
right: icon.right
}
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
maximumLineCount: 2
text: model.ApplicationNameRole
font.pixelSize: theme.smallestFont.pixelSize
color: PlasmaCore.ColorScope.textColor
}
text: modelData.ApplicationNameRole
font.pixelSize: theme.smallestFont.pixelSize
color: PlasmaCore.ColorScope.textColor
}
}

View file

@ -1,7 +1,9 @@
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
PlasmaCore.ColorScope {
colorGroup: PlasmaCore.Theme.NormalColorGroup
PlasmaCore.FrameSvgItem {
z: -1
imagePath: "widgets/background"
@ -11,10 +13,17 @@ Item {
topMargin: -margins.top
bottomMargin: -margins.bottom
}
Rectangle {
anchors {
fill: parent
topMargin: parent.margins.top
bottomMargin: parent.margins.bottom
}
color: PlasmaCore.ColorScope.backgroundColor
}
}
opacity: 0.6
height: Math.max(100, units.gridUnit * 2.5)
height: applicationsView.cellWidth
width: parent.width
y: parent.height / 2 - height / 2
x: 0

View file

@ -16,18 +16,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.1
import QtQuick 2.4
import QtQuick.Layouts 1.1
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
import org.kde.kquickcontrolsaddons 2.0
import org.kde.satellite.components 0.1 as SatelliteComponents
import "plasmapackage:/code/LayoutManager.js" as LayoutManager
Item {
MouseEventListener {
id: root
width: 480
height: 640
@ -42,6 +42,12 @@ Item {
LayoutManager.save();
}
Plasmoid.onFocusChanged: {
if (!plasmoid.focus && applicationsView.contentY > -(applicationsView.headerItem.height - root.height/2)) {
applicationsView.contentY = -root.height;
}
}
function addApplet(applet, x, y) {
var container = appletContainerComponent.createObject(appletsSpace.layout)
container.visible = true
@ -65,7 +71,8 @@ Item {
container.animationsEnabled = false;
if (appletsSpace.lastSpacer.parent === appletsSpace.layout) {
before = appletsSpace.lastSpacer;
//Uncomment to make the spacer the last element again
// before = appletsSpace.lastSpacer;
}
if (before) {
@ -92,24 +99,28 @@ Item {
LayoutManager.lastSpacer = appletsSpace.lastSpacer;
LayoutManager.restore();
applicationsView.contentY = -root.height;
appListModel.appOrder = plasmoid.configuration.AppOrder;
appListModel.loadApplications();
}
SatelliteComponents.ApplicationListModel {
id: appListModel
onAppOrderChanged: {
plasmoid.configuration.AppOrder = appListModel.appOrder;
}
}
Timer {
id: autoScrollTimer
property bool scrollDown: true
repeat: true
interval: 10
interval: 1500
onTriggered: {
applicationsView.contentY += scrollDown ? 8 : -8;
if (applicationsView.draggingItem) {
applicationsView.draggingItem.y += scrollDown ? 8 : -8;
applicationsView.draggingItem.updateRow();
}
scrollAnim.to = scrollDown ?
Math.min(applicationsView.contentItem.height - applicationsView.headerItem.height - root.height, applicationsView.contentY + root.height/2) :
Math.max(0, applicationsView.contentY - root.height/2);
scrollAnim.running = true;
}
}
@ -128,7 +139,7 @@ Item {
}
Layout.minimumWidth: root.width
Layout.minimumHeight: Math.max(applet.Layout.minimumHeight, root.height / 2)
Layout.minimumHeight: Math.max(applet.Layout.minimumHeight, (root.height-applicationsView.headerItem.margin) / 2)
Layout.preferredWidth: root.width
Layout.preferredHeight: Layout.minimumHeight
@ -138,15 +149,197 @@ Item {
}
}
Rectangle {
color: Qt.rgba(0, 0, 0, 0.9 * (Math.min(applicationsView.contentY + root.height, root.height) / root.height))
anchors.fill: parent
}
onPressAndHold: {
var pos = mapToItem(applicationsView.headerItem.favoritesStrip, mouse.x, mouse.y);
//in favorites area?
var item;
if (applicationsView.headerItem.favoritesStrip.contains(pos)) {
item = applicationsView.headerItem.favoritesStrip.itemAt(pos.x, pos.y);
} else {
pos = mapToItem(applicationsView.contentItem, mouse.x, mouse.y);
item = applicationsView.itemAt(pos.x, pos.y)
}
if (!item) {
return;
}
applicationsView.dragData = new Object;
applicationsView.dragData.ApplicationNameRole = item.modelData.ApplicationNameRole;
applicationsView.dragData.ApplicationIconRole = item.modelData.ApplicationIconRole;
applicationsView.dragData.ApplicationStorageIdRole = item.modelData.ApplicationStorageIdRole;
applicationsView.dragData.ApplicationEntryPathRole = item.modelData.ApplicationEntryPathRole;
applicationsView.dragData.ApplicationOriginalRowRole = item.modelData.ApplicationOriginalRowRole;
dragDelegate.modelData = applicationsView.dragData;
applicationsView.interactive = false;
root.reorderingApps = true;
dragDelegate.x = Math.floor(mouse.x / root.buttonHeight) * root.buttonHeight
dragDelegate.y = Math.floor(mouse.y / root.buttonHeight) * root.buttonHeight
dragDelegate.xTarget = mouse.x - dragDelegate.width/2;
dragDelegate.yTarget = mouse.y - dragDelegate.width/2;
dragDelegate.opacity = 1;
}
onPositionChanged: {
if (!applicationsView.dragData) {
return;
}
dragDelegate.x = mouse.x - dragDelegate.width/2;
dragDelegate.y = mouse.y - dragDelegate.height/2;
var pos = mapToItem(applicationsView.contentItem, mouse.x, mouse.y);
//in favorites area?
if (applicationsView.headerItem.favoritesStrip.contains(mapToItem(applicationsView.headerItem.favoritesStrip, mouse.x, mouse.y))) {
pos.y = 1;
}
var newRow = (Math.round(applicationsView.width / applicationsView.cellWidth) * Math.floor(pos.y / applicationsView.cellHeight) + Math.floor(pos.x / applicationsView.cellWidth));
if (applicationsView.dragData.ApplicationOriginalRowRole != newRow) {
appListModel.moveItem(applicationsView.dragData.ApplicationOriginalRowRole, newRow);
applicationsView.dragData.ApplicationOriginalRowRole = newRow;
}
var pos = mapToItem(applicationsView.headerItem.favoritesStrip, mouse.x, mouse.y);
//FAVORITES
if (applicationsView.headerItem.favoritesStrip.contains(pos)) {
autoScrollTimer.running = false;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 0;
//SCROLL UP
} else if (applicationsView.contentY > 0 && mouse.y < root.buttonHeight + root.height / 4) {
autoScrollTimer.scrollDown = false;
autoScrollTimer.running = true;
scrollUpIndicator.opacity = 1;
scrollDownIndicator.opacity = 0;
//SCROLL DOWN
} else if (!applicationsView.atYEnd && mouse.y > 3 * (root.height / 4)) {
autoScrollTimer.scrollDown = true;
autoScrollTimer.running = true;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 1;
//DON't SCROLL
} else {
autoScrollTimer.running = false;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 0;
}
}
onReleased: {
applicationsView.interactive = true;
dragDelegate.xTarget = Math.floor(mouse.x / root.buttonHeight) * root.buttonHeight;
dragDelegate.yTarget = Math.floor(mouse.y / root.buttonHeight) * root.buttonHeight;
dragDelegate.opacity = 0;
dragDelegate.modelData.ApplicationIconRole = "";
dragDelegate.modelDataChanged();
applicationsView.dragData = null;
root.reorderingApps = false;
applicationsView.forceLayout();
autoScrollTimer.running = false;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 0;
}
onClicked: {
var pos = mapToItem(applicationsView.headerItem.favoritesStrip, mouse.x, mouse.y);
//in favorites area?
var item;
if (applicationsView.headerItem.favoritesStrip.contains(pos)) {
item = applicationsView.headerItem.favoritesStrip.itemAt(pos.x, pos.y);
} else {
pos = mapToItem(applicationsView.contentItem, mouse.x, mouse.y);
item = applicationsView.itemAt(pos.x, pos.y)
}
if (!item) {
return;
}
appListModel.runApplication(item.modelData.ApplicationStorageIdRole)
}
PlasmaCore.ColorScope {
anchors.fill: parent
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
Rectangle {
color: PlasmaCore.ColorScope.backgroundColor
opacity: 0.9 * (Math.min(applicationsView.contentY + root.height, root.height) / root.height)
anchors.fill: parent
}
PlasmaCore.Svg {
id: arrowsSvg
imagePath: "widgets/arrows"
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
}
PlasmaCore.SvgItem {
id: scrollUpIndicator
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 200
}
z: 2
opacity: 0
svg: arrowsSvg
elementId: "up-arrow"
width: units.iconSizes.large
height: width
Behavior on opacity {
OpacityAnimator {
duration: 1000
easing.type: Easing.InOutQuad
}
}
}
PlasmaCore.SvgItem {
id: scrollDownIndicator
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
z: 2
opacity: 0
svg: arrowsSvg
elementId: "down-arrow"
width: units.iconSizes.large
height: width
Behavior on opacity {
OpacityAnimator {
duration: 1000
easing.type: Easing.InOutQuad
}
}
}
HomeLauncher {
id: dragDelegate
z: 999
property int xTarget
property int yTarget
Behavior on opacity {
ParallelAnimation {
OpacityAnimator {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
PropertyAnimation {
properties: "x"
to: dragDelegate.xTarget
target: dragDelegate
duration: units.longDuration
easing.type: Easing.InOutQuad
}
PropertyAnimation {
properties: "y"
to: dragDelegate.yTarget
target: dragDelegate
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
GridView {
id: applicationsView
anchors {
@ -156,7 +349,7 @@ Item {
right: parent.right
}
property Item draggingItem
property var dragData
cellWidth: root.buttonHeight
cellHeight: cellWidth
@ -183,29 +376,50 @@ Item {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
move: Transition {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
properties: "x,y"
}
}
moveDisplaced: Transition {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
properties: "x,y"
}
}
//clip: true
delegate: HomeLauncher {}
delegate: HomeLauncher {
visible: index > 3
}
header: MouseArea {
z: 999
property Item layout: appletsLayout
property Item lastSpacer: spacer
property Item favoritesStrip: favoritesView
width: root.width
height: mainLayout.Layout.minimumHeight
height: mainLayout.Layout.minimumHeight
property int margin: stripe.height + units.gridUnit * 2
onPressAndHold: {
plasmoid.action("configure").trigger();
print(favoritesView.contains(mapToItem(favoritesView, mouse.x, mouse.y)))
if (!favoritesView.contains(mapToItem(favoritesView, mouse.x, mouse.y))) {
plasmoid.action("configure").trigger();
}
}
ColumnLayout {
id: mainLayout
anchors {
fill: parent
bottomMargin: stripe.height + units.gridUnit * 2
}
Item {
Layout.fillWidth: true
Layout.minimumHeight: root.height
Layout.maximumHeight: root.height
Clock {
anchors {
horizontalCenter: parent.horizontalCenter
@ -226,13 +440,20 @@ Item {
}
ColumnLayout {
id: appletsLayout
Layout.minimumHeight: Math.max(root.height, Math.round(Layout.preferredHeight / root.height) * root.height)
Item {
id: spacer
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: plasmoid.applets.length % 2 == 0 ? 0 : (root.height - margin)/2
Layout.maximumHeight: Layout.minimumHeight
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: margin
Layout.maximumHeight: Layout.minimumHeight
}
}
SatelliteStripe {
id: stripe
@ -240,48 +461,42 @@ Item {
property int viewPos: applicationsView.contentItem.height * applicationsView.visibleArea.yPosition
y: Math.max(viewPos,
Math.min(parent.height, viewPos + root.height) - height + Math.max(0, -(parent.height - height + applicationsView.contentY)))
Math.min(parent.height, viewPos + root.height - height) + Math.max(0, -(parent.height - height + applicationsView.contentY)))
PlasmaCore.Svg {
id: stripeIcons
imagePath: Qt.resolvedUrl("../images/homescreenicons.svg")
}
Row {
GridView {
id: favoritesView
//FIXME: QQuickItem has a contains, but seems to not work
function contains(point) {
return point.x > 0 && point.x < width && point.y > 0 && point.y < height;
}
anchors.fill: parent
property int columns: 4
property alias buttonHeight: stripe.height
interactive: false
flow: GridView.FlowTopToBottom
cellWidth: root.buttonHeight
cellHeight: cellWidth
HomeLauncherSvg {
id: phoneIcon
svg: stripeIcons
elementId: "phone"
callback: function() {
console.log("Start phone")
model: appListModel
delegate: HomeLauncher {}
move: Transition {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
properties: "x,y"
}
}
HomeLauncherSvg {
id: messagingIcon
svg: stripeIcons
elementId: "messaging"
callback: function() { console.log("Start messaging") }
}
HomeLauncherSvg {
id: emailIcon
svg: stripeIcons
elementId: "email"
callback: function() { console.log("Start email") }
}
HomeLauncherSvg {
id: webIcon
svg: stripeIcons
elementId: "web"
callback: function() { console.log("Start web") }
moveDisplaced: Transition {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
properties: "x,y"
}
}
}
}
@ -310,7 +525,7 @@ Item {
radius: width
anchors.right: parent.right
y: applicationsView.height * applicationsView.visibleArea.yPosition
opacity: scrollbarMouse.pressed || applicationsView.flicking ? 0.8 : 0
opacity: scrollbarMouse.pressed || applicationsView.flicking || scrollDownIndicator.opacity > 0 || scrollUpIndicator.opacity > 0 ? 0.8 : 0
Behavior on opacity {
NumberAnimation {
duration: units.longDuration

View file

@ -0,0 +1,189 @@
/*
* Copyright 2013 Marco Martin <mart@kde.org>
*
* 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 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.
*/
.pragma library
var layout;
var root;
var plasmoid;
var lastSpacer;
function restore() {
var configString = String(plasmoid.configuration.AppletOrder)
//array, a cell for encoded item order
var itemsArray = configString.split(";");
//map applet id->order in panel
var idsOrder = new Object();
//map order in panel -> applet pointer
var appletsOrder = new Object();
for (var i = 0; i < itemsArray.length; i++) {
//property name: applet id
//property value: order
idsOrder[itemsArray[i]] = i;
}
for (var i = 0; i < plasmoid.applets.length; ++i) {
if (idsOrder[plasmoid.applets[i].id] !== undefined) {
appletsOrder[idsOrder[plasmoid.applets[i].id]] = plasmoid.applets[i];
//ones that weren't saved in AppletOrder go to the end
} else {
appletsOrder["unordered"+i] = plasmoid.applets[i];
}
}
//finally, restore the applets in the correct order
for (var i in appletsOrder) {
root.addApplet(appletsOrder[i], -1, -1)
}
//rewrite, so if in the orders there were now invalid ids or if some were missing creates a correct list instead
save();
}
function save() {
var ids = new Array();
for (var i = 0; i < layout.children.length; ++i) {
var child = layout.children[i];
if (child.applet) {
ids.push(child.applet.id);
}
}
plasmoid.configuration.AppletOrder = ids.join(';');
}
function removeApplet (applet) {
for (var i = layout.children.length - 1; i >= 0; --i) {
var child = layout.children[i];
if (child.applet === applet) {
child.destroy();
}
}
}
//insert item2 before item1
function insertBefore(item1, item2) {
if (item1 === item2) {
return;
}
var removed = new Array();
var child;
var i;
for (i = layout.children.length - 1; i >= 0; --i) {
child = layout.children[i];
removed.push(child);
child.parent = root;
if (child === item1) {
break;
}
}
item2.parent = layout;
for (var j = removed.length - 1; j >= 0; --j) {
removed[j].parent = layout;
}
return i;
}
//insert item2 after item1
function insertAfter(item1, item2) {
if (item1 === item2) {
return;
}
var removed = new Array();
var child;
var i;
for (i = layout.children.length - 1; i >= 0; --i) {
child = layout.children[i];
//never ever insert after lastSpacer
if (child === lastSpacer && item1 === lastSpacer) {
removed.push(child);
child.parent = root;
break;
} else if (child === item1) {
break;
}
removed.push(child);
child.parent = root;
}
item2.parent = layout;
for (var j = removed.length - 1; j >= 0; --j) {
removed[j].parent = layout;
}
return i;
}
function insertAtIndex(item, position) {
if (position < 0 || position >= layout.children.length) {
return;
}
//never ever insert after lastSpacer
if (layout.children[position] === lastSpacer) {
--position;
}
var removedItems = new Array();
for (var i = position; i < layout.children.length; ++i) {
var child = layout.children[position];
child.parent = root;
removedItems.push(child);
}
item.parent = layout;
for (var i in removedItems) {
removedItems[i].parent = layout;
}
}
function insertAtCoordinates(item, x, y) {
if (root.isHorizontal) {
y = layout.height / 2;
} else {
x = layout.width / 2;
}
var child = layout.childAt(x, y);
if (!child || child === item) {
child = layout.children[0];
}
item.parent = root;
//PlasmaCore.Types.Vertical = 3
if ((plasmoid.formFactor === 3 && y < child.y + child.height/2) ||
(plasmoid.formFactor !== 3 && x < child.x + child.width/2)) {
return insertBefore(child, item);
} else {
return insertAfter(child, item);
}
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<entry name="AppletOrder" type="String">
<label>encoded order of items</label>
</entry>
</group>
</kcfg>

View file

@ -18,7 +18,7 @@
*/
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
Window {
@ -26,8 +26,10 @@ Window {
flags: Qt.WindowDoesNotAcceptFocus
property int offset: 0
property int overShoot: units.gridUnit * 2
color: "transparent"
property alias contents: contentArea.data
function updateState() {
var delta = offset - mouseArea.startOffset;
@ -50,15 +52,17 @@ Window {
MouseArea {
id: mouseArea
y: units.iconSizes.small
y: 0
width: window.width
height: window.height - y
clip: true
state: "closed"
drag.filterChildren: true
property int oldMouseY: 0
property int startOffset: units.iconSizes.large;
property string startState: "closed"
onPressed: {
startState = state;
startOffset = window.offset;
@ -67,26 +71,63 @@ Window {
window.offset = startOffset;
}
onPositionChanged: {
window.offset = window.offset + (mouse.y - oldMouseY);
var factor = (mouse.y - oldMouseY > 0) ? (1 - Math.max(0, (slidingArea.y + overShoot) / overShoot)) : 1
window.offset = window.offset + (mouse.y - oldMouseY) * factor;
oldMouseY = mouse.y;
}
onReleased: window.updateState()
onReleased: {
if (Math.abs(window.offset - mouseArea.startOffset) < units.gridUnit &&
slidingArea.y + slidingArea.height < mouse.y) {
mouseArea.state = "closed";
} else {
window.updateState();
}
}
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.6-Math.abs(slidingArea.y/slidingArea.height))
}
PlasmaCore.ColorScope {
id: slidingArea
width: parent.width
height: parent.height
height: parent.height/1.5
y: Math.min(0, -height + window.offset)
color: Qt.rgba(0, 0, 0, 0.7)
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
Rectangle {
width: parent.width / 4
height: units.gridUnit/2
color: "yellow"
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: units.gridUnit/2
anchors.fill: parent
Item {
id: contentArea
anchors {
fill: parent
topMargin: overShoot
}
}
color: PlasmaCore.ColorScope.backgroundColor
Rectangle {
height: units.gridUnit
anchors {
left: parent.left
right: parent.right
top: parent.bottom
}
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(0, 0, 0, 0.6)
}
GradientStop {
position: 0.5
color: Qt.rgba(0, 0, 0, 0.2)
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
}
}
@ -103,7 +144,7 @@ Window {
name: "open"
PropertyChanges {
target: window
offset: slidingArea.height
offset: slidingArea.height - overShoot
}
},
State {
@ -127,7 +168,7 @@ Window {
PropertyAnimation {
target: window
duration: units.longDuration
easing: Easing.InOutQuad
easing.type: Easing.InOutQuad
properties: "offset"
}
ScriptAction {

View file

@ -0,0 +1,309 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import MeeGo.QOfono 0.2
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
import org.kde.plasma.workspace.components 2.0 as PlasmaWorkspace
import "plasmapackage:/code/LayoutManager.js" as LayoutManager
PlasmaCore.ColorScope {
id: root
width: 480
height: 640
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
property Item toolBox
property int buttonHeight: width/4
property bool reorderingApps: false
property QtObject expandedApplet
Containment.onAppletAdded: {
addApplet(applet, x, y);
LayoutManager.save();
}
function addApplet(applet, x, y) {
var container = appletContainerComponent.createObject(layout)
container.visible = true
print("Applet added: " + applet)
var appletWidth = applet.width;
var appletHeight = applet.height;
applet.parent = container;
container.applet = applet;
//applet.anchors.fill = container;
applet.anchors.left = container.left;
applet.anchors.right = container.right;
applet.height = units.iconSizes.medium
applet.visible = true;
container.visible = true;
// If the provided position is valid, use it.
if (x >= 0 && y >= 0) {
var index = LayoutManager.insertAtCoordinates(container, x , y);
// Fall through to determining an appropriate insert position.
} else {
var before = null;
if (lastSpacer.parent === layout) {
before = lastSpacer;
}
if (before) {
LayoutManager.insertBefore(before, container);
// Fall through to adding at the end.
} else {
container.parent = layout;
}
//event compress the enable of animations
//startupTimer.restart();
}
if (applet.Layout.fillWidth) {
lastSpacer.parent = root;
} else {
lastSpacer.parent = layout;
}
}
Component.onCompleted: {
LayoutManager.plasmoid = plasmoid;
LayoutManager.root = root;
LayoutManager.layout = layout;
LayoutManager.restore();
}
RowLayout {
id: appletsLayout
Layout.minimumHeight: Math.max(root.height, Math.round(Layout.preferredHeight / root.height) * root.height)
}
Component {
id: appletContainerComponent
Item {
property Item applet
Layout.fillWidth: true
clip: true
anchors {
left: parent.left
right: parent.right
}
height: applet && applet.expanded ? Math.max(applet.fullRepresentationItem.Layout.minimumHeight, units.iconSizes.medium) : units.iconSizes.medium
Behavior on height {
NumberAnimation {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
Connections {
target: applet
onExpandedChanged: {
if (root.expandedApplet) {
root.expandedApplet.expanded = false;
}
root.expandedApplet = applet;
}
}
}
}
Component {
id: tabComponent
PlasmaComponents.TabButton {
width: parent.width / parent.children.length
height: units.iconSizes.huge
}
}
PlasmaCore.DataSource {
id: timeSource
engine: "time"
connectedSources: ["Local"]
interval: 60 * 1000
}
OfonoManager {
id: ofonoManager
onAvailableChanged: {
console.log("Ofono is " + available)
}
onModemAdded: {
console.log("modem added " + modem)
}
onModemRemoved: console.log("modem removed")
}
OfonoNetworkRegistration {
id: netreg
Component.onCompleted: {
netreg.scan()
updateStrengthIcon()
}
onNetworkOperatorsChanged : {
console.log("operators :"+netreg.currentOperator["Name"].toString())
}
modemPath: ofonoManager.modems.length ? ofonoManager.modems[0] : ""
function updateStrengthIcon() {
if (netreg.strength >= 100) {
strengthIcon.source = "network-mobile-100";
} else if (netreg.strength >= 80) {
strengthIcon.source = "network-mobile-80";
} else if (netreg.strength >= 60) {
strengthIcon.source = "network-mobile-60";
} else if (netreg.strength >= 40) {
strengthIcon.source = "network-mobile-40";
} else if (netreg.strength >= 20) {
strengthIcon.source = "network-mobile-20";
} else {
strengthIcon.source = "network-mobile-0";
}
}
onStrengthChanged: {
console.log("Strength changed to " + netreg.strength)
updateStrengthIcon()
}
}
Rectangle {
parent: slidingPanel.visible ? panelContents : root
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: root.height
color: PlasmaCore.ColorScope.backgroundColor
PlasmaCore.IconItem {
id: strengthIcon
colorGroup: PlasmaCore.ColorScope.colorGroup
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
width: height
height: parent.height
}
PlasmaComponents.Label {
anchors {
left: strengthIcon.right
verticalCenter: parent.verticalCenter
}
text: netreg.strength + "% " + netreg.name
color: PlasmaCore.ColorScope.textColor
font.pixelSize: parent.height / 2
}
PlasmaComponents.Label {
id: clock
anchors.fill: parent
text: Qt.formatTime(timeSource.data.Local.DateTime, "hh:mm")
color: PlasmaCore.ColorScope.textColor
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.pixelSize: height / 2
}
PlasmaWorkspace.BatteryIcon {
id: batteryIcon
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
width: height
height: parent.height
hasBattery: pmSource.data["Battery"]["Has Battery"]
// batteryType: "Phone"
percent: pmSource.data["Battery0"] ? pmSource.data["Battery0"]["Percent"] : 0
PlasmaCore.DataSource {
id: pmSource
engine: "powermanagement"
connectedSources: sources
onSourceAdded: {
disconnectSource(source);
connectSource(source);
}
onSourceRemoved: {
disconnectSource(source);
}
}
}
Rectangle {
height: units.smallSpacing/2
color: PlasmaCore.ColorScope.highlightColor
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
}
MouseArea {
property int oldMouseY: 0
anchors.fill: parent
onPressed: {
oldMouseY = mouse.y;
slidingPanel.visibility = Qt.WindowFullScreen;
}
onPositionChanged: {
//var factor = (mouse.y - oldMouseY > 0) ? (1 - Math.max(0, (slidingArea.y + slidingPanel.overShoot) / slidingPanel.overShoot)) : 1
var factor = 1;
slidingPanel.offset = slidingPanel.offset + (mouse.y - oldMouseY) * factor;
oldMouseY = mouse.y;
}
onReleased: slidingPanel.updateState();
}
SlidingPanel {
id: slidingPanel
width: plasmoid.availableScreenRect.width
height: plasmoid.availableScreenRect.height
contents: Item {
id: panelContents
anchors.fill: parent
clip: true
Item {
id: lastSpacer
Layout.fillWidth: true
Layout.fillHeight: true
}
Column {
id: layout
anchors.fill: parent
spacing: units.smallSpacing
}
}
}
}

View file

@ -0,0 +1,18 @@
[Desktop Entry]
Encoding=UTF-8
Keywords=
Name=Phone Panel
Type=Service
X-KDE-ServiceTypes=Plasma/Applet,Plasma/Containment
X-Plasma-API=declarativeappletscript
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Marco Martin
X-KDE-PluginInfo-Category=
X-KDE-PluginInfo-Email=mart@kde.org
X-KDE-PluginInfo-License=GPLv2+
X-KDE-PluginInfo-Name=org.kde.phone.panel
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=
X-Plasma-MainScript=ui/main.qml
X-Plasma-ContainmentType=Panel

5
dialer/CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
kpackage_install_package(package org.kde.phone.dialer genericqml)
install(FILES package/metadata.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} RENAME org.kde.phone.dialer.desktop)
add_subdirectory(src)

View file

@ -0,0 +1,99 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.nemomobile.voicecall 1.0
MouseArea {
id: root
signal accepted
signal rejected
Layout.minimumHeight: units.gridUnit * 5
Layout.fillWidth: true
property int handlePosition: (answerHandle.x + answerHandle.width/2)
drag {
target: answerHandle
axis: Drag.XAxis
minimumX: 0
maximumX: width - answerHandle.width
}
Rectangle {
anchors.fill: parent
radius: height
color: Qt.rgba((handlePosition > root.width/2 ? 0.6 : 0)+0.2, (handlePosition < root.width/2 ? 0.6 : 0)+0.2, 0.2, Math.abs(handlePosition - (root.width/2)) / answerHandle.width/2);
PlasmaCore.IconItem {
source: "call-start"
width: icon.width
height: width
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: units.largeSpacing
}
}
PlasmaCore.IconItem {
source: "call-stop"
width: icon.width
height: width
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
leftMargin: units.largeSpacing
}
}
Rectangle {
id: answerHandle
x: parent.width/2 - width/2
height: parent.height
width: height
radius: width
color: Qt.rgba(0.2, 0.8, 0.2, 1)
PlasmaCore.IconItem {
id: icon
source: "call-start"
width: parent.width * 0.7
height: width
anchors.centerIn: parent
}
Behavior on x {
enabled: root.pressed
XAnimator {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
onReleased: {
if (answerHandle.x <= answerHandle.width) {
root.accepted();
} else if (answerHandle.x + answerHandle.width >= root.width - answerHandle.width) {
root.rejected();
}
answerHandle.x = width/2 - answerHandle.width/2
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: parent.height/2
Rectangle {
height: Math.min(parent.width, parent.height)
width: height
radius: 5
anchors.centerIn: parent
PlasmaCore.IconItem {
anchors {
fill: parent
centerIn: parent
margins: 20
}
source: "im-user"
}
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.nemomobile.voicecall 1.0
import "../Dialpad"
Item {
id: callPage
state: voiceCallmanager.activeVoiceCall ? voiceCallmanager.activeVoiceCall.statusText : "disconnected"
property int status: voiceCallmanager.activeVoiceCall ? voiceCallmanager.activeVoiceCall.status : 0
property string providerId: voiceCallmanager.providers.id(0)
function secondsToTimeString(seconds) {
seconds = Math.floor(seconds/1000)
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds - (h * 3600)) / 60);
var s = seconds - h * 3600 - m * 60;
if(h < 10) h = '0' + h;
if(m < 10) m = '0' + m;
if(s < 10) s = '0' + s;
return '' + h + ':' + m + ':' + s;
}
onStatusChanged: {
if (status != 1) {
dialerButton.checked = false;
}
}
ColumnLayout {
id: activeCallUi
spacing: 10
anchors {
fill: parent
margins: 20
}
Flickable {
id: topFlickable
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: parent.height/2
contentWidth: topContents.width
contentHeight: topContents.height
interactive: status == 1;
Row {
id: topContents
Avatar {
width: topFlickable.width
height: topFlickable.height
}
Dialpad {
width: topFlickable.width
height: topFlickable.height
callback: function (string) {
if (voiceCallmanager.activeVoiceCall) {
voiceCallmanager.activeVoiceCall.sendDtmf(string);
}
}
}
}
onMovingChanged: {
var checked = contentX > topFlickable.width/2;
if (checked) {
topSlideAnim.to = topFlickable.width;
} else {
topSlideAnim.to = 0;
}
dialerButton.checked = checked;
topSlideAnim.running = true;
}
PropertyAnimation {
id: topSlideAnim
target: topFlickable
properties: "contentX"
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
PlasmaComponents.Label {
Layout.fillWidth: true
Layout.minimumHeight: implicitHeight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.pointSize: theme.defaultFont.pointSize * 2
text: voiceCallmanager.activeVoiceCall ? voiceCallmanager.activeVoiceCall.lineId : ""
}
PlasmaComponents.Label {
Layout.fillWidth: true
Layout.minimumHeight: implicitHeight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
text: {
if (!voiceCallmanager.activeVoiceCall) {
return '';
//STATUS_DIALING
} else if (voiceCallmanager.activeVoiceCall.status == 3) {
return i18n("Calling...");
} else if (voiceCallmanager.activeVoiceCall.duration > 0) {
return secondsToTimeString(voiceCallmanager.activeVoiceCall.duration);
} else {
return '';
}
}
}
PlasmaComponents.ButtonRow {
opacity: status == 1 ? 1 : 0
exclusive: false
spacing: 0
Layout.alignment: Qt.AlignHCenter
PlasmaComponents.ToolButton {
id: muteButton
flat: false
iconSource: voiceCallmanager.isMicrophoneMuted ? "audio-volume-muted" : "audio-volume-high"
onClicked: {
voiceCallmanager.isMicrophoneMuted = !voiceCallmanager.isMicrophoneMuted;
}
}
PlasmaComponents.ToolButton {
id: dialerButton
flat: false
iconSource: "input-keyboard"
checkable: true
onCheckedChanged: {
if (checked) {
topSlideAnim.to = topFlickable.width;
} else {
topSlideAnim.to = 0;
}
topSlideAnim.running = true;
}
}
}
Item {
Layout.minimumHeight: units.gridUnit * 5
Layout.fillWidth: true
AnswerSwipe {
anchors.fill: parent
//STATUS_INCOMING
visible: status == 5
onAccepted: {
if (voiceCallmanager.activeVoiceCall) {
voiceCallmanager.activeVoiceCall.answer();
}
}
onRejected: {
if (voiceCallmanager.activeVoiceCall) {
voiceCallmanager.activeVoiceCall.hangup();
}
}
}
PlasmaComponents.Button {
anchors.fill: parent
//STATUS_INCOMING
visible: status != 5
iconSource: "call-stop"
Layout.fillWidth: true
text: i18n("End Call")
onClicked: {
if (voiceCallmanager.activeVoiceCall) {
voiceCallmanager.activeVoiceCall.hangup();
}
}
}
}
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.people 1.0 as KPeople
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons
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
Item {
PlasmaComponents.Label {
anchors.centerIn: parent
text: i18n("No contacts")
visible: contactsModel.count == 0
}
ColumnLayout {
anchors.fill: parent
//visible: contactsModel.count > 0
PlasmaComponents.ToolBar {
Layout.fillWidth: true
tools: RowLayout {
id: toolBarLayout
PlasmaComponents.TextField {
id: searchField
clearButtonShown: true
Layout.fillWidth: true
Layout.fillHeight: true
placeholderText: i18n("Search...")
}
}
}
PlasmaExtras.ScrollArea {
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: view
model: PlasmaCore.SortFilterModel {
id: filterModel
sourceModel: KPeople.PersonsModel {
id: contactsModel
}
sortRole: "display"
filterRole: "display"
filterRegExp: ".*"+searchField.text+".*"
sortOrder: Qt.AscendingOrder
}
section {
property: "display"
criteria: ViewSection.FirstCharacter
delegate: PlasmaComponents.ListItem {
id: sectionItem
sectionDelegate: true
PlasmaComponents.Label {
text: section
}
}
}
delegate: PlasmaComponents.ListItem {
RowLayout {
id: delegateLayout
KQuickControlsAddons.QPixmapItem {
id: avatarLabel
Layout.minimumWidth: units.iconSizes.medium
Layout.maximumWidth: Layout.minimumWidth
Layout.minimumHeight: Layout.minimumWidth
Layout.maximumHeight: Layout.minimumWidth
pixmap: model.decoration
fillMode: KQuickControlsAddons.QPixmapItem.PreserveAspectFit
smooth: true
}
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
PlasmaComponents.Label {
id: nickLabel
Layout.fillWidth: true
text: model.display
elide: Text.ElideRight
}
PlasmaComponents.Label {
id: dataLabel
Layout.fillWidth: true
text: "605-909-123"
elide: Text.ElideRight
}
}
}
MouseArea {
anchors.fill: parent
onClicked: call(12345)
}
}
}
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Column {
spacing: 0
PlasmaComponents.TabGroup {
anchors {
left: parent.left
right: parent.right
}
height: parent.height - tabbar.height
History {
id: history
}
Contacts {
id: contacts
}
Dialer {
id: dialer
}
}
PlasmaComponents.TabBar {
id: tabbar
height: units.gridUnit * 5
anchors {
left: parent.left
right: parent.right
}
tabPosition: Qt.BottomEdge
PlasmaComponents.TabButton {
iconSource: "view-history"
text: i18n("History")
tab: history
}
PlasmaComponents.TabButton {
iconSource: "view-pim-contacts"
text: i18n("Contacts")
tab: contacts
}
PlasmaComponents.TabButton {
iconSource: "input-keyboard"
text: i18n("Dialpad")
tab: dialer
}
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2014 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.nemomobile.voicecall 1.0
import "../Dialpad"
Item {
id: dialer
property alias numberEntryText: status.text
property string providerId: voiceCallmanager.providers.id(0)
function addNumber(number) {
status.text = status.text + number
}
ColumnLayout {
id: dialPadArea
anchors {
fill: parent
margins: units.largeSpacing
}
PlasmaComponents.Label {
id: status
Layout.fillWidth: true
Layout.minimumHeight: parent.height / 6
Layout.maximumHeight: Layout.minimumHeight
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter
font.pointSize: 1024
fontSizeMode: Text.Fit
}
Dialpad {
callback: function (string) {
addNumber(string);
}
pressedCallback: function (string) {
voiceCallmanager.startDtmfTone(string);
}
releasedCallback: function (string) {
voiceCallmanager.stopDtmfTone();
}
}
RowLayout {
Layout.fillWidth: true
Layout.minimumHeight: parent.height / 6
Layout.maximumHeight: Layout.minimumHeight
DialerIconButton {
id: callButton
Layout.minimumWidth: dialPadArea.width/3
Layout.fillWidth: true
Layout.fillHeight: true
enabled: status.text.length > 0
opacity: enabled ? 1 : 0.5
source: "call-start"
callback: function() {
call(status.text);
}
}
Item {
Layout.minimumWidth: dialPadArea.width/3
Layout.fillWidth: true
Layout.fillHeight: true
}
DialerIconButton {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: dialPadArea.width/3
enabled: status.text.length > 0
opacity: enabled ? 1 : 0.5
source: "edit-clear"
callback: function(text) {
if (status.text.length > 0) {
status.text = status.text.substr(0, status.text.length - 1);
}
}
}
}
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
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
Item {
function secondsToTimeString(seconds) {
seconds = Math.floor(seconds/1000)
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds - (h * 3600)) / 60);
var s = seconds - h * 3600 - m * 60;
if(h < 10) h = '0' + h;
if(m < 10) m = '0' + m;
if(s < 10) s = '0' + s;
return '' + h + ':' + m + ':' + s;
}
PlasmaComponents.Label {
anchors.centerIn: parent
text: i18n("No recent calls")
visible: historyModel.count == 0
}
ColumnLayout {
anchors.fill: parent
visible: historyModel.count > 0
PlasmaComponents.ToolBar {
Layout.fillWidth: true
tools: RowLayout {
id: toolBarLayout
PlasmaComponents.TabBar {
tabPosition: Qt.TopEdge
PlasmaComponents.TabButton {
iconSource: "call-start"
text: i18n("All")
onCheckedChanged: {
if (checked) {
filterModel.filterString = "";
}
}
}
PlasmaComponents.TabButton {
iconSource: "list-remove"
text: i18n("Missed")
onCheckedChanged: {
if (checked) {
filterModel.filterString = "0";
}
}
}
}
Item {
Layout.fillWidth: true
}
PlasmaComponents.Button {
text: i18n("Clear")
onClicked: clearHistory();
}
}
}
PlasmaExtras.ScrollArea {
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: view
model: PlasmaCore.SortFilterModel {
id: filterModel
sourceModel: historyModel
filterRole: "callType"
sortRole: "time"
sortOrder: Qt.DescendingOrder
}
section {
property: "date"
delegate: PlasmaComponents.ListItem {
id: sectionItem
sectionDelegate: true
PlasmaComponents.Label {
text: Qt.formatDate(section, Qt.locale().dateFormat(Locale.LongFormat));
}
}
}
delegate: HistoryDelegate {}
}
}
}
}

View file

@ -0,0 +1,122 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
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
Item {
id: delegateParent
width: view.width
height: childrenRect.height
Behavior on height {
SpringAnimation { spring: 5; damping: 0.3 }
}
SequentialAnimation {
id: removeAnim
XAnimator {
target: delegate
from: delegate.x
to: delegate.x > 0 ? width : -width
duration: units.longDuration
easing.type: Easing.InOutQuad
}
PropertyAnimation {
target: delegateParent
properties: "height"
to: 0
duration: units.longDuration
easing.type: Easing.InOutQuad
}
ScriptAction {
script: removeCallFromHistory(model.originalIndex);
}
}
XAnimator {
id: resetAnim
target: delegate
from: delegate.x
to: 0
duration: units.longDuration
easing.type: Easing.InOutQuad
}
PlasmaComponents.ListItem {
id: delegate
MouseArea {
width: parent.width
height: childrenRect.height
onClicked: call(model.number);
drag.axis: Drag.XAxis
drag.target: delegate
onReleased: {
if (drag.active) {
if (delegate.x > delegate.width / 3 || delegate.x < width / -3) {
removeAnim.running = true;
} else {
resetAnim.running = true;
}
}
}
RowLayout {
width: parent.width
//FIXME: ad hoc icons
PlasmaCore.IconItem {
width: units.iconSizes.medium
height: width
source: {
switch (model.callType) {
case 0:
return "list-remove";
case 1:
return "go-down";
case 2:
return "go-up";
}
}
}
ColumnLayout {
PlasmaComponents.Label {
text: "Name (todo)"
}
PlasmaComponents.Label {
text: i18n("Number: %1", model.number)
Layout.fillWidth: true
}
}
ColumnLayout {
PlasmaComponents.Label {
Layout.alignment: Qt.AlignRight
text: Qt.formatTime(model.time, Qt.locale().timeFormat(Locale.ShortFormat));
}
PlasmaComponents.Label {
Layout.alignment: Qt.AlignRight
text: i18n("Duration: %1", secondsToTimeString(model.duration));
}
}
}
}
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2014 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
PlasmaComponents.Label {
Layout.fillWidth: true
Layout.fillHeight: true
//This is 0 to override the Label default height that would cause a binding loop
height: 0
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.pointSize: 1024
fontSizeMode: Text.VerticalFit
property alias sub: longHold.text
property var callback
property var pressedCallback
property var releasedCallback
Rectangle {
anchors.fill: parent
z: -1
color: PlasmaCore.ColorScope.highlightColor
radius: units.smallSpacing
opacity: mouse.pressed ? 0.4 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
MouseArea {
id: mouse
anchors.fill: parent
onPressed: {
if (pressedCallback) {
pressedCallback(parent.text);
} else if (pad.pressedCallback) {
pad.pressedCallback(parent.text);
}
}
onReleased: {
if (releasedCallback) {
releasedCallback(parent.text);
} else if (pad.releasedCallback) {
pad.releasedCallback(parent.text);
}
}
onCanceled: {
if (releasedCallback) {
releasedCallback(parent.text);
} else if (pad.releasedCallback) {
pad.releasedCallback(parent.text);
}
}
onClicked: {
if (callback) {
callback(parent.text);
} else if (pad.callback) {
pad.callback(parent.text);
}
}
onPressAndHold: {
var text;
if (longHold.visible) {
text = longHold.text;
} else {
text = parent.text;
}
if (text.length > 1) {
return;
}
if (callback) {
callback(text);
} else if (pad.callback) {
pad.callback(text);
}
}
}
PlasmaComponents.Label {
id: longHold
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: units.largeSpacing
}
height: parent.height * 0.4
width: parent.width / 4
verticalAlignment: Qt.AlignVCenter
visible: text.length > 0
opacity: 0.6
font.pointSize: 1024
fontSizeMode: Text.Fit
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2014 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Item {
id: buttonRoot
Layout.fillWidth: true
Layout.fillHeight: true
property var callback
property var pressedCallback
property var releasedCallback
property string sub
property alias source: icon.source
property alias text: label.text
Rectangle {
anchors.fill: parent
z: -1
color: PlasmaCore.ColorScope.highlightColor
radius: units.smallSpacing
opacity: mouse.pressed ? 0.4 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Row {
anchors.centerIn: parent
PlasmaCore.IconItem {
id: icon
anchors.verticalCenter: parent.verticalCenter
width: height
height: buttonRoot.height * 0.6
}
PlasmaComponents.Label {
id: label
height: buttonRoot.height
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 1024
fontSizeMode: Text.VerticalFit
}
}
MouseArea {
id: mouse
anchors.fill: parent
onClicked: {
if (callback) {
callback(parent.text);
} else {
addNumber(parent.text);
}
}
onPressAndHold: {
var text;
if (longHold.visible) {
text = longHold.text;
} else {
text = parent.text;
}
if (callback) {
callback(text);
} else if (pad.callback) {
pad.callback(text);
}
}
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2014 Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
GridLayout {
id: pad
columns: 3
property var callback
property var pressedCallback
property var releasedCallback
property int buttonHeight: parent.height / 6
Layout.fillWidth: true
Layout.fillHeight: true
DialerButton { id: one; text: "1" }
DialerButton { text: "2"; sub: "ABC" }
DialerButton { text: "3"; sub: "DEF" }
DialerButton { text: "4"; sub: "GHI" }
DialerButton { text: "5"; sub: "JKL" }
DialerButton { text: "6"; sub: "MNO" }
DialerButton { text: "7"; sub: "PQRS" }
DialerButton { text: "8"; sub: "TUV" }
DialerButton { text: "9"; sub: "WXYZ" }
DialerButton { text: "*"; }
DialerButton { text: "0"; sub: "+"; }
DialerButton { text: "#" }
}

View file

@ -0,0 +1,296 @@
/**
* Copyright 2014 Aaron Seigo <aseigo@kde.org>
* Copyright 2014 Marco Martin <mart@kde.org>
*
* 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 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.
*/
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQuick.LocalStorage 2.0
import org.nemomobile.voicecall 1.0
import MeeGo.QOfono 0.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtras
ApplicationWindow {
id: root
//BEGIN PROPERTIES
width: 600
height: 800
property int status: voiceCallmanager.activeVoiceCall ? voiceCallmanager.activeVoiceCall.status : 0
//keep track of the status we were in
property int previousStatus
//keep track if we were visible when ringing
property bool wasVisible
//support a single provider for now
property string providerId: voiceCallmanager.providers.id(0)
//was the last call an incoming one?
property bool isIncoming
//END PROPERTIES
//BEGIN SIGNAL HANDLERS
onStatusChanged: {
//STATUS_ACTIVE
if (status == 1) {
root.isIncoming = voiceCallmanager.activeVoiceCall.isIncoming;
//STATUS_INCOMING
} else if (status == 5) {
wasVisible = root.visible;
root.visible = true;
dialerUtils.notifyRinging();
//Was STATUS_INCOMING now is STATUS_DISCONNECTED: Missed call!
} else if (status == 7 && previousStatus == 5) {
var prettyDate = Qt.formatTime(voiceCallmanager.activeVoiceCall.startedAt, Qt.locale().timeFormat(Locale.ShortFormat));
dialerUtils.notifyMissedCall(voiceCallmanager.activeVoiceCall.lineId, i18n("%1 called at %2", voiceCallmanager.activeVoiceCall.lineId, prettyDate));
root.visible = wasVisible;
insertCallInHistory(voiceCallmanager.activeVoiceCall.lineId, 0, 0);
} else if (status == 7) {
insertCallInHistory(voiceCallmanager.activeVoiceCall.lineId, voiceCallmanager.activeVoiceCall.duration, isIncoming ? 1 : 2);
}
if (status != 5) {
dialerUtils.stopRinging();
}
previousStatus = status;
}
Connections {
target: dialerUtils
onMissedCallsActionTriggered: {
root.visible = true;
}
}
onVisibleChanged: {
//reset missed calls if the status is not STATUS_INCOMING when got visible
if (visible && status != 5) {
dialerUtils.resetMissedCalls();
}
}
//END SIGNAL HANDLERS
//BEGIN FUNCTIONS
function call(number) {
if (!voiceCallmanager.activeVoiceCall) {
console.log("Calling: " + providerId + " " + number);
voiceCallmanager.dial(providerId, number);
} else {
console.log("Hanging up: " + voiceCallmanager.activeVoiceCall.lineId);
status.text = '';
var call = voiceCallmanager.activeVoiceCall;
if (call) {
call.hangup();
}
}
}
function insertCallInHistory(number, duration, callType) {
//DATABSE
var db = LocalStorage.openDatabaseSync("PlasmaPhoneDialer", "1.0", "Call history of the Plasma Phone dialer", 1000000);
db.transaction(
function(tx) {
var rs = tx.executeSql("INSERT INTO History VALUES(NULL, ?, datetime('now'), ?, ? )", [number, duration, callType]);
var rs = tx.executeSql('SELECT * FROM History where id=?', [rs.insertId]);
for(var i = 0; i < rs.rows.length; i++) {
var row = rs.rows.item(i);
row.date = Qt.formatDate(row.time, "yyyy-MM-dd");
row.originalIndex = historyModel.count;
historyModel.append(row);
}
}
)
}
//index is historyModel row number, not db id and not sortmodel row number
function removeCallFromHistory(index) {
var item = historyModel.get(index);
if (!item) {
return;
}
var db = LocalStorage.openDatabaseSync("PlasmaPhoneDialer", "1.0", "Call history of the Plasma Phone dialer", 1000000);
db.transaction(
function(tx) {
tx.executeSql("DELETE from History WHERE id=?", [item.id]);
}
)
historyModel.remove(index);
}
function clearHistory() {
var db = LocalStorage.openDatabaseSync("PlasmaPhoneDialer", "1.0", "Call history of the Plasma Phone dialer", 1000000);
db.transaction(
function(tx) {
tx.executeSql("DELETE from History");
}
)
historyModel.clear();
}
//END FUNCTIONS
//BEGIN DATABASE
Component.onCompleted: {
//HACK: make sure activeVoiceCall is loaded if already existing
voiceCallmanager.voiceCalls.onVoiceCallsChanged();
voiceCallmanager.onActiveVoiceCallChanged();
//DATABSE
var db = LocalStorage.openDatabaseSync("PlasmaPhoneDialer", "1.0", "Call history of the Plasma Phone dialer", 1000000);
db.transaction(
function(tx) {
// Create the database if it doesn't already exist
//callType: wether is incoming, outgoing, unanswered
tx.executeSql('CREATE TABLE IF NOT EXISTS History(id INTEGER PRIMARY KEY AUTOINCREMENT, number TEXT, time DATETIME, duration INTEGER, callType INTEGER)');
var rs = tx.executeSql('SELECT * FROM History');
for(var i = 0; i < rs.rows.length; i++) {
var row = rs.rows.item(i);
row.date = Qt.formatDate(row.time, "yyyy-MM-dd");
row.originalIndex = historyModel.count;
historyModel.append(row);
}
}
)
}
//END DATABASE
//BEGIN MODELS
ListModel {
id: historyModel
}
OfonoManager {
id: ofonoManager
onAvailableChanged: {
console.log("Ofono is " + available)
}
onModemAdded: {
console.log("modem added " + modem)
}
onModemRemoved: console.log("modem removed")
}
OfonoConnMan {
id: ofono1
Component.onCompleted: {
console.log(ofonoManager.modems)
}
modemPath: ofonoManager.modems.length > 0 ? ofonoManager.modems[0] : ""
}
OfonoModem {
id: modem1
modemPath: ofonoManager.modems.length > 0 ? ofonoManager.modems[0] : ""
}
OfonoContextConnection {
id: context1
contextPath : ofono1.contexts.length > 0 ? ofono1.contexts[0] : ""
Component.onCompleted: {
print("Context Active: " + context1.active)
}
onActiveChanged: {
print("Context Active: " + context1.active)
}
}
OfonoNetworkRegistration {
id: netreg
Component.onCompleted: {
netreg.scan()
}
onNetworkOperatorsChanged : {
console.log("operators :"+netreg.currentOperator["Name"].toString())
}
modemPath: ofonoManager.modems.length ? ofonoManager.modems[0] : ""
}
OfonoNetworkOperator {
id: netop
}
VoiceCallManager {
id: voiceCallmanager
onActiveVoiceCallChanged: {
if (activeVoiceCall) {
//main.activeVoiceCallPerson = people.personByPhoneNumber(activeVoiceCall.lineId);
// dialerOverlay.item.numberEntryText = activeVoiceCall.lineId;
} else {
// dialerOverlay.item.numberEntryText = '';
//main.activeVoiceCallPerson = null;
}
}
onError: {
console.log('*** QML *** VCM ERROR: ' + message);
}
}
//END MODELS
//BEGIN UI
PlasmaExtras.ConditionalLoader {
anchors.fill: parent
when: root.visible && root.status == 0
source: Qt.resolvedUrl("Dialer/DialPage.qml")
z: root.status == 0 ? 2 : 0
opacity: root.status == 0 ? 1 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
PlasmaExtras.ConditionalLoader {
anchors.fill: parent
when: root.status > 0
source: Qt.resolvedUrl("Call/CallPage.qml")
opacity: root.status > 0 ? 1 : 0
z: root.status > 0 ? 2 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
//END UI
}

View file

@ -0,0 +1,21 @@
[Desktop Entry]
Comment=Plasma phone dialer
Encoding=UTF-8
Keywords=
Name=Phone
Type=Application
Icon=call-start
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Marco Martin
X-KDE-PluginInfo-Category=Communications
X-KDE-PluginInfo-Email=mart@kde.org
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=org.kde.phone.dialer
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=KPackage/Generic
Exec=plasmaphonedialer %u
X-Plasma-MainScript=ui/main.qml
X-Plasma-RemoteLocation=
MimeType=x-scheme-handler/call

26
dialer/src/CMakeLists.txt Normal file
View file

@ -0,0 +1,26 @@
set(plasmaphonedialer_SRCS
main.cpp
dialerutils.cpp
)
add_executable(plasmaphonedialer ${plasmaphonedialer_SRCS})
target_compile_definitions(plasmaphonedialer PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
#find_package(ActiveApp REQUIRED)
target_link_libraries(plasmaphonedialer
Qt5::Gui
Qt5::Quick
Qt5::Widgets
KF5::Declarative
KF5::I18n
KF5::Package
KF5::QuickAddons
KF5::DBusAddons
KF5::Notifications
)
install(TARGETS plasmaphonedialer ${INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES plasma_dialer.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR})

View file

@ -0,0 +1,95 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "dialerutils.h"
#include <QDebug>
#include <KLocalizedString>
DialerUtils::DialerUtils(QObject *parent)
: QObject(parent),
m_missedCalls(0)
{
}
DialerUtils::~DialerUtils()
{
}
void DialerUtils::notifyMissedCall(const QString &caller, const QString &description)
{
qWarning() << "Missed Call.";
++m_missedCalls;
if (!m_callsNotification) {
m_callsNotification = new KNotification("callMissed", KNotification::Persistent, 0);
}
m_callsNotification->setComponentName("plasma_dialer");
m_callsNotification->setIconName("call-start");
if (m_missedCalls == 1) {
m_callsNotification->setTitle(i18n("Missed call from %1", caller));
m_callsNotification->setText(description);
} else {
m_callsNotification->setTitle(i18n("%1 calls missed", m_missedCalls));
m_callsNotification->setText(i18n("Last call: %1", description));
}
QStringList actions;
actions.append(i18n("View"));
m_callsNotification->setActions(actions);
QObject::connect(m_callsNotification.data(), &KNotification::action1Activated,
[=]() {
qWarning()<<"View action activated";
emit missedCallsActionTriggered();
resetMissedCalls();
});
if (m_missedCalls == 1) {
m_callsNotification->sendEvent();
} else {
m_callsNotification->update();
}
}
void DialerUtils::resetMissedCalls()
{
m_missedCalls = 0;
if (m_callsNotification) {
m_callsNotification->close();
}
m_callsNotification.clear();
}
void DialerUtils::notifyRinging()
{
if (!m_ringingNotification) {
m_ringingNotification = new KNotification("ringing", KNotification::Persistent|KNotification::LoopSound, 0);
m_ringingNotification->setComponentName("plasma_dialer");
}
m_ringingNotification->sendEvent();
}
void DialerUtils::stopRinging()
{
if (m_ringingNotification) {
m_ringingNotification->close();
}
}
#include "moc_dialerutils.cpp"

49
dialer/src/dialerutils.h Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef DIALERUTILS_H
#define DIALERUTILS_H
#include <QObject>
#include <QPointer>
#include <KNotification>
class DialerUtils : public QObject
{
Q_OBJECT
public:
DialerUtils(QObject *parent = 0);
virtual ~DialerUtils();
Q_INVOKABLE void notifyMissedCall(const QString &caller, const QString &description);
Q_INVOKABLE void resetMissedCalls();
Q_INVOKABLE void notifyRinging();
Q_INVOKABLE void stopRinging();
Q_SIGNALS:
void missedCallsActionTriggered();
private:
QPointer <KNotification> m_callsNotification;
QPointer <KNotification> m_ringingNotification;
int m_missedCalls;
};
#endif

130
dialer/src/main.cpp Normal file
View file

@ -0,0 +1,130 @@
/*
* Copyright 2015 Marco Martin <mart@kde.org>
*
* 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 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 <QApplication>
#include "dialerutils.h"
#include <klocalizedstring.h>
#include <qcommandlineparser.h>
#include <qcommandlineoption.h>
#include <QQuickItem>
#include <QtQml>
#include <kpackage/package.h>
#include <kpackage/packageloader.h>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlExpression>
#include <QQmlProperty>
#include <QQuickWindow>
#include <kdeclarative/qmlobject.h>
#include <KAboutData>
#include <KDBusService>
int main(int argc, char **argv)
{
QCommandLineParser parser;
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
KDBusService service(KDBusService::Unique);
const QString description = i18n("Plasma Phone Dialer");
const char version[] = PROJECT_VERSION;
app.setApplicationVersion(version);
parser.addVersionOption();
parser.addHelpOption();
parser.setApplicationDescription(description);
QCommandLineOption daemonOption(QStringList() << QStringLiteral("d") <<
QStringLiteral("daemon"),
i18n("Daemon mode. run without displaying anything."));
parser.addPositionalArgument("number", i18n("Call the given number"));
parser.addOption(daemonOption);
parser.process(app);
const QString packagePath("org.kde.phone.dialer");
//usually we have an ApplicationWindow here, so we do not need to create a window by ourselves
KDeclarative::QmlObject *obj = new KDeclarative::QmlObject();
obj->setTranslationDomain(packagePath);
obj->setInitializationDelayed(true);
obj->loadPackage(packagePath);
obj->engine()->rootContext()->setContextProperty("commandlineArguments", parser.positionalArguments());
DialerUtils *dialerUtils = new DialerUtils;
obj->engine()->rootContext()->setContextProperty("dialerUtils", QVariant::fromValue(dialerUtils));
obj->completeInitialization();
if (!obj->package().metadata().isValid()) {
return -1;
}
KPluginMetaData data = obj->package().metadata();
// About data
KAboutData aboutData(data.pluginId(), data.name(), data.version(), data.description(), KAboutLicense::byKeyword(data.license()).key());
for (auto author : data.authors()) {
aboutData.addAuthor(author.name(), author.task(), author.emailAddress(), author.webAddress(), author.ocsUsername());
}
//The root is not a window?
//have to use a normal QQuickWindow since the root item is already created
QWindow *window = qobject_cast<QWindow *>(obj->rootObject());
if (window) {
QObject::connect(&service, &KDBusService::activateRequested, [=](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
window->show();
window->requestActivate();
if (arguments.length() > 0) {
QString numberArg = arguments[1];
if (numberArg.startsWith("call://")) {
numberArg = numberArg.mid(7);
}
obj->rootObject()->metaObject()->invokeMethod(obj->rootObject(), "call", Q_ARG(QVariant, numberArg));
}
});
if (!parser.isSet(daemonOption)) {
window->show();
window->requestActivate();
}
window->setTitle(obj->package().metadata().name());
window->setIcon(QIcon::fromTheme(obj->package().metadata().iconName()));
if (!parser.positionalArguments().isEmpty()) {
QString numberArg = parser.positionalArguments().first();
if (numberArg.startsWith("call://")) {
numberArg = numberArg.mid(7);
}
qWarning() << "Calling" << numberArg;
obj->rootObject()->metaObject()->invokeMethod(obj->rootObject(), "call", Q_ARG(QVariant, numberArg));
}
} else {
qWarning() << "Error loading the ApplicationWindow";
}
return app.exec();
}

View file

@ -0,0 +1,15 @@
[Global]
IconName=call-start
Comment=Phone
[Event/callMissed]
Name=Missed Call
Comment=A call has been missed
Action=Popup
[Event/ringing]
Name=Ringing
Comment=The phone is ringing
Action=Sound
Sound=KDE-Sys-Question.ogg

View file

@ -0,0 +1,187 @@
/*
* Copyright 2015 Martin Klapetek <mklapetek@kde.org>
*
* 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 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.
*/
import QtQuick 2.0
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import org.kde.people 1.0 as KPeople
import org.kde.kquickcontrolsaddons 2.0 as ExtraComponents
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
PlasmaExtras.ScrollArea {
anchors.fill: parent
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
contentItem: ListView {
id: contactsList
property bool delegateSelected: false
section.property: "display"
section.criteria: ViewSection.FirstCharacter
clip: true
model: PlasmaCore.SortFilterModel {
sortRole: "display"
sourceModel: KPeople.PersonsModel {
id: contactsModel
}
}
boundsBehavior: Flickable.StopAtBounds
highlight: PlasmaComponents.Highlight {
hover: contactsList.focus
}
highlightMoveDuration: 0
delegate: PlasmaComponents.ListItem {
height: units.gridUnit * 6
enabled: true
clip: true
opacity: contactsList.delegateSelected && contactsList.currentIndex != index ? 0.4 : 1
onClicked: {
if (contactsList.delegateSelected) {
contactsList.currentIndex = -1;
contactsList.delegateSelected = false;
} else {
contactsList.currentIndex = index;
contactsList.delegateSelected = true;
}
contactsList.toggleOverlayButtons(contactsList.delegateSelected);
}
RowLayout {
id: mainLayout
anchors.fill: parent
ExtraComponents.QPixmapItem {
id: avatarLabel
Layout.maximumWidth: parent.height
Layout.minimumWidth: parent.height
Layout.fillHeight: true
pixmap: model.decoration
fillMode: ExtraComponents.QPixmapItem.PreserveAspectFit
smooth: true
}
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Label {
id: nickLabel
Layout.fillWidth: true
text: model.display
elide: Text.ElideRight
}
Label {
id: dataLabel
Layout.fillWidth: true
text: "605-909-123"
elide: Text.ElideRight
}
}
}
}
function toggleOverlayButtons(show) {
if (show) {
settingsRect.parent = contactsList.currentItem
settingsRect.visible = true;
callRect.parent = contactsList.currentItem
callRect.visible = true;
} else {
settingsRect.visible = false;
callRect.visible = false;
}
}
Rectangle {
id: settingsRect
height: units.gridUnit * 6
width: height + units.gridUnit * 2
radius: 45
z: 100
visible: false
color: "lightblue"
anchors {
left: parent.left
leftMargin: -width/2
verticalCenter: parent.verticalCenter
}
PlasmaCore.IconItem {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: parent.height / 4
source: "configure-shortcuts"
}
}
Rectangle {
id: callRect
height: settingsRect.height
width: settingsRect.width
radius: height
z: 100
visible: false
color: "lightgreen"
anchors {
right: parent.right
rightMargin: -width/2
verticalCenter: parent.verticalCenter
}
PlasmaCore.IconItem {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: parent.height / 4
source: "call-start"
}
MouseArea {
anchors.fill: parent
//TODO: needs the proper number
onClicked: Qt.openUrlExternally("call://" + "605909123")
}
}
CustomSectionScroller {
listView: contactsList
}
}
}

View file

@ -0,0 +1,205 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Components project.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import "private/SectionScroller.js" as Sections
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.components 2.0 as PlasmaComponents
/**
* Similar to a ScrollBar or a ScrollDecorator.
*
* It's interactive and works on ListViews that have section.property set, so
* its contents are categorized.
*
* An indicator will say to what category the user scrolled to. Useful for
* things like address books or things sorted by date. Don't use with models
* too big (thousands of items) because it implies loading all the items to
* memory, as well loses precision.
*
* @inherit QtQuick.Item
*/
Item {
id: root
/**
* The listview the sectionScroller will operate on. This component doesn't
* work with Flickable or GridView.
*/
property ListView listView
onListViewChanged: {
if (listView && listView.model)
internal.initDirtyObserver();
}
Connections {
target: listView
onModelChanged: {
if (listView && listView.model) {
internal.initDirtyObserver()
}
}
}
implicitWidth: scrollBar.implicitWidth
Behavior on opacity {
NumberAnimation {
duration: units.longDuration
}
}
anchors {
right: listView.right
top: listView.top
bottom: listView.bottom
}
PlasmaComponents.RangeModel {
id: range
minimumValue: 0
maximumValue: Math.max(0, listView.contentHeight - listView.height)
stepSize: 0
//inverted: true
positionAtMinimum: root.width*2
positionAtMaximum: root.height - root.width*2
value: listView.contentY
onPositionChanged: {
var section = Sections.closestSection(position/listView.height);
if (section) {
if (listView.section.criteria == ViewSection.FirstCharacter) {
sectionLabel.text = section[0];
} else {
sectionLabel.text = section;
}
}
}
}
PlasmaComponents.ScrollBar {
id: scrollBar
flickableItem: listView
anchors.fill: parent
interactive: true
}
PlasmaCore.FrameSvgItem {
id: tooltip
imagePath: "widgets/background"
width: units.gridUnit * 5 + margins.left + margins.right
height: sectionLabel.height + subtitle.height + margins.top + margins.bottom
ColumnLayout {
anchors.centerIn: parent
PlasmaExtras.Title {
id: sectionLabel
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter
}
PlasmaComponents.Label {
id: subtitle
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
visible: text.length > 0
text: "Thursday, 7th"
}
}
y: 0
x: -listView.width/2 - width/2
opacity: sectionLabel.text && scrollBar.pressed ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: units.longDuration
}
}
}
Timer {
id: dirtyTimer
interval: 250
onTriggered: {
Sections.initSectionData(listView);
internal.modelDirty = false;
tooltip.visible = Sections._sections.length > 1
}
}
QtObject {
id: internal
property bool modelDirty: false
function initDirtyObserver() {
Sections.initSectionData(listView);
tooltip.visible = Sections._sections.length > 1
function dirtyObserver() {
if (!internal.modelDirty) {
internal.modelDirty = true;
dirtyTimer.running = true;
}
}
if (listView.model.countChanged)
listView.model.countChanged.connect(dirtyObserver);
if (listView.model.itemsChanged)
listView.model.itemsChanged.connect(dirtyObserver);
if (listView.model.itemsInserted)
listView.model.itemsInserted.connect(dirtyObserver);
if (listView.model.itemsMoved)
listView.model.itemsMoved.connect(dirtyObserver);
if (listView.model.itemsRemoved)
listView.model.itemsRemoved.connect(dirtyObserver);
}
}
Accessible.role: Accessible.ScrollBar
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2015 Martin Klapetek <mklapetek@kde.org>
*
* 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 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.
*/
import QtQuick 2.0
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
ApplicationWindow {
width: 720
height: 1280
visible: true
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
Layout.fillWidth: true
ToolButton {
text: i18n("Settings")
iconName: "call-start"
}
ToolButton {
text: i18n("Recent")
iconName: "appointment-new"
}
ToolButton {
text: i18n("Alphabetical")
iconName: "im-user"
}
}
}
ColumnLayout {
anchors.fill: parent
ContactsList {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}

View file

@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Components project.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
var _sectionData = [];
var _sections = [];
function initSectionData(list) {
if (!list || !list.model) return;
_sectionData = [];
_sections = [];
var current = "";
var prop = list.section.property;
for (var i = 0, count = list.model.count; i < count; i++) {
var item = list.model.get(i);
if (item[prop] !== current) {
current = item[prop];
_sections.push(current);
_sectionData.push({ index: i, header: current });
}
}
}
function closestSection(pos) {
var tmp = (_sections.length) * pos;
var val = Math.ceil(tmp) // TODO: better algorithm
val = val < 2 ? 1 : val;
return _sections[val-1];
}
function indexOf(sectionName) {
var val = _sectionData[_sections.indexOf(sectionName)].index;
return val === 0 || val > 0 ? val : -1;
}

View file

@ -0,0 +1,20 @@
[Desktop Entry]
Comment=
Encoding=UTF-8
Keywords=
Name=Phonebook
Type=Application
Icon=package_toys
Exec=kpackagelauncherqml -a org.kde.phone.phonebook
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Martin Klapetek
X-KDE-PluginInfo-Category=Miscellaneous
X-KDE-PluginInfo-Email=mklapetek@kde.org
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=org.kde.phone.phonebook
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=KPackage/GenericQML
X-Plasma-MainScript=ui/main.qml
X-Plasma-RemoteLocation=

View file

@ -0,0 +1,253 @@
import QtQuick 2.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Rectangle {
id: root
width: 300
height: 500
property Item currentApp
state: "switcher"
Text {
anchors.centerIn: parent
text: "Homescreen"
}
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.6 * Math.min(1, mainFlickable.contentY/(root.height*2))
}
Flickable {
id: mainFlickable
//Scale adjusted in the 0-1 range
property real zoomFactor: Math.max(mainFlickable.scale/0.5, 1) - 1
width: root.width * 2 + 5
height: root.height * 2 + 5
scale: 0.5
contentWidth: width
contentHeight: mainContent.height
Behavior on scale {
NumberAnimation {
duration: units.longDuration
easing.type: Easing.InOutQuad
}
}
onMovingChanged: {
if (!moving && contentY < root.height * 2) {
root.state = "dragging";
}
if (contentY < root.height) {
root.state = "homescreen"
} else {
root.state = "switcher"
}
}
Item {
id: mainContent
width: parent.width
height: flow.y + flow.height + root.height
Flow {
id: flow
anchors {
left: parent.left
right: parent.right
}
y: root.height*2
spacing: 5
Repeater {
model: 5
delegate: Rectangle {
id: appRect
color: "red"
width: root.width
height: root.height
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "scrolling"
root.currentApp = appRect
root.state = "app"
}
Text {
anchors.centerIn: parent
text: "App " + modelData
}
}
}
}
}
}
}
Rectangle {
id: bottomBar
z: 99
color: "blue"
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 50
MouseArea {
anchors.fill: parent
property int oldY
property real startY
onClicked: {
root.state = "switcher"
}
onPressed: {
if (root.state == "app") {
root.state = "zooming";
} else {
root.state = "dragging";
}
oldY = mouse.y;
startY = mouse.y;
}
onPositionChanged: {
if (root.state == "app" || root.state == "zooming") {
mainFlickable.scale = (1 - (startY - mouse.y) / root.height);
} else {
mainFlickable.contentY += oldY - mouse.y;
}
oldY = mouse.y;
}
onReleased: {
if (root.state == "app" || root.state == "zooming") {
if (mainFlickable.scale < 0.7) {
root.state = "switcher"
} else {
root.state = "app"
}
} else {
if (mainFlickable.contentY < root.height) {
root.state = "homescreen"
} else {
root.state = "switcher"
}
}
}
Row {
PlasmaComponents.ToolButton {
height: bottomBar.height
width: height
iconSource: "applications-other"
onClicked: root.state = "switcher"
}
PlasmaComponents.ToolButton {
height: bottomBar.height
width: height
iconSource: "go-home"
onClicked: root.state = "homescreen"
}
}
}
}
states: [
State {
name: "switcher"
PropertyChanges {
target: mainFlickable
scale: 0.5
x: -root.width / 2
y: -root.height / 2
interactive: true
contentY: root.height*2 + (root.currentApp ? root.currentApp.y : 0)
visible: true
}
},
State {
name: "dragging"
PropertyChanges {
target: mainFlickable
scale: 0.5
x: -root.width / 2
y: -root.height / 2
interactive: true
contentY: contentY
visible: true
}
},
State {
name: "zooming"
PropertyChanges {
target: mainFlickable
scale: scale
x: (-root.currentApp.x * mainFlickable.zoomFactor ) + (1 - mainFlickable.zoomFactor) * (-root.width / 2)
y: (-root.height / 2) * (1 - mainFlickable.zoomFactor)
interactive: true
contentY: (root.height*2 + (root.currentApp ? root.currentApp.y : 0)) * (1 - mainFlickable.zoomFactor) + (root.height*2 + root.currentApp.y) * mainFlickable.zoomFactor
visible: true
}
},
State {
name: "app"
PropertyChanges {
target: mainFlickable
scale: 1
x: -root.currentApp.x
y: 0
interactive: false
contentY: root.height*2 + root.currentApp.y
visible: true
}
},
State {
name: "homescreen"
PropertyChanges {
target: mainFlickable
scale: 0.5
x: -root.width / 2
y: -root.height / 2
interactive: true
contentY: 0
visible: true
}
}
]
transitions: [
Transition {
to: "dragging"
ScriptAction {
script: {
root.currentApp = null;
}
}
},
Transition {
to: "zooming"
},
Transition {
SequentialAnimation {
ScriptAction {
script: {
if (root.state != "homescreen") {
mainFlickable.visible = true;
}
}
}
PropertyAnimation {
target: mainFlickable
duration: units.longDuration
easing.type: Easing.InOutQuad
properties: "x,y,scale,contentY"
}
ScriptAction {
script: {
if (root.state == "homescreen") {
mainFlickable.visible = false;
root.currentApp = null;
}
}
}
}
}
]
}

View file

@ -20,4 +20,4 @@ install(TARGETS satellitecomponentsplugin DESTINATION ${QML_INSTALL_DIR}/org/kde
install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/satellite/components)
add_subdirectory(modeltest)

View file

@ -30,13 +30,16 @@
#include <KService>
#include <KServiceGroup>
#include <KServiceTypeTrader>
#include <KSycoca>
#include <KSycocaEntry>
#include <QDebug>
ApplicationListModel::ApplicationListModel(QObject *parent)
: QAbstractListModel(parent)
{
loadApplications();
//can't use the new syntax as this signal is overloaded
connect(KSycoca::self(), SIGNAL(databaseChanged(const QStringList &)),
this, SLOT(sycocaDbChanged(const QStringList &)));
}
ApplicationListModel::~ApplicationListModel()
@ -55,15 +58,35 @@ QHash<int, QByteArray> ApplicationListModel::roleNames() const
return roleNames;
}
void ApplicationListModel::sycocaDbChanged(const QStringList &changes)
{
if (!changes.contains("apps") && !changes.contains("xdgdata-apps")) {
return;
}
m_applicationList.clear();
loadApplications();
}
bool appNameLessThan(const ApplicationData &a1, const ApplicationData &a2)
{
return a1.name.toLower() < a2.name.toLower();
}
void ApplicationListModel::loadApplications()
{
beginResetModel();
m_applicationList.clear();
KServiceGroup::Ptr group = KServiceGroup::root();
if (!group || !group->isValid()) return;
KServiceGroup::List subGroupList = group->entries(true);
QMap<int, ApplicationData> orderedList;
QList<ApplicationData> unorderedList;
// Iterate over all entries in the group
for(KServiceGroup::List::ConstIterator it = subGroupList.begin();it != subGroupList.end(); it++) {
KSycocaEntry::Ptr groupEntry = (*it);
@ -85,7 +108,12 @@ void ApplicationListModel::loadApplications()
data.icon = service->icon();
data.storageId = service->storageId();
data.entryPath = service->exec();
m_applicationList << data;
if (m_appPositions.contains(service->storageId())) {
orderedList[m_appPositions.value(service->storageId())] = data;
} else {
unorderedList << data;
}
}
}
}
@ -93,6 +121,10 @@ void ApplicationListModel::loadApplications()
}
}
std::sort(unorderedList.begin(), unorderedList.end(), appNameLessThan);
m_applicationList << orderedList.values();
m_applicationList << unorderedList;
endResetModel();
emit countChanged();
}
@ -121,6 +153,13 @@ QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
}
}
Qt::ItemFlags ApplicationListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsDragEnabled|QAbstractItemModel::flags(index);
}
int ApplicationListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
@ -130,6 +169,11 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
return m_applicationList.count();
}
void ApplicationListModel::moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild)
{
moveItem(sourceRow, destinationChild);
}
Q_INVOKABLE void ApplicationListModel::moveItem(int row, int destination)
{
if (row < 0 || destination < 0 || row >= m_applicationList.length() ||
@ -141,8 +185,27 @@ Q_INVOKABLE void ApplicationListModel::moveItem(int row, int destination)
}
beginMoveRows(QModelIndex(), row, row, QModelIndex(), destination);
ApplicationData data = m_applicationList.takeAt(row);
m_applicationList.insert(destination, data);
if (destination > row) {
ApplicationData data = m_applicationList.at(row);
m_applicationList.insert(destination, data);
m_applicationList.takeAt(row);
} else {
ApplicationData data = m_applicationList.takeAt(row);
m_applicationList.insert(destination, data);
}
m_appOrder.clear();
m_appPositions.clear();
int i = 0;
for (auto app : m_applicationList) {
m_appOrder << app.storageId;
m_appPositions[app.storageId] = i;
++i;
}
emit appOrderChanged();
endMoveRows();
}
@ -154,7 +217,29 @@ void ApplicationListModel::runApplication(const QString &storageId)
KService::Ptr service = KService::serviceByStorageId(storageId);
QProcess::startDetached(service->exec());
//ignore parameters like %u
QProcess::startDetached(service->exec().replace(QRegExp("%\\w"), ""));
}
QStringList ApplicationListModel::appOrder() const
{
return m_appOrder;
}
void ApplicationListModel::setAppOrder(const QStringList &order)
{
if (m_appOrder == order) {
return;
}
m_appOrder = order;
m_appPositions.clear();
int i = 0;
for (auto app : m_appOrder) {
m_appPositions[app] = i;
++i;
}
emit appOrderChanged();
}
#include "applicationlistmodel.moc"

View file

@ -38,6 +38,7 @@ class ApplicationListModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QStringList appOrder READ appOrder WRITE setAppOrder NOTIFY appOrderChanged)
public:
ApplicationListModel(QObject *parent = 0);
@ -45,10 +46,14 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
void moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild);
int count() { return m_applicationList.count(); }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
enum Roles {
@ -59,16 +64,27 @@ public:
ApplicationOriginalRowRole = Qt::UserRole + 6
};
QStringList appOrder() const;
void setAppOrder(const QStringList &order);
Q_INVOKABLE void moveItem(int row, int order);
Q_INVOKABLE void runApplication(const QString &storageId);
Q_INVOKABLE void loadApplications();
public Q_SLOTS:
void sycocaDbChanged(const QStringList &change);
Q_SIGNALS:
void countChanged();
void appOrderChanged();
private:
QList<ApplicationData> m_applicationList;
void loadApplications();
QStringList m_appOrder;
QHash<QString, int> m_appPositions;
};
#endif // APPLICATIONLISTMODEL_H

View file

@ -0,0 +1,15 @@
set(applicationlistmodeltest_SRCS
main.cpp
modeltest.cpp
../applicationlistmodel.cpp
)
add_executable(applicationlistmodeltest ${applicationlistmodeltest_SRCS})
target_link_libraries(applicationlistmodeltest
Qt5::Core
Qt5::Test
Qt5::Widgets
KF5::Service
)

View file

@ -0,0 +1,54 @@
#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QDebug>
#include "../applicationlistmodel.h"
#include "modeltest.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *widget = new QWidget;
QVBoxLayout *layout = new QVBoxLayout(widget);
ApplicationListModel *applicationListModel = new ApplicationListModel(widget);
ModelTest *test = new ModelTest(applicationListModel, widget);
QTreeView *view = new QTreeView(widget);
QPushButton *upButton = new QPushButton(widget);
upButton->setText("Move Up");
QObject::connect(upButton, &QPushButton::clicked, [=](){
QModelIndex idx = view->currentIndex();
if (idx.row() > 0) {
applicationListModel->moveItem(idx.row(), idx.row()-1);
}
});
QPushButton *downButton = new QPushButton(widget);
downButton->setText("Move Down");
QObject::connect(downButton, &QPushButton::clicked, [=](){
QModelIndex idx = view->currentIndex();
if (idx.row() > 0) {
applicationListModel->moveItem(idx.row(), idx.row()+1);
}
});
layout->addWidget(upButton);
layout->addWidget(downButton);
view->setDragDropMode(QAbstractItemView::InternalMove);
view->setModel(applicationListModel);
applicationListModel->loadApplications();
layout->addWidget(view);
QAction *quit = new QAction(widget);
quit->setShortcut(Qt::CTRL + Qt::Key_Q);
QObject::connect(quit, SIGNAL(triggered()), &app, SLOT(quit()));
widget->addAction(quit);
widget->show();
return app.exec();
}

View file

@ -0,0 +1,592 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "modeltest.h"
#include <QtCore/QtCore>
#include <QtTest/QtTest>
/*!
Connect to all of the models signals. Whenever anything happens recheck everything.
*/
ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false )
{
if (!model)
qFatal("%s: model must not be null", Q_FUNC_INFO);
connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests()) );
connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests()) );
connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests()) );
connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()) );
connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
this, SLOT(runAllTests()) );
// Special checks for changes
connect(model, SIGNAL(layoutAboutToBeChanged()),
this, SLOT(layoutAboutToBeChanged()) );
connect(model, SIGNAL(layoutChanged()),
this, SLOT(layoutChanged()) );
connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)) );
connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)) );
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(rowsInserted(QModelIndex,int,int)) );
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(rowsRemoved(QModelIndex,int,int)) );
connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(dataChanged(QModelIndex,QModelIndex)) );
connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
this, SLOT(headerDataChanged(Qt::Orientation,int,int)) );
runAllTests();
}
void ModelTest::runAllTests()
{
if ( fetchingMore )
return;
nonDestructiveBasicTest();
rowCount();
columnCount();
hasIndex();
index();
parent();
data();
}
/*!
nonDestructiveBasicTest tries to call a number of the basic functions (not all)
to make sure the model doesn't outright segfault, testing the functions that makes sense.
*/
void ModelTest::nonDestructiveBasicTest()
{
QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() );
model->canFetchMore ( QModelIndex() );
QVERIFY( model->columnCount ( QModelIndex() ) >= 0 );
QVERIFY( model->data ( QModelIndex() ) == QVariant() );
fetchingMore = true;
model->fetchMore ( QModelIndex() );
fetchingMore = false;
Qt::ItemFlags flags = model->flags ( QModelIndex() );
QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 );
model->hasChildren ( QModelIndex() );
model->hasIndex ( 0, 0 );
model->headerData ( 0, Qt::Horizontal );
model->index ( 0, 0 );
model->itemData ( QModelIndex() );
QVariant cache;
model->match ( QModelIndex(), -1, cache );
model->mimeTypes();
QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() );
QVERIFY( model->rowCount() >= 0 );
QVariant variant;
model->setData ( QModelIndex(), variant, -1 );
model->setHeaderData ( -1, Qt::Horizontal, QVariant() );
model->setHeaderData ( 999999, Qt::Horizontal, QVariant() );
QMap<int, QVariant> roles;
model->sibling ( 0, 0, QModelIndex() );
model->span ( QModelIndex() );
model->supportedDropActions();
}
/*!
Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
Models that are dynamically populated are not as fully tested here.
*/
void ModelTest::rowCount()
{
// qDebug() << "rc";
// check top row
QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
int rows = model->rowCount ( topIndex );
QVERIFY( rows >= 0 );
if ( rows > 0 )
QVERIFY( model->hasChildren ( topIndex ) );
QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex );
if ( secondLevelIndex.isValid() ) { // not the top level
// check a row count where parent is valid
rows = model->rowCount ( secondLevelIndex );
QVERIFY( rows >= 0 );
if ( rows > 0 )
QVERIFY( model->hasChildren ( secondLevelIndex ) );
}
// The models rowCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
*/
void ModelTest::columnCount()
{
// check top row
QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
QVERIFY( model->columnCount ( topIndex ) >= 0 );
// check a column count where parent is valid
QModelIndex childIndex = model->index ( 0, 0, topIndex );
if ( childIndex.isValid() )
QVERIFY( model->columnCount ( childIndex ) >= 0 );
// columnCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::hasIndex()
*/
void ModelTest::hasIndex()
{
// qDebug() << "hi";
// Make sure that invalid values returns an invalid index
QVERIFY( !model->hasIndex ( -2, -2 ) );
QVERIFY( !model->hasIndex ( -2, 0 ) );
QVERIFY( !model->hasIndex ( 0, -2 ) );
int rows = model->rowCount();
int columns = model->columnCount();
// check out of bounds
QVERIFY( !model->hasIndex ( rows, columns ) );
QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) );
if ( rows > 0 )
QVERIFY( model->hasIndex ( 0, 0 ) );
// hasIndex() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::index()
*/
void ModelTest::index()
{
// qDebug() << "i";
// Make sure that invalid values returns an invalid index
QVERIFY( model->index ( -2, -2 ) == QModelIndex() );
QVERIFY( model->index ( -2, 0 ) == QModelIndex() );
QVERIFY( model->index ( 0, -2 ) == QModelIndex() );
int rows = model->rowCount();
int columns = model->columnCount();
if ( rows == 0 )
return;
// Catch off by one errors
QVERIFY( model->index ( rows, columns ) == QModelIndex() );
QVERIFY( model->index ( 0, 0 ).isValid() );
// Make sure that the same index is *always* returned
QModelIndex a = model->index ( 0, 0 );
QModelIndex b = model->index ( 0, 0 );
QVERIFY( a == b );
// index() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::parent()
*/
void ModelTest::parent()
{
// qDebug() << "p";
// Make sure the model won't crash and will return an invalid QModelIndex
// when asked for the parent of an invalid index.
QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() );
if ( model->rowCount() == 0 )
return;
// Column 0 | Column 1 |
// QModelIndex() | |
// \- topIndex | topIndex1 |
// \- childIndex | childIndex1 |
// Common error test #1, make sure that a top level index has a parent
// that is a invalid QModelIndex.
QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
QVERIFY( model->parent ( topIndex ) == QModelIndex() );
// Common error test #2, make sure that a second level index has a parent
// that is the first level index.
if ( model->rowCount ( topIndex ) > 0 ) {
QModelIndex childIndex = model->index ( 0, 0, topIndex );
QVERIFY( model->parent ( childIndex ) == topIndex );
}
// Common error test #3, the second column should NOT have the same children
// as the first column in a row.
// Usually the second column shouldn't have children.
QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() );
if ( model->rowCount ( topIndex1 ) > 0 ) {
QModelIndex childIndex = model->index ( 0, 0, topIndex );
QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 );
QVERIFY( childIndex != childIndex1 );
}
// Full test, walk n levels deep through the model making sure that all
// parent's children correctly specify their parent.
checkChildren ( QModelIndex() );
}
/*!
Called from the parent() test.
A model that returns an index of parent X should also return X when asking
for the parent of the index.
This recursive function does pretty extensive testing on the whole model in an
effort to catch edge cases.
This function assumes that rowCount(), columnCount() and index() already work.
If they have a bug it will point it out, but the above tests should have already
found the basic bugs because it is easier to figure out the problem in
those tests then this one.
*/
void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth )
{
// First just try walking back up the tree.
QModelIndex p = parent;
while ( p.isValid() )
p = p.parent();
// For models that are dynamically populated
if ( model->canFetchMore ( parent ) ) {
fetchingMore = true;
model->fetchMore ( parent );
fetchingMore = false;
}
int rows = model->rowCount ( parent );
int columns = model->columnCount ( parent );
if ( rows > 0 )
QVERIFY( model->hasChildren ( parent ) );
// Some further testing against rows(), columns(), and hasChildren()
QVERIFY( rows >= 0 );
QVERIFY( columns >= 0 );
if ( rows > 0 )
QVERIFY( model->hasChildren ( parent ) );
//qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
// << "columns:" << columns << "parent column:" << parent.column();
const QModelIndex topLeftChild = model->index( 0, 0, parent );
QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) );
for ( int r = 0; r < rows; ++r ) {
if ( model->canFetchMore ( parent ) ) {
fetchingMore = true;
model->fetchMore ( parent );
fetchingMore = false;
}
QVERIFY( !model->hasIndex ( r, columns + 1, parent ) );
for ( int c = 0; c < columns; ++c ) {
QVERIFY( model->hasIndex ( r, c, parent ) );
QModelIndex index = model->index ( r, c, parent );
// rowCount() and columnCount() said that it existed...
QVERIFY( index.isValid() );
// index() should always return the same index when called twice in a row
QModelIndex modifiedIndex = model->index ( r, c, parent );
QVERIFY( index == modifiedIndex );
// Make sure we get the same index if we request it twice in a row
QModelIndex a = model->index ( r, c, parent );
QModelIndex b = model->index ( r, c, parent );
QVERIFY( a == b );
{
const QModelIndex sibling = model->sibling( r, c, topLeftChild );
QVERIFY( index == sibling );
}
{
const QModelIndex sibling = topLeftChild.sibling( r, c );
QVERIFY( index == sibling );
}
// Some basic checking on the index that is returned
QVERIFY( index.model() == model );
QCOMPARE( index.row(), r );
QCOMPARE( index.column(), c );
// While you can technically return a QVariant usually this is a sign
// of a bug in data(). Disable if this really is ok in your model.
// QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() );
// If the next test fails here is some somewhat useful debug you play with.
if (model->parent(index) != parent) {
qDebug() << r << c << currentDepth << model->data(index).toString()
<< model->data(parent).toString();
qDebug() << index << parent << model->parent(index);
// And a view that you can even use to show the model.
// QTreeView view;
// view.setModel(model);
// view.show();
}
// Check that we can get back our real parent.
QCOMPARE( model->parent ( index ), parent );
// recursively go down the children
if ( model->hasChildren ( index ) && currentDepth < 10 ) {
//qDebug() << r << c << "has children" << model->rowCount(index);
checkChildren ( index, ++currentDepth );
}/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
// make sure that after testing the children that the index doesn't change.
QModelIndex newerIndex = model->index ( r, c, parent );
QVERIFY( index == newerIndex );
}
}
}
/*!
Tests model's implementation of QAbstractItemModel::data()
*/
void ModelTest::data()
{
// Invalid index should return an invalid qvariant
QVERIFY( !model->data ( QModelIndex() ).isValid() );
if ( model->rowCount() == 0 )
return;
// A valid index should have a valid QVariant data
QVERIFY( model->index ( 0, 0 ).isValid() );
// shouldn't be able to set data on an invalid index
QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) );
// General Purpose roles that should return a QString
QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole );
if ( variant.isValid() ) {
QVERIFY( variant.canConvert<QString>() );
}
variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole );
if ( variant.isValid() ) {
QVERIFY( variant.canConvert<QString>() );
}
variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole );
if ( variant.isValid() ) {
QVERIFY( variant.canConvert<QString>() );
}
// General Purpose roles that should return a QSize
variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole );
if ( variant.isValid() ) {
QVERIFY( variant.canConvert<QSize>() );
}
// General Purpose roles that should return a QFont
QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole );
if ( fontVariant.isValid() ) {
QVERIFY( fontVariant.canConvert<QFont>() );
}
// Check that the alignment is one we know about
QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole );
if ( textAlignmentVariant.isValid() ) {
int alignment = textAlignmentVariant.toInt();
QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) );
}
// General Purpose roles that should return a QColor
QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole );
if ( colorVariant.isValid() ) {
QVERIFY( colorVariant.canConvert<QColor>() );
}
colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole );
if ( colorVariant.isValid() ) {
QVERIFY( colorVariant.canConvert<QColor>() );
}
// Check that the "check state" is one we know about.
QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole );
if ( checkStateVariant.isValid() ) {
int state = checkStateVariant.toInt();
QVERIFY( state == Qt::Unchecked ||
state == Qt::PartiallyChecked ||
state == Qt::Checked );
}
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsInserted()
*/
void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int /* end */)
{
// Q_UNUSED(end);
// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString()
// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) );
// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) );
Changing c;
c.parent = parent;
c.oldSize = model->rowCount ( parent );
c.last = model->data ( model->index ( start - 1, 0, parent ) );
c.next = model->data ( model->index ( start, 0, parent ) );
insert.push ( c );
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeInserted()
*/
void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end )
{
Changing c = insert.pop();
QVERIFY( c.parent == parent );
// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize
// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent );
// for (int ii=start; ii <= end; ii++)
// {
// qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent ));
// }
// qDebug();
QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) );
QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
qDebug() << start << end;
for (int i=0; i < model->rowCount(); ++i)
qDebug() << model->index(i, 0).data().toString();
qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
}
QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) );
}
void ModelTest::layoutAboutToBeChanged()
{
for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i )
changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) );
}
void ModelTest::layoutChanged()
{
for ( int i = 0; i < changing.count(); ++i ) {
QPersistentModelIndex p = changing[i];
QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) );
}
changing.clear();
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsRemoved()
*/
void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end )
{
qDebug() << "ratbr" << parent << start << end;
Changing c;
c.parent = parent;
c.oldSize = model->rowCount ( parent );
c.last = model->data ( model->index ( start - 1, 0, parent ) );
c.next = model->data ( model->index ( end + 1, 0, parent ) );
remove.push ( c );
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeRemoved()
*/
void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end )
{
qDebug() << "rr" << parent << start << end;
Changing c = remove.pop();
QVERIFY( c.parent == parent );
QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) );
QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) );
}
void ModelTest::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
QVERIFY(topLeft.isValid());
QVERIFY(bottomRight.isValid());
QModelIndex commonParent = bottomRight.parent();
QVERIFY(topLeft.parent() == commonParent);
QVERIFY(topLeft.row() <= bottomRight.row());
QVERIFY(topLeft.column() <= bottomRight.column());
int rowCount = model->rowCount(commonParent);
int columnCount = model->columnCount(commonParent);
QVERIFY(bottomRight.row() < rowCount);
QVERIFY(bottomRight.column() < columnCount);
}
void ModelTest::headerDataChanged(Qt::Orientation orientation, int start, int end)
{
QVERIFY(start >= 0);
QVERIFY(end >= 0);
QVERIFY(start <= end);
int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
QVERIFY(start < itemCount);
QVERIFY(end < itemCount);
}

View file

@ -0,0 +1,88 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MODELTEST_H
#define MODELTEST_H
#include <QtCore/QObject>
#include <QtCore/QAbstractItemModel>
#include <QtCore/QStack>
class ModelTest : public QObject
{
Q_OBJECT
public:
ModelTest( QAbstractItemModel *model, QObject *parent = 0 );
private Q_SLOTS:
void nonDestructiveBasicTest();
void rowCount();
void columnCount();
void hasIndex();
void index();
void parent();
void data();
protected Q_SLOTS:
void runAllTests();
void layoutAboutToBeChanged();
void layoutChanged();
void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end );
void rowsInserted( const QModelIndex & parent, int start, int end );
void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end );
void rowsRemoved( const QModelIndex & parent, int start, int end );
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void headerDataChanged(Qt::Orientation orientation, int start, int end);
private:
void checkChildren( const QModelIndex &parent, int currentDepth = 0 );
QAbstractItemModel *model;
struct Changing {
QModelIndex parent;
int oldSize;
QVariant last;
QVariant next;
};
QStack<Changing> insert;
QStack<Changing> remove;
bool fetchingMore;
QList<QPersistentModelIndex> changing;
};
#endif

View file

@ -0,0 +1,51 @@
/*
* Copyright 2013 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
RowLayout {
id: root
Layout.minimumWidth: units.gridUnit * 20
Layout.minimumHeight: units.gridUnit * 8
property alias reason: messageText.text
clip: true
PlasmaCore.IconItem {
id: icon
anchors.verticalCenter: parent.verticalCenter
Layout.minimumWidth: units.iconSizes.huge
Layout.minimumHeight: units.iconSizes.huge
source: "dialog-error"
}
PlasmaComponents.TextArea {
id: messageText
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: TextEdit.AlignVCenter
backgroundVisible: false
readOnly: true
width: parent.width - icon.width
wrapMode: Text.Wrap
}
}

View file

@ -0,0 +1,117 @@
/*
* Copyright 2013 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.kquickcontrolsaddons 2.0
PlasmaCore.ToolTipArea {
id: root
objectName: "org.kde.desktop-CompactApplet"
anchors.fill: parent
icon: plasmoid.icon
mainText: plasmoid.toolTipMainText
subText: plasmoid.toolTipSubText
location: plasmoid.location
active: !plasmoid.expanded
property Item fullRepresentation
property Item compactRepresentation
property Item expandedFeedback: expandedItem
onCompactRepresentationChanged: {
if (compactRepresentation) {
compactRepresentation.parent = root;
compactRepresentation.anchors.fill = root;
compactRepresentation.visible = true;
}
root.visible = true;
}
onFullRepresentationChanged: {
if (!fullRepresentation) {
return;
}
/* //if the fullRepresentation size was restored to a stored size, or if is dragged from the desktop, restore popup size
if (fullRepresentation.width > 0) {
appletParent.width = fullRepresentation.width;
} else if (fullRepresentation.Layout && fullRepresentation.Layout.preferredWidth > 0) {
appletParent.width = fullRepresentation.Layout.preferredWidth
} else if (fullRepresentation.implicitWidth > 0) {
appletParent.width = fullRepresentation.implicitWidth
} else {
appletParent.width = theme.mSize(theme.defaultFont).width * 35
}
if (fullRepresentation.height > 0) {
appletParent.height = fullRepresentation.height;
} else if (fullRepresentation.Layout && fullRepresentation.Layout.preferredHeight > 0) {
appletParent.height = fullRepresentation.Layout.preferredHeight
} else if (fullRepresentation.implicitHeight > 0) {
appletParent.height = fullRepresentation.implicitHeight
} else {
appletParent.height = theme.mSize(theme.defaultFont).height * 25
}*/
fullRepresentation.parent = appletParent;
fullRepresentation.anchors.fill = fullRepresentation.parent;
}
Rectangle {
id: expandedItem
anchors.fill: parent
color: PlasmaCore.ColorScope.highlightColor
opacity: plasmoid.expanded ? 0.3 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
Timer {
id: expandedSync
interval: 100
onTriggered: plasmoid.expanded = popupWindow.visible;
}
Item {
id: appletParent
opacity: plasmoid.expanded ? 1 : 0
anchors.top: parent.bottom
Layout.minimumWidth: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.minimumWidth : 0
Layout.minimumHeight: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.minimumHeight: 0
Layout.maximumWidth: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.maximumWidth : Infinity
Layout.maximumHeight: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.maximumHeight: Infinity
width: Math.max(parent.width, Layout.minimumWidth)
height: Layout.minimumHeight
Behavior on opacity {
OpacityAnimator {
duration: units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright 2013 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
Row {
id: main
spacing: units.largeSpacing
Layout.minimumWidth: {
switch (plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return 0;
case PlasmaCore.Types.Horizontal:
return height;
default:
return units.gridUnit * 3;
}
}
Layout.minimumHeight: {
switch (plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return width;
case PlasmaCore.Types.Horizontal:
return 0;
default:
return units.gridUnit * 3;
}
}
PlasmaCore.IconItem {
id: icon
source: plasmoid.icon ? plasmoid.icon : "plasma"
active: mouseArea.containsMouse
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
anchors.verticalCenter: parent.verticalCenter
}
PlasmaCore.SvgItem {
svg: PlasmaCore.Svg {
id: arrowSvg
imagePath: "widgets/arrows"
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
}
width: units.iconSizes.smallMedium
height: width
elementId: plasmoid.expanded ? "up-arrow" : "down-arrow"
anchors.verticalCenter: parent.verticalCenter
}
PlasmaComponents.Label {
text: plasmoid.title
anchors.verticalCenter: parent.verticalCenter
}
MouseArea {
id: mouseArea
property bool wasExpanded: false
anchors.fill: parent
hoverEnabled: true
onPressed: wasExpanded = plasmoid.expanded
onClicked: plasmoid.expanded = !wasExpanded
}
}

View file

@ -2,7 +2,7 @@
LookAndFeelPackage=org.kde.satellite.phone
[Desktop]
Containment=org.kde.desktopcontainment
Containment=org.kde.phone.homescreen
ToolBox=
[Desktop][ContainmentActions]

View file

@ -14,3 +14,7 @@ for (var j = 0; j < desktopsArray.length; j++) {
desktopsArray[j].writeConfig("Image", "org.kde.satellite.lockers");
}
desktopsForActivity(id)[0].addWidget("org.kde.phone.notifications");
var panel = new Panel("org.kde.phone.panel");
panel.addWidget("org.kde.phone.quicksettings");

View file

@ -41,6 +41,7 @@ Item {
property int notificationId: 0;
property int buttonHeight: width/4
<<<<<<< HEAD
/*
Notification data object has the following properties:
appIcon
@ -63,29 +64,19 @@ Item {
tmp.summary == data.summary &&
tmp.body == data.body);
var sameSource = tmp.source == source;
=======
onContainmentChanged: {
containment.parent = homescreen;
>>>>>>> mart/newContainmentShell
if (sameSource && matches) {
return;
}
if (sameSource || matches) {
notificationsModel.remove(i)
break;
}
if (containment != null) {
containment.visible = true;
}
data["id"] = ++notificationId;
data["source"] = source;
if (data["summary"].length < 1) {
data["summary"] = data["body"];
data["body"] = '';
}
data["actions"] = actions;
notificationsModel.insert(0, data);
if (!data["isPersistent"]) {
pendingRemovals.push(notificationId);
pendingTimer.start();
if (containment != null) {
containment.anchors.left = homescreen.left;
containment.anchors.top = homescreen.top;
containment.anchors.right = homescreen.right;
containment.anchors.bottom = homescreen.bottom;
}
}
@ -131,39 +122,6 @@ Item {
modemPath: ofonoManager.modems.length > 0 ? ofonoManager.modems[0] : ""
}
OfonoNetworkRegistration {
id: netreg
Component.onCompleted: {
netreg.scan()
updateStrengthIcon()
}
onNetworkOperatorsChanged : {
console.log("operators :"+netreg.currentOperator["Name"].toString())
}
modemPath: ofonoManager.modems.length ? ofonoManager.modems[0] : ""
function updateStrengthIcon() {
if (netreg.strength >= 100) {
strengthIcon.source = "network-mobile-100";
} else if (netreg.strength >= 80) {
strengthIcon.source = "network-mobile-80";
} else if (netreg.strength >= 60) {
strengthIcon.source = "network-mobile-60";
} else if (netreg.strength >= 40) {
strengthIcon.source = "network-mobile-40";
} else if (netreg.strength >= 20) {
strengthIcon.source = "network-mobile-20";
} else {
strengthIcon.source = "network-mobile-0";
}
}
onStrengthChanged: {
console.log("Strength changed to " + netreg.strength)
updateStrengthIcon()
}
}
OfonoNetworkOperator {
id: netop
}
@ -190,88 +148,11 @@ Item {
}
}
Timer {
id: pendingTimer
interval: 5000
repeat: false
onTriggered: {
for (var i = 0; i < pendingRemovals.length; ++i) {
var id = pendingRemovals[i];
for (var j = 0; j < notificationsModel.count; ++j) {
if (notificationsModel.get(j).id == id) {
notificationsModel.remove(j);
}
}
}
pendingRemovals = [];
}
}
Rectangle {
z: 1
color: Qt.rgba(0, 0, 0, 0.9 * (Math.min(applications.contentY + homescreen.height, homescreen.height) / homescreen.height))
anchors.fill: parent
}
PlasmaCore.DataSource {
id: timeSource
engine: "time"
connectedSources: ["Local"]
interval: 60 * 1000
}
PlasmaCore.DataSource {
id: notificationsSource
engine: "notifications"
interval: 0
onSourceAdded: {
connectSource(source);
}
onSourceRemoved: {
for (var i = 0; i < notificationsModel.count; ++i) {
if (notificationsModel.get(i) == source) {
notificationsModel.remove(i);
break;
}
}
}
onNewData: {
var actions = new Array()
if (data["actions"] && data["actions"].length % 2 == 0) {
for (var i = 0; i < data["actions"].length; i += 2) {
var action = new Object();
action["id"] = data["actions"][i];
action["text"] = data["actions"][i+1];
actions.push(action);
}
}
homescreen.addNotification(
sourceName,
data,
actions);
}
}
ListModel {
id: notificationsModel
ListElement {
appIcon: "call-start"
summary: "Missed call from Joe"
body: "Called at 8:42 from +41 56 373 37 31"
}
ListElement {
appIcon: "im-google"
summary: "July: Hey! Are you around?"
}
ListElement {
appIcon: "im-google"
summary: "July: Hello?"
//pass the focus to the containment, so it can react to homescreen activate/inactivate
Connections {
target: desktop
onActiveChanged: {
containment.focus = desktop.active;
}
}
@ -304,255 +185,6 @@ Item {
source: simManager.pinRequired != OfonoSimManager.NoPin ? Qt.resolvedUrl("Pin.qml") : ""
}
PlasmaCore.ColorScope {
id: statusPanel
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: units.iconSizes.small
z: 2
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.9)
PlasmaCore.IconItem {
id: strengthIcon
colorGroup: PlasmaCore.ColorScope.colorGroup
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
width: units.iconSizes.small
height: width
}
PlasmaComponents.Label {
anchors {
left: strengthIcon.right
verticalCenter: parent.verticalCenter
}
text: netreg.strength + "% " + netreg.name
color: PlasmaCore.ColorScope.textColor
font.pixelSize: parent.height / 2
}
PlasmaComponents.Label {
id: clock
anchors.fill: parent
text: Qt.formatTime(timeSource.data.Local.DateTime, "hh:mm")
color: PlasmaCore.ColorScope.textColor
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.pixelSize: height / 2
}
MouseArea {
property int oldMouseY: 0
anchors.fill: parent
enabled: !dialerOverlay.item.visible
onPressed: {
oldMouseY = mouse.y;
slidingPanel.visible = true;
}
onPositionChanged: {
slidingPanel.offset = slidingPanel.offset + (mouse.y - oldMouseY);
oldMouseY = mouse.y;
}
onReleased: slidingPanel.updateState();
}
PlasmaWorkspace.BatteryIcon {
id: batteryIcon
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
width: units.iconSizes.small
height: width
hasBattery: pmSource.data["Battery"]["Has Battery"]
batteryType: "Phone"
percent: pmSource.data["Battery0"] ? pmSource.data["Battery0"]["Percent"] : 0
PlasmaCore.DataSource {
id: pmSource
engine: "powermanagement"
connectedSources: sources
onSourceAdded: {
disconnectSource(source);
connectSource(source);
}
onSourceRemoved: {
disconnectSource(source);
}
}
}
}
}
SlidingPanel {
id: slidingPanel
width: homescreen.width
height: homescreen.height
}
PlasmaCore.ColorScope {
z: 1
anchors {
fill: parent
}
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
SatelliteComponents.ApplicationListModel {
id: appListModel
}
GridView {
id: applications
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.right
}
z: 1
cellWidth: homescreen.buttonHeight
cellHeight: cellWidth
model: appListModel
snapMode: GridView.SnapToRow
clip: true
header: MouseArea {
z: 999
width: homescreen.width
height: homescreen.height - units.iconSizes.medium
onPressAndHold: {
containment.action("configure").trigger();
}
PlasmaComponents.Label {
id: bigClock
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
bottom: notificationView.top
}
text: Qt.formatTime(timeSource.data.Local.DateTime, "hh:mm")
color: PlasmaCore.ColorScope.textColor
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.pointSize: 40
style: Text.Raised
styleColor: "black"
}
ListView {
id: notificationView
spacing: units.smallSpacing
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
bottomMargin: stripe.height * 2
}
height: parent.height / 3
interactive: false
z: 1
verticalLayoutDirection: ListView.BottomToTop
model: notificationsModel
add: Transition {
NumberAnimation {
properties: "x"
from: notificationView.width
duration: 100
}
}
remove: Transition {
NumberAnimation {
properties: "x"
to: notificationView.width
duration: 500
}
NumberAnimation {
properties: "opacity"
to: 0
duration: 500
}
}
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: 600 }
NumberAnimation { properties: "x,y"; duration: 100 }
}
}
delegate: NotificationStripe {}
}
SatelliteStripe {
id: stripe
z: 99
y: Math.max(applications.contentY + parent.height, parent.height - height)
PlasmaCore.Svg {
id: stripeIcons
imagePath: Qt.resolvedUrl("../images/homescreenicons.svg")
}
Row {
anchors.fill: parent
property int columns: 4
property alias buttonHeight: stripe.height
HomeLauncherSvg {
id: phoneIcon
svg: stripeIcons
elementId: "phone"
callback: function() {
dialerOverlay.open()
}
}
HomeLauncherSvg {
id: messagingIcon
svg: stripeIcons
elementId: "messaging"
callback: function() { console.log("Start messaging") }
}
HomeLauncherSvg {
id: emailIcon
svg: stripeIcons
elementId: "email"
callback: function() { console.log("Start email") }
}
HomeLauncherSvg {
id: webIcon
svg: stripeIcons
elementId: "web"
callback: function() { console.log("Start web") }
}
}
}
}
delegate: HomeLauncher {}
Component.onCompleted : { console.log("WTF " + width) }
}
}
Component.onCompleted: {
//configure the view behavior
if (desktop) {

View file

@ -0,0 +1,41 @@
/*
* Copyright 2012 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.FrameSvgItem {
id: root
visible: false //adjust borders is run during setup. We want to avoid painting till completed
property Item containment
onContainmentChanged: {
containment.parent = root;
containment.visible = true;
containment.anchors.fill = root;
}
Component.onCompleted: {
visible = true
}
}