Commit ac55986b authored by Nicolas Fella's avatar Nicolas Fella

Initial Kirigami time picker

parent eca69b1b
......@@ -12,7 +12,7 @@ include(FeatureSummary)
include(KDEInstallDirs)
include(KDECMakeSettings)
find_package(Qt5 REQUIRED COMPONENTS Gui Qml)
find_package(Qt5 REQUIRED COMPONENTS Gui Qml Quick)
find_package(KF5 REQUIRED COMPONENTS I18n)
if (ANDROID)
......
add_subdirectory(dateandtime)
set(ktrip_SRCS
main.cpp
querycontroller.cpp
......
set(dateandtime_SRCS
lib/timezonemodel.cpp
lib/timezonesi18n.cpp
lib/knumbermodel.cpp
lib/plugin.cpp)
add_definitions(-DTRANSLATION_DOMAIN=\"kirigami_dateandtime\")
add_library(dateandtimeplugin SHARED ${dateandtime_SRCS})
target_link_libraries(dateandtimeplugin
Qt5::Quick
Qt5::Qml
KF5::I18n
)
install(TARGETS dateandtimeplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigamiaddons/dateandtime)
install(FILES
qmldir
ClockElement.qml
ClockFace.qml
DateInput.qml
Hand.qml
TimezoneTable.qml
TimePicker.qml
DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigamiaddons/dateandtime)
/*
* Copyright 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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.12
import QtQuick.Controls 2.5 as Controls2
import org.kde.kirigami 2.0 as Kirigami
import QtQuick.Layouts 1.11
Controls2.ToolButton {
id: hoursButton
property int selectedValue
property string type
checkable: true
checked: index == selectedValue
autoExclusive: true
text: index == selectedValue ? ( (type == "hours" && index == 0) ? 12 : index )
: ( (type == "hours") ? ( index == 0 ? 12 : ( (index % 3 == 0) ? index : ".") ) : (index % 15 == 0) ? index : ".")
contentItem: Controls2.Label {
text: hoursButton.text
color: index <= parent.selectedValue ? Kirigami.Theme.activeTextColor : Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
implicitHeight: Kirigami.Units.gridUnit
implicitWidth: height
radius: width*0.5
color: parent.checked ? Kirigami.Theme.buttonBackgroundColor : "transparent"
}
}
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: clock
property date time
property bool showSecondsHand: false
property int _hours
property int _minutes
property int _seconds
onTimeChanged: {
_hours = time.getHours();
_minutes = time.getMinutes();
_seconds = time.getSeconds();
}
PlasmaCore.Svg {
id: clockSvg
imagePath: "widgets/clock"
}
PlasmaCore.SvgItem {
id: face
anchors.centerIn: parent
width: Math.min(parent.width, parent.height)
height: Math.min(parent.width, parent.height)
svg: clockSvg
elementId: "ClockFace"
}
Hand {
anchors.topMargin: 3
elementId: "HourHandShdow"
rotation: 180 + _hours * 30 + (_minutes/2)
svgScale: face.width / face.naturalSize.width
}
Hand {
elementId: "HourHand"
rotation: 180 + _hours * 30 + (_minutes/2)
svgScale: face.width / face.naturalSize.width
}
Hand {
anchors.topMargin: 3
elementId: "MinuteHandShadow"
rotation: 180 + _minutes * 6
svgScale: face.width / face.naturalSize.width
}
Hand {
elementId: "MinuteHand"
rotation: 180 + _minutes * 6
svgScale: face.width / face.naturalSize.width
}
Hand {
anchors.topMargin: 3
elementId: "SecondHandShadow"
rotation: 180 + _seconds * 6
visible: showSecondsHand
svgScale: face.width / face.naturalSize.width
}
Hand {
elementId: "SecondHand"
rotation: 180 + _seconds * 6
visible: showSecondsHand
svgScale: face.width / face.naturalSize.width
}
PlasmaCore.SvgItem {
id: center
width: naturalSize.width * face.width / face.naturalSize.width
height: naturalSize.height * face.width / face.naturalSize.width
anchors.centerIn: clock
svg: clockSvg
elementId: "HandCenterScrew"
z: 1000
}
PlasmaCore.SvgItem {
anchors.fill: face
svg: clockSvg
elementId: "Glass"
width: naturalSize.width * face.width / face.naturalSize.width
height: naturalSize.height * face.width / face.naturalSize.width
}
}
import QtQuick 2.3
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.3
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kirigamiaddons.dateandtime 0.1
/**
* This Item provides an entry box for inputting a date.
* It is represented in a form suitable for entering a known date (i.e date of birth, current date)
* rather than date selection where a user might want a grid view.
*/
RowLayout {
id: layout
//DAVE - if I'm in an RTL country are my date formats pre-reversed?
//i.e Wikipedia says afghan is d/m/yyyy should year go on the left or the right?
property date value
property string dateFormat: Qt.locale().dateFormat(Locale.ShortFormat)
//date formats can be in big endian (china), little endian (Europe), or absolutely mental endian
//separators are also different
Component.onCompleted: {
value = new Date();
//dave, some idiot could have added children externally, maybe would be safer to make RowLayout internal?
for (var i in layout.children) {
layout.children[i].destroy();
}
var tabletMode = Kirigami.Settings.tabletMode
var parse = /([^dMy]*)([dMy]+)([^dMy]*)([dMy]+)([^dMy]*)([dMy]+)([^dMy]*)/
var parts = parse.exec(dateFormat);
for(var i=1; i < parts.length; i++) {
var part = parts[i];
if (!part) {
continue;
}
if (part.startsWith("d")) {
if (tabletMode) {
daySelectTouchComponent.createObject(layout);
} else {
daySelectComponent.createObject(layout);
}
}
else if (part.startsWith("M")) {
if (tabletMode) {
monthSelectTouchComponent.createObject(layout);
} else {
monthSelectComponent.createObject(layout);
} }
else if (part.startsWith("y")) {
if (tabletMode) {
yearSelectTouchComponent.createObject(layout);
} else {
yearSelectComponent.createObject(layout);
} }
else {
labelComponent.createObject(layout, {"text": part})
}
}
}
Component {
id: tumberDelegate
Label {
text: model.display
color: Tumbler.tumbler.visualFocus ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Component {
id: daySelectComponent
SpinBox {
from: 1
to: 31
editable: true
value: layout.value.getDate();
onValueModified: {
var dt = layout.value;
dt.setDate(value);
layout.value = dt;
}
}
}
Component {
id: daySelectTouchComponent
Tumbler {
delegate: tumberDelegate
model: NumberModel {
minimumValue: 1
maximumValue: 31
}
}
//Tumbler doesn't have a separate user modified signal...booooooo!!!!!
}
Component {
id: monthSelectComponent
SpinBox {
from: 1
to: 12
editable: true
value: layout.value.getMonth() + 1;
onValueModified: {
var dt = layout.value;
dt.setMonth(value - 1);
layout.value = dt;
}
}
}
Component {
id: monthSelectTouchComponent
Tumbler {
delegate: tumberDelegate
model: NumberModel {
minimumValue: 1
maximumValue: 12
}
}
}
Component {
id: yearSelectComponent
SpinBox {
from: 1970
to: 2100 //I assume we'll have a new LTS release by then
editable: true
textFromValue: function(value) {return value} //default implementation does toLocaleString which looks super weird
value: layout.value.getFullYear();
onValueModified: {
var dt = layout.value;
dt.setFullYear(value);
layout.value = dt;
}
}
}
Component {
id: yearSelectTouchComponent
Tumbler {
delegate: tumberDelegate
model: NumberModel {
minimumValue: 1970
maximumValue: 2100
}
}
}
Component {
id: labelComponent
Label {
}
}
}
/*
* Copyright 2012 Viranch Mehta <viranch.mehta@gmail.com>
* Copyright 2012 Marco Martin <mart@kde.org>
* Copyright 2013 David Edmundson <davidedmundson@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 org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.SvgItem {
id: secondHand
property alias rotation: rotation.angle
property double svgScale
width: Math.round(naturalSize.width * svgScale) + Math.round(naturalSize.width * svgScale) % 2
height: Math.round(naturalSize.height * svgScale) + width % 2
anchors {
top: clock.verticalCenter
topMargin: -width/2
horizontalCenter: clock.horizontalCenter
}
svg: clockSvg
smooth: !anim.running
transform: Rotation {
id: rotation
angle: 0
origin {
x: secondHand.width/2
y: secondHand.width/2
}
Behavior on angle {
RotationAnimation {
id: anim
duration: 200
direction: RotationAnimation.Clockwise
easing.type: Easing.OutElastic
easing.overshoot: 0.5
}
}
}
}
/*
* Copyright 2019 Dimitris Kardarakos <dimkard@posteo.net>
*
* 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.12
import QtQuick.Controls 2.5 as Controls2
import org.kde.kirigami 2.0 as Kirigami
import QtQuick.Layouts 1.11
ColumnLayout {
id: root
property int hours
property int minutes
property bool pm
Item {
id: clock
width: Kirigami.Units.gridUnit * 18
height: width
//Hours clock
PathView {
id: hoursClock
delegate: ClockElement {
type: "hours"
selectedValue: root.hours
onClicked: root.hours = index
}
model: 12
path: Path {
PathAngleArc {
centerX: Kirigami.Units.gridUnit * 9
centerY: centerX
radiusX: Kirigami.Units.gridUnit * 4
radiusY: radiusX
startAngle: -90
sweepAngle: 360
}
}
}
//Minutes clock
PathView {
id: minutesClock
model: 60
delegate: ClockElement {
type: "minutes"
selectedValue: root.minutes
onClicked: root.minutes = index
}
path: Path {
PathAngleArc {
centerX: Kirigami.Units.gridUnit * 9
centerY: centerX
radiusX: Kirigami.Units.gridUnit * 7
radiusY: radiusX
startAngle: -90
sweepAngle: 360
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Controls2.Label {
text: ((root.hours < 10) ? "0" : "" ) + root.hours + ":" + ( (root.minutes < 10) ? "0" : "") + root.minutes
font.pointSize: Kirigami.Units.fontMetrics.font.pointSize * 1.5
}
Controls2.ToolButton {
id: pm
checked: root.pm
checkable: true
text: checked ? "PM" : "AM"
font.pointSize: Kirigami.Units.fontMetrics.font.pointSize * 1.5
onClicked: root.pm = checked
}
}
}
import QtQuick 2.0
import QtQuick.Controls 1.2 as QtControls
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.1
import org.kde.kirigamiaddons.dateandtime 0.1
ColumnLayout {
QtControls.TextField {
id: filter
Layout.fillWidth: true
placeholderText: i18n("Search Time Zones")
}
QtControls.TableView {
id: timeZoneView
signal toggleCurrent
Layout.fillWidth: true
Layout.fillHeight: true
Keys.onSpacePressed: toggleCurrent()
TimeZoneModel {
id: timeZones
onSelectedTimeZonesChanged: {
if (selectedTimeZones.length == 0) {
messageWidget.visible = true;
timeZones.selectLocalTimeZone();
}
}
}
model: TimeZoneFilterModel {
sourceModel: timeZones
filterString: filter.text
}
QtControls.TableViewColumn {
role: "city"
title: i18n("City")
}
QtControls.TableViewColumn {
role: "region"
title: i18n("Region")
}
QtControls.TableViewColumn {
role: "comment"
title: i18n("Comment")
}
}
}
/*
* Copyright (C) 2018 David Edmundson <davidedmundson@kde.org>
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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 library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "knumbermodel.h"
#include <QLocale>
#include <QtMath>
#include <cmath>
class KNumberModelPrivate
{
public:
qreal minimumValue = 0.0;
qreal maximumValue = 0.0;
qreal stepSize = 1.0;
QLocale::NumberOptions formattingOptions = QLocale::DefaultNumberOptions;
};
KNumberModel::KNumberModel(QObject *parent):
QAbstractListModel(parent),
d(new KNumberModelPrivate)
{}
KNumberModel::~KNumberModel()
{}
void KNumberModel::setMinimumValue(qreal minimumValue)
{
if (minimumValue == d->minimumValue) {
return;
}
beginResetModel();
d->minimumValue = minimumValue;
endResetModel();
emit minimumValueChanged();
}
qreal KNumberModel::minimumValue() const
{
return d->minimumValue;
}
void KNumberModel::setMaximumValue(qreal maximumValue)
{
if (maximumValue == d->maximumValue) {
return;
}
beginResetModel();
d->maximumValue = maximumValue;
endResetModel();
emit maximumValueChanged();
}
qreal KNumberModel::maximumValue() const
{
return d->maximumValue;
}
void KNumberModel::setStepSize(qreal stepSize)
{
Q_ASSERT(stepSize != 0);
if (stepSize == d->stepSize) {
return;
}
beginResetModel();
d->stepSize = stepSize;
endResetModel();
emit stepSizeChanged();
}
qreal KNumberModel::stepSize() const
{
return d->stepSize;
}
void KNumberModel::setFormattingOptions(QLocale::NumberOptions formattingOptions)
{
if (d->formattingOptions == formattingOptions) {
return;
}
d->formattingOptions = formattingOptions;
if (rowCount() == 0) {
return;
}
dataChanged(index(0, 0, QModelIndex()), index(rowCount(), 0, QModelIndex()), QVector<int>{DisplayRole});
emit formattingOptionsChanged();
}
QLocale::NumberOptions KNumberModel::formattingOptions() const
{
return d->formattingOptions;
}
qreal KNumberModel::value(const QModelIndex &index) const
{
if (!index.isValid()) {
return 0.0;
}
return d->minimumValue + d->stepSize * index.row();
}
int KNumberModel::rowCount(const QModelIndex &index) const
{
if (index.parent().isValid()) {
return 0;
}
//1 initial entry (the minimumValue) + the number of valid steps afterwards
return 1 + std::max(0, qFloor((d->maximumValue - d->minimumValue) / d->stepSize));
}
QVariant KNumberModel::data(const QModelIndex &index, int role) const
{
switch(role) {
case KNumberModel::DisplayRole: {
auto locale = QLocale::system();
locale.setNumberOptions(d->formattingOptions);
return QVariant(locale.toString(value(index)));
}
case KNumberModel::ValueRole:
return QVariant(value(index));
}
return QVariant();
}
QHash<int, QByteArray> KNumberModel::roleNames() const
{
return {{KNumberModel::DisplayRole, QByteArrayLiteral("display")},
{KNumberModel::ValueRole, QByteArrayLiteral("value")}};
}
/*
* Copyright (C) 2018 David Edmundson <davidedmundson@kde.org>
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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