diff --git a/extras/scripts/checkqmlimports.py b/extras/scripts/checkqmlimports.py new file mode 100644 index 0000000..e0a46cb --- /dev/null +++ b/extras/scripts/checkqmlimports.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Sebastian Gottfried +# +# 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, see . +# + +import argparse +import re +from pathlib import Path +from collections import namedtuple + +Import = namedtuple('Import', ['name', 'version', 'alias']) + +ALLOWED_IMPORTS = { + 'common': [ + Import('QtQuick.Controls', '2.2', None), + Import('QtQuick.Controls', '2.2', 'Controls'), + Import('org.kde.kquickcontrolsaddons', '2.0', 'Addons'), + ], + '*': [ + Import('QtQuick', '2.9', alias=None), + Import('QtQuick.Layouts', '1.3', alias=None), + Import('ktouch', '1.0', alias=None), + Import('QtGraphicalEffects', '1.0', alias=None), + Import('org.kde.charts', '0.1', alias='Charts'), + ], +} + +IMPORT_RE = re.compile(r'^import ([\w\.]+) ([\d\.]+)( as ([\w\.]+))?$') +LOCAL_IMPORT = re.compile('^import ("|\')[\w\./]+("|\')') + +def dir_arg(path_str): + path = Path(path_str) + if not path.exists(): + msg = "'{}' doesn't exist".format(path) + raise argparse.ArgumentTypeError(msg) + if not path.is_dir(): + msg = "'{}' has to be a directory".format(path) + raise argparse.ArgumentTypeError(msg) + return path + +def check_imports(file, module, local_path): + for line in file: + line = line.strip() + if not line.startswith('import'): + continue + if LOCAL_IMPORT.match(line) is not None: + continue + try: + check_package_import(line, module, local_path) + except ValueError: + print(f"{local_path}: unrecognized import statement: '{line}'") + + +def check_package_import(line, module, local_path): + matches = IMPORT_RE.match(line) + if matches is None: + raise ValueError('unrecgonized import') + current_import = Import(*matches.group(1, 2, 4)) + for allowed_module, allowed_imports in ALLOWED_IMPORTS.items(): + if allowed_module == '*' or allowed_module == module: + for allowed_import in allowed_imports: + if current_import == allowed_import: + return + print(f"{local_path}: import not allowed here: '{line}'") + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='check QML imports for KTouch' + ) + parser.add_argument('path', + type=dir_arg, + metavar="PATH", + default="" + ) + args = parser.parse_args() + for qml_file_path in args.path.glob('**/*.qml'): + local_path = qml_file_path.relative_to(args.path) + if len(local_path.parts) > 1: + module = local_path.parts[0] + else: + module = None + check_imports(qml_file_path.open(mode='r'), module, local_path) + diff --git a/src/qml/common/Balloon.qml b/src/qml/common/Balloon.qml index e914440..9a5aae0 100644 --- a/src/qml/common/Balloon.qml +++ b/src/qml/common/Balloon.qml @@ -1,166 +1,166 @@ /* * Copyright 2012 Marco Martin * Copyright 2015 Sebastian Gottfried * * 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 2.9 import QtGraphicalEffects 1.0 Loader { id: root property Item visualParent property string status: 'closed' default property Item data active: status != 'closed' function open() { root.status = 'loading' } function close() { root.status = 'closing' } sourceComponent: Component { MouseArea { id: dismissArea anchors.fill: parent opacity: root.active && (root.status == 'open' || root.status =='opening')? 1 : 0 layer.enabled: true layer.effect: DropShadow { anchors.fill: parent radius: 5 samples: 11 } Behavior on opacity { SequentialAnimation { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad properties: "opacity" } ScriptAction { script: { root.status = root.status == 'opening' ? 'open' : 'closed' } } } } SystemPalette { id: palette colorGroup: SystemPalette.Active } Rectangle { id: internal color: palette.alternateBase radius: 5 property variant parentPos: root.visualParent? root.visualParent.mapToItem(dismissArea, 0, 0): Qt.point(0, 0) property bool under: root.visualParent ? internal.parentPos.y + root.visualParent.height + height < dismissArea.height : true //bindings won't work inside anchors definition onUnderChanged: { if (under) { balloonTip.anchors.top = undefined balloonTip.anchors.bottom = balloonTip.parent.top } else { balloonTip.anchors.bottom = undefined balloonTip.anchors.top = balloonTip.parent.bottom } } property int preferedX: internal.parentPos.x - internal.width/2 + root.visualParent.width/2 x: Math.round(Math.max(radius, Math.min(dismissArea.width - internal.width - radius, preferedX))) y: { if (root.visualParent) { if (under) { Math.round(internal.parentPos.y + root.visualParent.height + balloonTip.height + radius) } else { Math.round(internal.parentPos.y - internal.height - balloonTip.height - radius) } } else { Math.round(dismissArea.height/2 - internal.height/2) } } width: contentItem.width + 2 * internal.radius height: contentItem.height + 2 * internal.radius Rectangle { id: balloonTip color: internal.color anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: internal.preferedX - internal.x top: parent.bottom } width: 10 height: 10 visible: false } Image { id: balloonTipMask anchors.fill: balloonTip visible: false source: utils.findImage("balloontip.svgz") sourceSize: Qt.size(width, height) } OpacityMask { anchors.fill: balloonTip visible: root.visualParent != null source: balloonTip maskSource: balloonTipMask rotation: internal.under? 0: 180 } Item { id: contentItem x: internal.radius y: internal.radius width: childrenRect.width height: childrenRect.height + 2 data: root.data } } onClicked: { root.close() } Component.onCompleted: { var candidate = root while (candidate.parent.parent) { candidate = candidate.parent } if (candidate) { dismissArea.parent = candidate } root.status = 'opening' } } } } diff --git a/src/qml/common/Collapsable.qml b/src/qml/common/Collapsable.qml index 8fca355..9849795 100644 --- a/src/qml/common/Collapsable.qml +++ b/src/qml/common/Collapsable.qml @@ -1,72 +1,72 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 as Controls import ktouch 1.0 Controls.Control { id: root property bool collapsed: false default property Item data signal contentReadyForSwap function swapContent() { contentItem.isSwapping = true } property KColorScheme colorScheme: KColorScheme { colorGroup: KColorScheme.Active colorSet: KColorScheme.Window } height: collapsed && contentItem.opacity == 0? 0: contentItem.implicitHeight visible: height > 0 Behavior on height { NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } background: Rectangle { color: colorScheme.normalBackground } contentItem: Item { property bool isSwapping: false opacity: !root.collapsed && !isSwapping && root.height === implicitHeight? 1: 0 data: root.data implicitHeight: children.length > 0? children[0].implicitHeight: 0 Behavior on opacity { NumberAnimation { duration: 150 } } onOpacityChanged: { if (isSwapping && opacity === 0) { contentReadyForSwap() isSwapping = false; } } } } diff --git a/src/qml/common/IconButton.qml b/src/qml/common/IconButton.qml index 296cc10..b94755e 100644 --- a/src/qml/common/IconButton.qml +++ b/src/qml/common/IconButton.qml @@ -1,88 +1,88 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 import ktouch 1.0 import QtGraphicalEffects 1.0 Button { id: button property alias color: content.color property alias bgColor: bg.color property alias icon: content.icon property alias colorScheme: buttonColorScheme padding: 0 hoverEnabled: true KColorScheme { id: buttonColorScheme colorGroup: button.enabled? KColorScheme.Active: KColorScheme.Disabled colorSet: KColorScheme.Button } contentItem: IconLabel { color: buttonColorScheme.normalText id: content text: button.text elide: "ElideNone" Behavior on color { ColorAnimation { duration: 150 } } } background: Item { Rectangle { anchors.fill: parent; id: bg color: buttonColorScheme.alternateBackground HueSaturation { anchors.fill: bg source: bg saturation: hovered? 0.3: 0 lightness: hovered? -0.04: 0 Behavior on saturation { NumberAnimation { duration: 150 } } Behavior on lightness { NumberAnimation { duration: 150 } } } } FocusBar { anchors { left: parent.left right: parent.right bottom: parent.bottom } height: 3 control: button } } } diff --git a/src/qml/common/IconLabel.qml b/src/qml/common/IconLabel.qml index 76c0ac9..d9e25c1 100644 --- a/src/qml/common/IconLabel.qml +++ b/src/qml/common/IconLabel.qml @@ -1,44 +1,44 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 Label { property string icon: "" property bool reserveSpaceForIcon: false id: label elide: Text.ElideRight padding: Math.ceil(0.7 * font.pixelSize) leftPadding: (iconItem.visible || reserveSpaceForIcon? padding + iconItem.width: 0) + (label.text !== ""? padding: 0) verticalAlignment: Text.AlignVCenter MonochromeIcon { id: iconItem visible: label.icon != "" color: label.color anchors { left: parent.left leftMargin: label.text === ""? (label.width - width) / 2: label.padding verticalCenter: parent.verticalCenter } icon: label.icon width: 22 height: 22 } } diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/InfoItem.qml index d61c4e5..9b143fb 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/InfoItem.qml @@ -1,24 +1,24 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 QtObject { property string title property string text } diff --git a/src/qml/common/InformationTable.qml b/src/qml/common/InformationTable.qml index e321e0c..9818b32 100644 --- a/src/qml/common/InformationTable.qml +++ b/src/qml/common/InformationTable.qml @@ -1,63 +1,63 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 Item { property alias model: repeator.model width: parent.width height: childrenRect.height Column { id: column spacing: 3 width: parent.width height: childrenRect.height Repeater { id: repeator Row { spacing: 5 height: Math.max(titleLabel.height, valueLabel.height) width: column.width Label { id: titleLabel width: Math.round((parent.width - parent.spacing) / 2) horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignTop color: "#888" text: model.modelData.title wrapMode: Text.Wrap height: Math.max(paintedHeight, valueLabel.paintedHeight) } Label { id: valueLabel width: parent.width - titleLabel.width - parent.spacing horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignBottom text: model.modelData.text wrapMode: Text.Wrap height: Math.max(paintedHeight, titleLabel.paintedHeight) } } } } } diff --git a/src/qml/common/InlineToolbar.qml b/src/qml/common/InlineToolbar.qml index 4202560..46e3447 100644 --- a/src/qml/common/InlineToolbar.qml +++ b/src/qml/common/InlineToolbar.qml @@ -1,55 +1,55 @@ /* * Copyright 2013 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 Item { id: root property alias content: contentArea.data property alias color: background.color width: contentArea.width + 6 height: contentArea.height + 6 Rectangle { anchors.fill: parent id: background color: activePalette.mid opacity: 0.7 radius: 3 SystemPalette { id: activePalette colorGroup: SystemPalette.Active } } Behavior on opacity { NumberAnimation { duration: 250 } } Row { id: contentArea anchors.centerIn: parent height: childrenRect.height spacing: 3 } } diff --git a/src/qml/common/LearningProgressChart.qml b/src/qml/common/LearningProgressChart.qml index 99a03c0..ef4e22e 100644 --- a/src/qml/common/LearningProgressChart.qml +++ b/src/qml/common/LearningProgressChart.qml @@ -1,63 +1,62 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import org.kde.charts 0.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import org.kde.charts 0.1 as Charts import ktouch 1.0 -LineChart { +Charts.LineChart { id: chart - property Dimension accuracy: accuracyDimension - property Dimension charactersPerMinute: charactersPerMinuteDimension + property Charts.Dimension accuracy: accuracyDimension + property Charts.Dimension charactersPerMinute: charactersPerMinuteDimension pitch: 60 function minAccuracy(accuracy) { var canditades = [0.9, 0.8, 0.5] for (var i = 0; i < canditades.length; i++) { if (canditades[i] < accuracy) { return (canditades[i]) } } return 0; } dimensions: [ - Dimension { + Charts.Dimension { id: accuracyDimension dataColumn: 5 color: "#ffb12d" minimumValue: chart.minAccuracy(model.minAccuracy) maximumValue: 1.0 label: i18n("Accuracy") unit: "%" unitFactor: 100 }, - Dimension { + Charts.Dimension { id: charactersPerMinuteDimension dataColumn: 6 color: "#38aef4" maximumValue: Math.max(Math.ceil(model.maxCharactersTypedPerMinute / 120) * 120, 120) label: i18n("Characters per Minute") } ] } diff --git a/src/qml/common/ListItem.qml b/src/qml/common/ListItem.qml index 8a31a0c..8949cd2 100644 --- a/src/qml/common/ListItem.qml +++ b/src/qml/common/ListItem.qml @@ -1,63 +1,63 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Controls 2.0 +import QtQuick 2.9 +import QtQuick.Controls 2.2 import ktouch 1.0 ItemDelegate { id: root property string icon property alias label: label property alias bg: bg property alias reserveSpaceForIcon: label.reserveSpaceForIcon hoverEnabled: true padding: 0 KColorScheme { id: listItemColorSchemeNormal colorGroup: KColorScheme.Active colorSet: KColorScheme.View } KColorScheme { id: listItemColorSchemeHighlighted colorGroup: KColorScheme.Active colorSet: KColorScheme.Selection } background: Rectangle { id: bg anchors.fill: parent color: listItemColorSchemeHighlighted.normalBackground opacity: root.highlighted ? 1 : (root.hovered? 0.3: 0) Behavior on opacity { NumberAnimation { duration: 150 } } } contentItem: IconLabel { id: label text: root.text icon: root.icon color: root.highlighted? listItemColorSchemeHighlighted.activeText: listItemColorSchemeNormal.normalText; } } diff --git a/src/qml/common/MessageBox.qml b/src/qml/common/MessageBox.qml index b3021aa..9a2b9f7 100644 --- a/src/qml/common/MessageBox.qml +++ b/src/qml/common/MessageBox.qml @@ -1,124 +1,124 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 import org.kde.kquickcontrolsaddons 2.0 Rectangle { id: item anchors.verticalCenter: parent.verticalCenter radius: 3 color: "#eee4be" height: layout.height + 6 width: layout.width + 6 smooth: true function showMessage(msg, iconSource) { item.state = "hidden"; label.text = msg icon.icon = iconSource || "" item.state = "normal" } function clearMessage() { item.state = "cleared" } function clearMessageImmediately() { item.state = "hidden" } border { width: 1 color: "#decd87" } transform: Scale { id: itemScaler origin.x: 0 origin.y: 0 } state: "hidden" Row { id: layout anchors.centerIn: parent width: icon.width + spacing + label.width spacing: icon.valid? 3: 0 QIconItem { id: icon width: height height: icon? label.height: 0 } Label { id: label color: "#000000" } } states: [ State { name: "normal" PropertyChanges { target: itemScaler xScale: 1 } }, State { name: "hidden" PropertyChanges { target: itemScaler xScale: 0 } }, State { name: "cleared" PropertyChanges { target: itemScaler xScale: 0 } } ] transitions: [ Transition { from: "*" to: "normal" NumberAnimation { target: itemScaler property: "xScale" duration: 500 easing.type: Easing.InOutQuad } }, Transition { from: "normal" to: "cleared" NumberAnimation { target: itemScaler property: "xScale" duration: 500 easing.type: Easing.InOutQuad } } ] } diff --git a/src/qml/common/MonochromeIcon.qml b/src/qml/common/MonochromeIcon.qml index 1649e0b..c3b4c93 100644 --- a/src/qml/common/MonochromeIcon.qml +++ b/src/qml/common/MonochromeIcon.qml @@ -1,32 +1,32 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtGraphicalEffects 1.0 -import org.kde.kquickcontrolsaddons 2.0 +import org.kde.kquickcontrolsaddons 2.0 as Addons -QIconItem { +Addons.QIconItem { property color color: "#000000" id: icon width: 24 height: 24 ColorOverlay { anchors.fill: parent source: icon color: parent.color } } diff --git a/src/qml/common/PopupDialog.qml b/src/qml/common/PopupDialog.qml index 0488b6e..787af98 100644 --- a/src/qml/common/PopupDialog.qml +++ b/src/qml/common/PopupDialog.qml @@ -1,140 +1,140 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import ktouch 1.0 import QtGraphicalEffects 1.0 Dialog { id: root dim: true opacity: 0 scale: 0.9 leftMargin: Math.floor((parent.width - width) / 2) topMargin: Math.floor((parent.height - height) / 2) Component.onCompleted: { var candidate = root while (candidate.parent) { candidate = candidate.parent } if (candidate) { root.parent = candidate } } Item { id: dimOverlay parent: root.parent width: root.parent.width height: root.parent.height visible: root.visible && root.dim opacity: 0 ShaderEffectSource { id: effectSource sourceItem: root.parent.appContent anchors.fill: parent hideSource: false } HueSaturation { id: desaturatedBackground source: effectSource anchors.fill: parent lightness: -0.3 saturation: -0.5 visible: false } FastBlur { anchors.fill: parent source: desaturatedBackground radius: 50 } Rectangle { anchors.fill: parent color: "#55000000" } } enter: Transition { // grow_fade_in NumberAnimation { property: "scale"; to: 1.0; easing.type: Easing.OutQuint; duration: 220 } NumberAnimation { property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: 150 } NumberAnimation { target: dimOverlay; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: 220 } } exit: Transition { // shrink_fade_out NumberAnimation { property: "scale"; to: 0.9; easing.type: Easing.OutQuint; duration: 220 } NumberAnimation { property: "opacity"; to: 0.0; easing.type: Easing.OutCubic; duration: 150 } NumberAnimation { target: dimOverlay; property: "opacity"; to: 0.0; easing.type: Easing.OutCubic; duration: 220 } } background: Rectangle { color: dialogColorScheme.normalBackground KColorScheme { id: dialogColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Window } } header: Rectangle { implicitHeight: Math.max(titleLabel.implicitHeight, closeButton.implicitHeight) color: toolbarColorScheme.toolbarBackground KColorScheme { id: toolbarColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Complementary property color toolbarBackground: Qt.darker(toolbarColorScheme.shade(toolbarColorScheme.hoverDecoration, KColorScheme.MidShade, toolbarColorScheme.contrast, -0.2), 1.3) } RowLayout { anchors.fill: parent Label { id: titleLabel text: root.title width: parent.width color: toolbarColorScheme.normalText font.bold: true padding: font.pixelSize Layout.fillWidth: true } IconToolButton { id: closeButton icon: "window-close-symbolic" color: toolbarColorScheme.normalText visible: root.closePolicy & Popup.CloseOnEscape backgroundColor: toolbarColorScheme.normalBackground Layout.preferredWidth: titleLabel.implicitHeight Layout.preferredHeight: titleLabel.implicitHeight onClicked: root.close() } } } } diff --git a/src/qml/common/SelectionGrip.qml b/src/qml/common/SelectionGrip.qml index 1ac3808..68147ab 100644 --- a/src/qml/common/SelectionGrip.qml +++ b/src/qml/common/SelectionGrip.qml @@ -1,119 +1,119 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: root property KeyboardLayout keyboardLayout property variant target: null property variant targetItem: null property string horizontalPosition property string verticalPosition property bool interactive property bool xLocked: false property bool yLocked: false property int minSize: 20 x: targetItem? targetItem.x + (horizontalPosition == "left"? 0: targetItem.width - 1): 0 y: targetItem? targetItem.y + (verticalPosition == "top"? 0: targetItem.height - 1): 0 onXChanged: { if (!xLocked && mouseArea.drag.active) { xLocked = true var effX = Math.round(x / scaleFactor) if (horizontalPosition == "left") { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, effX, target.top, target.width + target.left - effX, target.height) } else { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, target.top, effX - target.left, target.height) } xLocked = false } } onYChanged: { if (!yLocked && mouseArea.drag.active) { yLocked = true var effY = Math.round(y / scaleFactor) if (verticalPosition == "top") { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, effY, target.width, target.height + target.top - effY) } else { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, target.top, target.width, effY - target.top) } yLocked = false } } width: 1 height: 1 SystemPalette { id: palette colorGroup: SystemPalette.Active } Rectangle { anchors { centerIn: parent verticalCenterOffset: verticalPosition == "top"? -10: 10 horizontalCenterOffset: horizontalPosition == "left"? -10: 10 } width: 15 height: 15 color: palette.highlight visible: interactive MouseArea { id: mouseArea anchors.fill: parent cursorShape: ((horizontalPosition == "left") != (verticalPosition == "top"))? Qt.SizeBDiagCursor: Qt.SizeFDiagCursor onClicked: { mouse.accepted = true } drag { target: root axis: Drag.XandYAxis minimumX: !targetItem? 0: horizontalPosition == "left"? 0: targetItem.x + scaleFactor * minSize maximumX: !targetItem? 0: horizontalPosition == "left"? targetItem.x + targetItem.width - minSize: keyboardLayout.width * scaleFactor minimumY: !targetItem? 0: verticalPosition == "top"? 0: targetItem.y + scaleFactor * minSize maximumY: !targetItem? 0: verticalPosition == "top"? targetItem.y + targetItem.height - minSize: keyboardLayout.height * scaleFactor onActiveChanged: { targetItem.manipulated = drag.active if (!drag.active) { var left = 10 * Math.round(target.left / 10) var top = 10 * Math.round(target.top / 10) var width = 10 * Math.round(target.width / 10) var height = 10 * Math.round(target.height / 10) keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, left, top, width, height) } } } } } } diff --git a/src/qml/common/SelectionRectangle.qml b/src/qml/common/SelectionRectangle.qml index 941289e..67fa21e 100644 --- a/src/qml/common/SelectionRectangle.qml +++ b/src/qml/common/SelectionRectangle.qml @@ -1,55 +1,55 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: root; property KeyboardLayout keyboardLayout property variant target: null property bool interactive: true property variant targetItem: findKeyItem(target) anchors.fill: parent visible: !!targetItem function findKeyItem(key) { for (var i = 0; i < keys.count; i++) { var keyItem = keys.itemAt(i) if (keyItem.key == key) { return keyItem } } return null; } Repeater { model: 4 delegate: SelectionGrip { keyboardLayout: root.keyboardLayout target: root.target targetItem: root.targetItem interactive: root.interactive horizontalPosition: index % 2 == 0? "left": "right" verticalPosition: index < 2? "top": "bottom" } } } diff --git a/src/qml/common/SheetDialog.qml b/src/qml/common/SheetDialog.qml index babfffc..aad239d 100644 --- a/src/qml/common/SheetDialog.qml +++ b/src/qml/common/SheetDialog.qml @@ -1,109 +1,109 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 Item { id: root SystemPalette { id: activePalette colorGroup: SystemPalette.Active } property alias content: contentArea.children property int innerMargin: 20 signal opened signal closed function open() { root.state = "open" opened() } function close() { root.state = "closed" closed() } function isOpen() { return root.state == "open" } clip: true visible: bg.opacity > 0 /* swallow all mouse events */ MouseArea { anchors.fill: parent hoverEnabled: true } Rectangle { id: bg anchors.fill: parent color: activePalette.window opacity: 1.0 Behavior on opacity { NumberAnimation { duration: 300 } } } Rectangle { id: slider color: activePalette.window width: parent.width height: parent.height Item { id: contentArea anchors { fill: parent } } } state: "closed" states: [ State { name: "open" PropertyChanges { target: bg; opacity: 1.0 } PropertyChanges { target: slider; y: 0 } }, State { name: "closed" PropertyChanges { target: bg; opacity: 0 } PropertyChanges { target: slider; y: -slider.parent.height } } ] transitions: [ Transition { from: "*" to: "*" NumberAnimation { target: slider; property: "y"; duration: 300; easing.type: Easing.InOutQuad } } ] } diff --git a/src/qml/common/TextArea.qml b/src/qml/common/TextArea.qml index 53ce3fd..fd80931 100644 --- a/src/qml/common/TextArea.qml +++ b/src/qml/common/TextArea.qml @@ -1,56 +1,56 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 as Controls import ktouch 1.0 Controls.TextArea { id: control property KColorScheme colorScheme: KColorScheme { colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled colorSet:KColorScheme.View } property KColorScheme selectionColorScheme: KColorScheme { colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled colorSet:KColorScheme.Selection } selectByMouse: true selectByKeyboard: true color: control.colorScheme.normalText selectionColor: selectionColorScheme.normalBackground selectedTextColor: selectionColorScheme.normalText background: Rectangle { color: control.colorScheme.normalBackground border.width: 1 border.color: control.activeFocus? control.colorScheme.focusDecoration: Qt.hsva(control.colorScheme.focusDecoration.hslHue, 0, control.colorScheme.focusDecoration.hslValue, control.enabled? 0.5: 0.2) Behavior on border.color { ColorAnimation { duration: 150 } } } } diff --git a/src/qml/common/TextField.qml b/src/qml/common/TextField.qml index 9c146e6..2e77952 100644 --- a/src/qml/common/TextField.qml +++ b/src/qml/common/TextField.qml @@ -1,52 +1,52 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 as Controls import ktouch 1.0 Controls.TextField { id: control property KColorScheme colorScheme: KColorScheme { colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled colorSet:KColorScheme.View } property KColorScheme selectionColorScheme: KColorScheme { colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled colorSet:KColorScheme.Selection } selectByMouse: true selectionColor: selectionColorScheme.normalBackground selectedTextColor: selectionColorScheme.normalText background: Rectangle { color: control.colorScheme.normalBackground border.width: 1 border.color: control.activeFocus? control.colorScheme.focusDecoration: Qt.hsva(control.colorScheme.focusDecoration.hslHue, 0, control.colorScheme.focusDecoration.hslValue, control.enabled? 0.5: 0.2) Behavior on border.color { ColorAnimation { duration: 150 } } } } diff --git a/src/qml/homescreen/CourseSelector.qml b/src/qml/homescreen/CourseSelector.qml index c69aa33..68e4c23 100644 --- a/src/qml/homescreen/CourseSelector.qml +++ b/src/qml/homescreen/CourseSelector.qml @@ -1,192 +1,192 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Controls 2.0 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" FocusScope { id: root property Profile profile property string currentKeyboardLayoutName property string selectedKeyboardLayoutName property DataIndexKeyboardLayout selectedKeyboardLayout property DataIndexCourse selectedCourse signal lessonSelected(variant course, variant lesson) signal courseSelectec(Course course) function selectLastUsedCourse() { if (!profile) { return } var courseId = profile.lastUsedCourseId; // fist try to to select the course the user has used last for (var i = 0; i < allCoursesModel.rowCount(); i++) { var dataIndexCourse = allCoursesModel.data(allCoursesModel.index(i, 0), ResourceModel.DataRole); if (dataIndexCourse.id === courseId) { root.selectedCourse = dataIndexCourse return } } // if this fails try to select course matching the current keyboard layout if (coursesForCurrentKeyboardLayoutModel.rowCount() > 0) { var blub = coursesForCurrentKeyboardLayoutModel.data(coursesForCurrentKeyboardLayoutModel.index(0, 0), ResourceModel.DataRole); console.log(blub) root.selectedCourse = blub return; } // finally just select the first course if (allCoursesModel.rowCount() > 0) { root.selectedCourse = allCoursesModel.data(allCoursesModel.index(0, 0), ResourceModel.DataRole); } } onSelectedCourseChanged: { root.selectedKeyboardLayoutName = root.selectedCourse.keyboardLayoutName; for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) { var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) if (dataIndexLayout.name === root.selectedKeyboardLayoutName) { root.selectedKeyboardLayout = dataIndexLayout; return } } root.selectedKeyboardLayout = null; } function saveLastUsedCourse(course) { if (profile.lastUsedCourseId != course.id) { profile.lastUsedCourseId = course.id; profileDataAccess.updateProfile(profileDataAccess.indexOfProfile(profile)); } } onProfileChanged: selectLastUsedCourse() ResourceModel { id: resourceModel dataIndex: ktouch.globalDataIndex onRowsRemoved: { selectLastUsedCourse() } onRowsInserted: { selectLastUsedCourse() } } CategorizedResourceSortFilterProxyModel { id: allCoursesModel resourceModel: resourceModel resourceTypeFilter: ResourceModel.CourseItem } CategorizedResourceSortFilterProxyModel { id: coursesForCurrentKeyboardLayoutModel resourceModel: resourceModel resourceTypeFilter: ResourceModel.CourseItem keyboardLayoutNameFilter: root.currentKeyboardLayoutName } CategorizedResourceSortFilterProxyModel { id: currentKeyboardLayoutsModel resourceModel: resourceModel resourceTypeFilter: ResourceModel.KeyboardLayoutItem keyboardLayoutNameFilter: root.currentKeyboardLayoutName } CategorizedResourceSortFilterProxyModel { id: otherKeyboardLayoutsModel resourceModel: resourceModel resourceTypeFilter: ResourceModel.KeyboardLayoutItem keyboardLayoutNameFilter: root.currentKeyboardLayoutName invertedKeyboardLayoutNameFilter: true } KColorScheme { id: courseSelectorColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } Rectangle { id: bg anchors.fill: parent color: courseSelectorColorScheme.normalBackground } Flickable { clip: true anchors.fill: parent contentWidth: width contentHeight: content.height Column { id: content width: parent.width CourseSelectorKeyboardLayoutList { width: parent.width title: i18n('Courses For Your Keyboard Layout') model: currentKeyboardLayoutsModel resourceModel: resourceModel colorScheme: courseSelectorColorScheme selectedKeyboardLayoutName: root.selectedKeyboardLayoutName selectedCourse: root.selectedCourse onCourseSelected: { root.selectedCourse = course root.saveLastUsedCourse(course) } } CourseSelectorKeyboardLayoutList { width: parent.width title: i18n('Other courses') model: otherKeyboardLayoutsModel resourceModel: resourceModel colorScheme: courseSelectorColorScheme selectedKeyboardLayoutName: root.selectedKeyboardLayoutName selectedCourse: root.selectedCourse onCourseSelected: { root.selectedCourse = course root.saveLastUsedCourse(course) } } } ScrollBar.vertical: ScrollBar { } } } diff --git a/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml b/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml index f638514..816a49f 100644 --- a/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml +++ b/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml @@ -1,116 +1,116 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import "../common" Column { id: root property string name property alias title: keyboardLayoutItem.text property ResourceModel resourceModel property string selectedKeyboardLayoutName property DataIndexCourse selectedCourse signal courseSelected(DataIndexCourse course) height: keyboardLayoutItem.height + (loader.active? loader.height: 0) clip: true onSelectedKeyboardLayoutNameChanged: { if (selectedKeyboardLayoutName == root.name) { loader.active = true } } CategorizedResourceSortFilterProxyModel { id: courseModel resourceModel: root.resourceModel resourceTypeFilter: ResourceModel.CourseItem keyboardLayoutNameFilter: loader.keyboardLayoutNameFilter } ListItem { id: keyboardLayoutItem icon: "input-keyboard" width: parent.width onClicked: { loader.active = !loader.active if (loader.active) { if (courseModel.rowCount()) { courseSelected(courseModel.data(courseModel.index(0, 0), ResourceModel.DataRole)) } } } } Loader { id: loader width: parent.width active: false property string keyboardLayoutNameFilter: root.name sourceComponent: Component { id: courseSelectionComponent Column { Repeater { id: courseRepeater model: courseModel ListItem { text: display width: parent.width reserveSpaceForIcon: true highlighted: root.selectedCourse == dataRole onClicked: { courseSelected(dataRole) } } } ListItem { DataIndexCourse { id: customLessonsCourse title: i18n("Custom Lessons") keyboardLayoutName: root.name Component.onCompleted: { id = "custom_lessons" } } text: customLessonsCourse.title id: ownLessonsItem reserveSpaceForIcon: true width: parent.width highlighted: root.selectedCourse == customLessonsCourse onClicked: { courseSelected(customLessonsCourse) } } } } } Behavior on height { NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } } diff --git a/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml b/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml index 709616b..b371841 100644 --- a/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml +++ b/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml @@ -1,57 +1,57 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import '../common' Column { id: root property alias title: header.text property CategorizedResourceSortFilterProxyModel model property ResourceModel resourceModel property string selectedKeyboardLayoutName property DataIndexCourse selectedCourse: null property KColorScheme colorScheme signal courseSelected(DataIndexCourse course) ListItem { id: header width: parent.width font.bold: true bg.color: colorScheme.alternateBackground bg.opacity: 1 label.opacity: 0.7 } Repeater { id: repeater model: root.model CourseSelectorKeyboardLayoutItem { width: parent.width name: keyboardLayoutName title: display resourceModel: root.resourceModel selectedKeyboardLayoutName: root.selectedKeyboardLayoutName selectedCourse: root.selectedCourse onCourseSelected: { root.courseSelected(course) } } } } diff --git a/src/qml/homescreen/HomeScreen.qml b/src/qml/homescreen/HomeScreen.qml index efa9dfa..3ef212a 100644 --- a/src/qml/homescreen/HomeScreen.qml +++ b/src/qml/homescreen/HomeScreen.qml @@ -1,157 +1,157 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import ktouch 1.0 import "../common" FocusScope { id: screen property KeyboardLayout selectedKeyboardLayout: KeyboardLayout {} property string activeKeyboardLayoutName signal lessonSelected(variant course, variant lesson, variant profile) Connections { target: profileDataAccess onProfileCountChanged: findCurrentProfile() } function start() {} function reset() { profileDataAccess.loadProfiles(); } function findCurrentProfile() { profileComboBox.profile = null var lastProfileId = preferences.lastUsedProfileId for (var i = 0; i < profileDataAccess.profileCount; i++) { var profile = profileDataAccess.profile(i) if (profile.id === lastProfileId) { profileComboBox.profile = profile return; } } if (profileDataAccess.profileCount > 0) { profileComboBox.profile = profileDataAccess.profile(0) preferences.lastUsedProfileId = profileComboBox.profile.id preferences.writeConfig() } } function safeLastUsedProfile(profile) { preferences.lastUsedProfileId = profile.id preferences.writeConfig() } RowLayout { anchors.fill: parent spacing: 0 Item { id: navigationArea z: 2 Layout.preferredWidth: 300 Layout.fillHeight: true DropShadow { anchors.fill: navigationAreaLayout source: navigationAreaLayout samples: 16 horizontalOffset: 0 verticalOffset: 0 } ColumnLayout { id: navigationAreaLayout anchors.fill: parent spacing: 0 ToolBar { id: header Layout.fillWidth: true background: Rectangle { color: toolbarColorScheme.toolbarBackground } KColorScheme { id: toolbarColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Complementary property color toolbarBackground: Qt.darker(toolbarColorScheme.shade(toolbarColorScheme.hoverDecoration, KColorScheme.MidShade, toolbarColorScheme.contrast, -0.2), 1.3) } RowLayout { anchors.fill: parent spacing: 5 ProfileComboBox { id: profileComboBox colorScheme: toolbarColorScheme manageProfileButtonBgColor: toolbarColorScheme.toolbarBackground Layout.fillHeight: true Layout.preferredWidth: 300 Layout.fillWidth: true onActivated: { safeLastUsedProfile(profile) } } } } CourseSelector { id: courseSelector Layout.fillHeight: true Layout.fillWidth: true profile: profileComboBox.profile currentKeyboardLayoutName: screen.activeKeyboardLayoutName onSelectedKeyboardLayoutChanged: { dataAccess.loadKeyboardLayout(courseSelector.selectedKeyboardLayout, screen.selectedKeyboardLayout) } } } } LessonSelector { Layout.fillHeight: true Layout.fillWidth: true profile: profileComboBox.profile selectedKeyboardLayout: screen.selectedKeyboardLayout activeKeyboardLayoutName: screen.activeKeyboardLayoutName dataIndexCourse: courseSelector.selectedCourse onLessonSelected: screen.lessonSelected(course, lesson, profileComboBox.profile) z: 1 } } InitialProfileDialog { id: initialProfileForm visible: profileDataAccess.profileCount == 0 } } diff --git a/src/qml/homescreen/InitialProfileDialog.qml b/src/qml/homescreen/InitialProfileDialog.qml index 32b5262..07196a9 100644 --- a/src/qml/homescreen/InitialProfileDialog.qml +++ b/src/qml/homescreen/InitialProfileDialog.qml @@ -1,47 +1,47 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 import '../common' PopupDialog { id: root modal: true title: i18n("Welcome to Typewriting Trainer") closePolicy: Popup.NoAutoClose padding: 20 function save() { var profile = profileDataAccess.createProfile() profile.name = form.name profile.skillLevel = form.skillLevel profileDataAccess.addProfile(profile) close() } contentItem: ProfileForm { id: form doneButtonIconSource: "go-next-view" doneButtonText: i18n("Start Training") onDone: save() } } diff --git a/src/qml/homescreen/LessonEditorDialog.qml b/src/qml/homescreen/LessonEditorDialog.qml index 7e07eb3..8cb5167 100644 --- a/src/qml/homescreen/LessonEditorDialog.qml +++ b/src/qml/homescreen/LessonEditorDialog.qml @@ -1,110 +1,110 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 import '../common' PopupDialog { id: root property Lesson lesson: Lesson {} property KeyboardLayout keyboardLayout: KeyboardLayout {} property Profile profile: Profile {} onClosed: { profileDataAccess.storeCustomLesson(root.lesson, root.profile, root.keyboardLayout.name) } title: i18n("Edit lesson") margins: { left: 40 bottom: 40 right: 40 top: 40 } width: parent.width - leftMargin - rightMargin height: parent.height - topMargin - bottomMargin padding: titleLabel.font.pixelSize contentItem: GridLayout { columnSpacing: titleLabel.font.pixelSize rowSpacing: titleLabel.font.pixelSize Label { id: titleLabel text: i18n("Title:") Layout.row: 0 Layout.column: 0 } TextField { id: titleTextField text: root.lesson? root.lesson.title: "" onTextChanged: { if (root.lesson) { root.lesson.title = text } } Layout.row: 0 Layout.column: 1 Layout.fillWidth: true } ScrollView { Layout.row: 1 Layout.column: 0 Layout.columnSpan: 2 Layout.fillHeight: true Layout.fillWidth: true TextArea { id: lessonTextArea text: root.lesson? root.lesson.text: "" onTextChanged: { if (root.lesson) { root.lesson.text = text } } placeholderText: i18n("Lesson text") font.family: "monospace" LessonTextHighlighter { document: lessonTextArea.textDocument allowedCharacters: root.visible? keyboardLayout.allCharacters(): "" } } } } footer: IconButton { text: i18n("Done") icon: "dialog-ok" bgColor: colorScheme.positiveBackground onClicked: { root.close() } } } diff --git a/src/qml/homescreen/LessonLockedNotice.qml b/src/qml/homescreen/LessonLockedNotice.qml index e3542f7..7b565fb 100644 --- a/src/qml/homescreen/LessonLockedNotice.qml +++ b/src/qml/homescreen/LessonLockedNotice.qml @@ -1,80 +1,80 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import org.kde.kquickcontrolsaddons 2.0 import ktouch 1.0 Item { id: root width: content.width height: content.height property color glowColor: "#ffffff" KColorScheme { id: palette colorGroup: KColorScheme.Active colorSet: KColorScheme.Window } Column { id: content anchors.centerIn: parent width: Math.max(icon.width, text.width) spacing: 10 QIconItem { id: icon anchors.horizontalCenter: parent.horizontalCenter icon: "object-locked" width: 128 height: 128 } Rectangle { anchors.horizontalCenter: parent.horizontalCenter width: text.width + 2 * text.font.pixelSize height: text.height + text.font.pixelSize radius: text.font.pixelSize color: palette.neutralBackground Label { anchors.centerIn: parent id: text text: i18n("Complete Previous Lessons to Unlock") horizontalAlignment: Text.AlignHCenter font.weight: Font.Bold wrapMode: Text.Wrap } } } Glow { anchors.fill: content source: content color: root.glowColor samples: 25 } } diff --git a/src/qml/homescreen/LessonSelector.qml b/src/qml/homescreen/LessonSelector.qml index 379d5c6..eff117b 100644 --- a/src/qml/homescreen/LessonSelector.qml +++ b/src/qml/homescreen/LessonSelector.qml @@ -1,401 +1,401 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 import QtGraphicalEffects 1.0 import "../common" ColumnLayout { id: root property Profile profile property DataIndexCourse dataIndexCourse property KeyboardLayout selectedKeyboardLayout property string activeKeyboardLayoutName property alias course: courseItem property Lesson selectedLesson: null signal lessonSelected(Course course, Lesson lesson) function update() { if (!course.isValid) return; if (!profile) return; course.updateLastUnlockedLessonIndex(); selectLastLesson() } spacing: 0 function selectLastLesson() { var lessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastSelectedLesson); if (lessonId !== "") { for (var index = 0; index < course.lessonCount; index++) { if (course.lesson(index).id === lessonId) { root.selectedLesson = course.lesson(index) content.currentIndex = index break } } } } onProfileChanged: update() onDataIndexCourseChanged: { root.selectedLesson = null; course.update(); root.update(); } Course { id: courseItem property int lastUnlockedLessonIndex: -1 property bool editable: courseItem.isValid && courseItem.id === "custom_lessons" function update() { if (root.dataIndexCourse === null) { return } if (dataIndexCourse.id == "custom_lessons") { profileDataAccess.loadCustomLessons(root.profile, dataIndexCourse.keyboardLayoutName, courseItem) } else { if (isValid && courseItem.id === dataIndexCourse.id) { return } dataAccess.loadCourse(dataIndexCourse, courseItem) } } function updateLastUnlockedLessonIndex() { lastUnlockedLessonIndex = 0; if (course.kind == Course.LessonCollection || profile.skillLevel === Profile.Advanced) { lastUnlockedLessonIndex = course.lessonCount - 1; return } var lastUnlockedLessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastUnlockedLesson); if (lastUnlockedLessonId !== "") { for (var index = 0; index < course.lessonCount; index++) { lastUnlockedLessonIndex = index if (course.lesson(index).id === lastUnlockedLessonId) { return } } } } function createNewCustomLesson() { var lesson = ktouch.createLesson(); lesson.id = utils.uuid() profileDataAccess.storeCustomLesson(lesson, root.profile, root.selectedKeyboardLayout.name) course.addLesson(lesson) updateLastUnlockedLessonIndex() content.currentIndex = course.indexOfLesson(lesson) root.selectedLesson = lesson lessonEditorDialog.open() } function deleteCustomLesson() { var msgItem = lessonDeletedMessageComponent.createObject(header); msgItem.deletedLesson.copyFrom(selectedLesson); profileDataAccess.deleteCustomLesson(selectedLesson.id); course.removeLesson(course.indexOfLesson(selectedLesson)); root.selectedLesson = null; content.currentIndex = -1; } function restoreCustomLesson(lesson) { course.addLesson(lesson); profileDataAccess.storeCustomLesson(lesson, root.profile, root.selectedKeyboardLayout.name) content.currentIndex = course.indexOfLesson(lesson); updateLastUnlockedLessonIndex(); } Component.onCompleted: update() } StatPopupDialog { id: statPopupDialog profile: root.profile course: course lesson: root.selectedLesson } LessonEditorDialog { id: lessonEditorDialog profile: root.profile keyboardLayout: root.selectedKeyboardLayout lesson: root.selectedLesson } Item { Layout.fillWidth: true Layout.preferredHeight: header.height z: 2 Column { id: header width: parent.width ToolBar { id: toolbar width: parent.width background: Rectangle { color: toolbarColorScheme.toolbarBackground } KColorScheme { id: toolbarColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Complementary property color toolbarBackground: Qt.darker(toolbarColorScheme.shade(toolbarColorScheme.hoverDecoration, KColorScheme.MidShade, toolbarColorScheme.contrast, -0.2), 1.5) } RowLayout { anchors.fill: parent anchors.leftMargin: 20 spacing: 5 Label { text: root.course? root.course.title: "" font.bold: true color: toolbarColorScheme.normalText } IconToolButton { id: toggleCourseDesciptionButton icon: "help-about" checkable: true color: toolbarColorScheme.normalText backgroundColor: toolbarColorScheme.normalBackground Layout.fillHeight: true Layout.preferredWidth: toolbar.height } ToolSeparator { visible: courseItem.editable } IconToolButton { id: newLessonButton icon: "document-new" text: "Add New Lesson" color: toolbarColorScheme.normalText visible: courseItem.editable backgroundColor: toolbarColorScheme.normalBackground Layout.fillHeight: true onClicked: { course.createNewCustomLesson() } } Item { Layout.fillWidth: true } IconToolButton { id: configureButton icon: "application-menu" color: toolbarColorScheme.normalText backgroundColor: toolbarColorScheme.normalBackground Layout.fillHeight: true Layout.preferredWidth: toolbar.height onClicked: { var position = mapToItem(null, 0, height) ktouch.showMenu(position.x, position.y) } } } } CourseDescriptionItem { id: courseDescriptionItem width: parent.width collapsed: !toggleCourseDesciptionButton.checked description: courseItem.description } KeyboardLayoutMismatchMessage { width: parent.width collapsed: !root.course || !root.course.isValid || root.activeKeyboardLayoutName == root.course.keyboardLayoutName } Component { id: lessonDeletedMessageComponent LessonDeletedMessage { width: parent.width onUndeleteRequested: { course.restoreCustomLesson(deletedLesson) } } } } DropShadow { anchors.fill: header source: header samples: 16 horizontalOffset: 0 verticalOffset: 0 } } Item { Layout.fillHeight: true Layout.fillWidth: true z: 1 Rectangle { anchors.fill: parent color: content.colorScheme.shade(content.colorScheme.normalBackground, KColorScheme.DarkShade, 1, 0.0) } GridView { id: content anchors.fill: parent anchors.leftMargin: 20 clip: true focus: true background.color: colorScheme.shade(colorScheme.normalBackground, KColorScheme.DarkShade, 1, 0.0) property int columns: Math.floor(width / (300 + 20)) cellWidth: Math.floor(content.width / content.columns) cellHeight: Math.round(cellWidth * 2 / 3) Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { event.accepted = true; if (root.selectedLesson && content.currentIndex <= course.lastUnlockedLessonIndex) { lessonSelected(course, root.selectedLesson) } } } model: LessonModel { id: lessonModel course: courseItem } onCurrentIndexChanged: { if (lessonModel.rowCount() > 0 && currentIndex != -1) { root.selectedLesson = lessonModel.data(lessonModel.index(currentIndex, 0), LessonModel.DataRole) } else { root.selectedLesson = null } } delegate: Item { id: item width: content.cellWidth height: content.cellHeight LessonSelectorItem { id: lessonItem anchors.fill: parent anchors.topMargin: 10 anchors.leftMargin: 0 anchors.rightMargin: 20 anchors.bottomMargin: 10 anchors.centerIn: parent lesson: dataRole selected: content.currentIndex == index editable: courseItem.editable onClicked: { item.forceActiveFocus() content.currentIndex = index } onDoubleClicked: { if (index <= course.lastUnlockedLessonIndex) { lessonSelected(course, dataRole) } } onStatButtonClicked: { statPopupDialog.open() } onEditButtonClicked: { lessonEditorDialog.open() } onDeleteButtonClicked: { course.deleteCustomLesson(); } } LessonLockedNotice { anchors.centerIn: parent visible: index > course.lastUnlockedLessonIndex glowColor: lessonItem.background.color } } } } Item { Layout.fillWidth: true height: footer.height z: 2 ToolBar { id: footer width: parent.width KColorScheme { id: footerColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Window } background: Rectangle { color: footerColorScheme.normalBackground } RowLayout { anchors.fill: parent spacing: 0 Item { Layout.fillWidth: true height: startButton.implicitHeight IconButton { id: startButton icon: "go-next-view" bgColor: colorScheme.positiveBackground anchors.centerIn: parent text: i18n("Start Training") enabled: root.selectedLesson && content.currentIndex <= course.lastUnlockedLessonIndex onClicked: lessonSelected(course, root.selectedLesson) } } } } DropShadow { anchors.fill: footer source: footer samples: 16 horizontalOffset: 0 verticalOffset: 0 } } } diff --git a/src/qml/homescreen/LessonSelectorItem.qml b/src/qml/homescreen/LessonSelectorItem.qml index 5a4bcb7..aed8a97 100644 --- a/src/qml/homescreen/LessonSelectorItem.qml +++ b/src/qml/homescreen/LessonSelectorItem.qml @@ -1,157 +1,157 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import ktouch 1.0 import '../common' Item { id: root property Lesson lesson property bool selected property bool editable: false property alias background: background signal clicked signal doubleClicked signal deleteButtonClicked signal editButtonClicked signal statButtonClicked clip: true KColorScheme { id: selectionColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.Selection } Rectangle { id: background anchors.fill: parent color: selected? Qt.tint(selectionColorScheme.normalBackground, "#a0ffffff"): "#ffffff" } MouseArea { anchors.fill: parent onClicked: { root.clicked() } onDoubleClicked: { root.doubleClicked() } } GridLayout { id: content anchors.fill: parent anchors.margins: 10 Label { id: titleLabel Layout.column: 0 Layout.row: 0 Layout.fillWidth: true Layout.preferredHeight: buttonRow.implicitHeight text: lesson? lesson.title: "" color: "#000000" font.bold: true elide: Label.ElideRight verticalAlignment: Qt.AlignVCenter ToolTip.text: titleLabel.text ToolTip.visible: titleMouseArea.containsMouse MouseArea { id: titleMouseArea anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: titleLabel.truncated } } Row { id: buttonRow Layout.column: 1 Layout.row: 0 visible: root.selected IconToolButton { id: editButton visible: root.editable icon: 'edit-entry' color: "#000000" backgroundColor: "#c0c0c0c0" onClicked: { root.editButtonClicked(); } } IconToolButton { id: deleteButton visible: root.editable icon: 'edit-delete' color: "#000000" backgroundColor: "#c0c0c0c0" onClicked: { root.deleteButtonClicked(); } } IconToolButton { icon: 'view-statistics' color: "#000000" backgroundColor: "#c0c0c0c0" onClicked: { root.statButtonClicked(); } } } Item { Layout.column: 0 Layout.row: 1 Layout.columnSpan: 2 Layout.fillHeight: true Layout.fillWidth: true clip: true Label { id: textLabel anchors.fill: parent text: lesson? lesson.text: "" color: "#000000" font.family: 'monospace' lineHeight: 1.5 scale: Math.min(1, width / implicitWidth) transformOrigin: Item.TopLeft } Rectangle { anchors.fill: parent gradient: Gradient { GradientStop { position: 0.0; color: "#00000000" } GradientStop { position: 0.8; color: Qt.rgba(background.color.r, background.color.g, background.color.b, 0) } GradientStop { position: 1.0; color: background.color } } } } } } diff --git a/src/qml/homescreen/ProfileDetailsItem.qml b/src/qml/homescreen/ProfileDetailsItem.qml index d37cd49..8f83874 100644 --- a/src/qml/homescreen/ProfileDetailsItem.qml +++ b/src/qml/homescreen/ProfileDetailsItem.qml @@ -1,275 +1,275 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import org.kde.kcoreaddons 1.0 import org.kde.charts 0.1 as Charts import ktouch 1.0 import "../common" Item { id: root property Profile profile function update() { if (profile) { var isNewProfile = root.profile.id === -1 profileForm.name = profile.name profileForm.skillLevel = profile.skillLevel profileForm.skillLevelSelectionEnabled = isNewProfile deleteConfirmationLabel.name = profile.name state = isNewProfile? "editor": "info" } } signal deletionRequest(); onProfileChanged: update() SystemPalette { id: activePalette colorGroup: SystemPalette.Active } Item { id: infoContainer width: parent.width height: childrenRect.height anchors.centerIn: parent Column { width: parent.width height: childrenRect.height spacing: 40 LearningProgressModel { id: learningProgressModel profile: root.profile } Rectangle { anchors.horizontalCenter: parent.horizontalCenter width: parent.width - 40 height: 250 color: activePalette.base border { width: 1 color: activePalette.text } Column { id: column anchors { fill: parent topMargin: column.spacing + spacing + legend.height leftMargin: column.spacing rightMargin: column.spacing bottomMargin: column.spacing } spacing: 20 width: parent.width height: parent.height - legend.height - parent.spacing LearningProgressChart { id: learningProgressChart anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: parent.height - legend.height - parent.spacing model: learningProgressModel } Row { id: legend anchors.horizontalCenter: parent.horizontalCenter spacing: 20 Charts.LegendItem { dimension: learningProgressChart.accuracy } Charts.LegendItem { dimension: learningProgressChart.charactersPerMinute } } } } InformationTable { id: profileInfoTable property int trainedLessonCount: profile && profile.id !== -1? profileDataAccess.lessonsTrained(profile): 0 property list infoModel: [ InfoItem { title: i18n("Lessons trained:") text: profile && profile.id !== -1? profileInfoTable.trainedLessonCount: "" }, InfoItem { title: i18n("Total training time:") text: profile && profile.id !== -1? Format.formatDuration(profileDataAccess.totalTrainingTime(profile)): "" }, InfoItem { title: i18n("Last trained:") text: profile && profile.id !== -1 && profileInfoTable.trainedLessonCount > 0? profileDataAccess.lastTrainingSession(profile).toLocaleDateString(): i18n("Never") } ] model: infoModel } } InlineToolbar { anchors { top: parent.top horizontalCenter: parent.horizontalCenter topMargin: 5 } content: [ IconToolButton { icon: "document-edit" text: i18n("Edit") onClicked: root.state = "editor" }, IconToolButton { icon: "edit-delete" text: i18n("Delete") enabled: profileDataAccess.profileCount > 1 onClicked: root.state = "deleteConfirmation" } ] } } Item { id: editorContainer width: parent.width - 40 height: childrenRect.height anchors.centerIn: parent ProfileForm { id: profileForm width: parent.width showWelcomeLabel: false onDone: { root.profile.name = profileForm.name root.profile.skillLevel = profileForm.skillLevel if (root.profile.id === -1) { profileDataAccess.addProfile(profile) } else { profileDataAccess.updateProfile(profileDataAccess.indexOfProfile(root.profile)) } root.update() root.state = "info" } } } Item { id: deleteConfirmationContainer width: parent.width - 40 height: childrenRect.height anchors.centerIn: parent Column { width: parent.width height: childrenRect.height spacing: 15 Label { property string name id: deleteConfirmationLabel width: parent.width text: i18n("Do you really want to delete the profile \"%1\"?", name) wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Row { spacing: 10 anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width height: childrenRect.height IconButton { icon: "edit-delete" text: i18n("Delete") bgColor: colorScheme.negativeBackground onClicked: root.deletionRequest() } IconButton { text: i18n("Cancel") onClicked: root.state = "info" } } } } states: [ State { name: "info" PropertyChanges { target: infoContainer visible: true } PropertyChanges { target: editorContainer visible: false } PropertyChanges { target: deleteConfirmationContainer visible: false } }, State { name: "editor" PropertyChanges { target: infoContainer visible: false } PropertyChanges { target: editorContainer visible: true } PropertyChanges { target: deleteConfirmationContainer visible: false } }, State { name: "deleteConfirmation" PropertyChanges { target: infoContainer visible: false } PropertyChanges { target: editorContainer visible: false } PropertyChanges { target: deleteConfirmationContainer visible: true } } ] } diff --git a/src/qml/homescreen/ProfileForm.qml b/src/qml/homescreen/ProfileForm.qml index e56f367..688140f 100644 --- a/src/qml/homescreen/ProfileForm.qml +++ b/src/qml/homescreen/ProfileForm.qml @@ -1,116 +1,116 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" ColumnLayout { id: root property alias name: nameTextField.text property int skillLevel: 0 property bool skillLevelSelectionEnabled: true property alias showWelcomeLabel: welcomeLabel.visible property alias doneButtonIconSource: doneBtn.icon property alias doneButtonText: doneBtn.text signal done() onSkillLevelChanged: { beginnerRadioButton.checked = skillLevel == Profile.Beginner advancedRadioButton.checked = skillLevel == Profile.Advanced } spacing: 15 Label { id: welcomeLabel Layout.fillWidth: true Layout.preferredWidth: 500 text: i18n("Before you start training, please introduce yourself:") } TextField { id: nameTextField Layout.fillWidth: true placeholderText: i18n("Name") } RadioButton { id: beginnerRadioButton Layout.maximumWidth: parent.width enabled: root.skillLevelSelectionEnabled text: i18n("I have no or only very little experience in machine typing") label.wrapMode: Text.Wrap onCheckedChanged: { if (checked) { root.skillLevel = Profile.Beginner } } } Label { text: i18n("Lessons are unlocked as your typing skills improve over time.") wrapMode: Text.Wrap Layout.maximumWidth: parent.width leftPadding: font.pixelSize * 2 font.italic: true enabled: root.skillLevelSelectionEnabled } RadioButton { id: advancedRadioButton Layout.maximumWidth: parent.width enabled: root.skillLevelSelectionEnabled text: i18n("I have no or only very little experience in machine typing") label.wrapMode: Text.Wrap onCheckedChanged: { if (checked) { root.skillLevel = Profile.Advanced } } } Label { text: i18n("All lessons are unlocked immediately.") wrapMode: Text.Wrap Layout.maximumWidth: parent.width leftPadding: font.pixelSize * 2 font.italic: true enabled: root.skillLevelSelectionEnabled } Item { Layout.fillWidth: true Layout.preferredHeight: doneBtn.implicitHeight IconButton { id: doneBtn anchors.horizontalCenter: parent.horizontalCenter bgColor: colorScheme.positiveBackground text: i18n("Done") enabled: nameTextField.text !== "" && (beginnerRadioButton.checked || advancedRadioButton.checked) icon: "dialog-ok" onClicked: done() } } } diff --git a/src/qml/homescreen/ProfileSelector.qml b/src/qml/homescreen/ProfileSelector.qml index aa9a31f..7062d0f 100644 --- a/src/qml/homescreen/ProfileSelector.qml +++ b/src/qml/homescreen/ProfileSelector.qml @@ -1,120 +1,120 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" FocusScope { id: root signal profileChosen(variant profile) function createNewProfile() { var profile = profileDataAccess.createProfile() profileForm.profile = profile } function selectProfile(index) { list.currentIndex = index profileForm.profile = profileDataAccess.profile(index) } KColorScheme { id: listColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } ColumnLayout { anchors.fill: parent spacing: 0 RowLayout { Layout.fillWidth: true Layout.fillHeight: true ListView { id: list Layout.fillWidth: true Layout.fillHeight: true model: profileDataAccess.profileCount + 1 clip: true delegate: ListItem { property bool isNewButton: index >= profileDataAccess.profileCount width: list.width text: isNewButton? i18n("Create New Profile"): index < profileDataAccess.profileCount? profileDataAccess.profile(index).name: null label.font.italic: isNewButton icon: isNewButton? "list-add": "user-identity" highlighted: ListView.isCurrentItem onClicked: { list.currentIndex = index if (isNewButton) { createNewProfile() } else { selectProfile(index) } } onDoubleClicked: { if (!isNewButton) { root.profileChosen(profileDataAccess.profile(list.currentIndex)) } } } onCurrentItemChanged: { if (currentItem.isNewButton) { createNewProfile() } else { selectProfile(currentIndex) } } } ProfileDetailsItem { id: profileForm Layout.fillWidth: true Layout.fillHeight: true onDeletionRequest: { var index = profileDataAccess.indexOfProfile(profileForm.profile) profileForm.profile = null profileDataAccess.removeProfile(index) selectProfile(Math.max(0, list.currentIndex - 1)) } } } IconButton { id: selectButton Layout.fillWidth: true icon: "go-next-view" text: i18n("Use Selected Profile") enabled: list.currentIndex !== -1 && list.currentIndex < profileDataAccess.profileCount bgColor: colorScheme.positiveBackground onClicked: root.profileChosen(profileDataAccess.profile(list.currentIndex)) } } } diff --git a/src/qml/homescreen/StatPopupDialog.qml b/src/qml/homescreen/StatPopupDialog.qml index 443264f..ce90929 100644 --- a/src/qml/homescreen/StatPopupDialog.qml +++ b/src/qml/homescreen/StatPopupDialog.qml @@ -1,90 +1,90 @@ /* * Copyright 2017 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.6 +import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 import org.kde.charts 0.1 as Charts import '../common' PopupDialog { id: root property alias course: learningProgressModel.courseFilter property alias lesson: learningProgressModel.lessonFilter property alias profile: learningProgressModel.profile title: i18n("Lesson training statistics") modal: true margins: { left: 40 bottom: 40 right: 40 top: 40 } width: parent.width - leftMargin - rightMargin height: parent.height - topMargin - bottomMargin padding: 0 contentItem: Rectangle { color: colorScheme.normalBackground ColumnLayout { anchors.fill: parent anchors.margins: titleLabel.font.pixelSize spacing: titleLabel.font.pixelSize Label { id: titleLabel text: lesson? lesson.title: "" font.bold: true } LearningProgressChart { id: learningProgressChart Layout.fillHeight: true Layout.fillWidth: true backgroundColor: colorScheme.normalBackground model: LearningProgressModel { id: learningProgressModel } } Row { spacing: 2 * titleLabel.font.pixelSize Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Charts.LegendItem { id: accuracyLegend dimension: learningProgressChart.accuracy } Charts.LegendItem { id: charactersPerMinuteLegend dimension: learningProgressChart.charactersPerMinute } } } KColorScheme { id: colorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } } } diff --git a/src/qml/keyboard/KeyItem.qml b/src/qml/keyboard/KeyItem.qml index 9404189..74ab7bb 100644 --- a/src/qml/keyboard/KeyItem.qml +++ b/src/qml/keyboard/KeyItem.qml @@ -1,310 +1,310 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtGraphicalEffects 1.0 import ktouch 1.0 Item { id: item property int keyIndex property KeyboardLayout keyboardLayout property bool isHighlighted: false property bool animateHighlight: true property bool enabled: true property bool pressed: false property AbstractKey key: item.keyboardLayout.key(item.keyIndex) property AbstractKey referenceKey: keyboardLayout.referenceKey function match(data) { var eventText = data var eventKey = -1 if (typeof data === "object") { eventText = data.text eventKey = data.key } if (typeof data === "number") { eventText = "" eventKey = data } switch (key.keyType()) { case "key": for (var i = 0; i < key.keyCharCount; i++) { if (key.keyChar(i).value == eventText) { return true; } } return false case "specialKey": switch (key.type) { case SpecialKey.Tab: return eventKey == Qt.Key_Tab case SpecialKey.Capslock: return eventKey == Qt.Key_CapsLock case SpecialKey.Shift: return eventKey == Qt.Key_Shift case SpecialKey.Backspace: return eventKey == Qt.Key_Backspace case SpecialKey.Return: return eventKey == Qt.Key_Return case SpecialKey.Space: return eventKey == Qt.Key_Space || eventText == " " } return false } return false; } function getTint(color) { color.a = 0.125 return color } property color tint: key && key.keyType() == "key"? getTint(preferences.fingerColor(key.fingerIndex)): "#00000000" x: Math.round(key.left * horizontalScaleFactor) y: Math.round(key.top * verticalScaleFactor) width: Math.round(key.width * horizontalScaleFactor) height: Math.round(key.height * verticalScaleFactor) state: enabled? (pressed? "pressed": "normal"): "disabled" onIsHighlightedChanged: { if (!animateHighlight) { shadow.state = isHighlighted? "highlighted1": "normal" } } Rectangle { id: shadow property int marginSize: 0 anchors.centerIn: parent width: item.width + marginSize height: item.height + marginSize smooth: true radius: body.radius state: "normal" states: [ State { name: "normal" PropertyChanges { target: shadow color: "#000" marginSize: 0 } PropertyChanges { target: shadowEffect glowRadius: 10 } }, State { name: "highlighted1" PropertyChanges { target: shadow color: "#54A7F0" marginSize: 4 } PropertyChanges { target: shadowEffect glowRadius: 15 } }, State { name: "highlighted2" PropertyChanges { target: shadow color: "#54A7F0" marginSize: 0 } PropertyChanges { target: shadowEffect glowRadius: 15 } } ] Behavior on marginSize { enabled: animateHighlight NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } Behavior on color { enabled: animateHighlight ColorAnimation { duration: 150 } } SequentialAnimation { id: pulseAnimation loops: Animation.Infinite running: isHighlighted && animateHighlight onRunningChanged: { if (!running) shadow.state = "normal" } ScriptAction { script: shadow.state = "highlighted1" } PauseAnimation { duration: 850 } ScriptAction { script: shadow.state = "highlighted2" } PauseAnimation { duration: 150 } } } RectangularGlow { id: shadowEffect anchors.fill: shadow color: shadow.color glowRadius: 5 Behavior on glowRadius { enabled: animateHighlight NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } } Rectangle { id: body anchors.fill: parent radius: Math.max(3, Math.min(referenceKey.height, referenceKey.width) / 10 * Math.min(horizontalScaleFactor, verticalScaleFactor)) border.width: 1 border.color: "#000" smooth: true gradient: Gradient { GradientStop { id: gradientStop0; position: 0.0; } GradientStop { id: gradientStop1; position: 0.5; } GradientStop { id: gradientStop2; position: 1.0; } } Rectangle { id: hapticMarker anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: 4 } visible: item.key.keyType() == "key" && item.key.hasHapticMarker height: 3 width: body.width / 3 radius: 1 color: topLeftLabel.color border { width: 1 color: topLeftLabel.color } } } Item { anchors.topMargin: Math.max(referenceKey.width / 20, 3 * verticalScaleFactor) anchors.bottomMargin: anchors.topMargin anchors.leftMargin: Math.max(referenceKey.width / 10, 5 * horizontalScaleFactor) anchors.rightMargin: anchors.leftMargin anchors.fill: parent KeyLabel { id: topLeftLabel key: item.key position: KeyChar.TopLeft } KeyLabel { id: topRightLabel anchors.right: parent.right key: item.key position: KeyChar.TopRight } KeyLabel { id: bottomLeftLabel anchors.bottom: parent.bottom key: item.key position: KeyChar.BottomLeft } KeyLabel { id: bottomRightLabel anchors.right: parent.right anchors.bottom: parent.bottom key: item.key position: KeyChar.BottomRight } } states: [ State { name: "normal" PropertyChanges { target: gradientStop0 color: Qt.tint("#f0f0f0", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#d5d5d5", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#ccc", item.tint) } }, State { name: "pressed" PropertyChanges { target: gradientStop0 color: Qt.tint("#666", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#888", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#999", item.tint) } }, State { name: "disabled" PropertyChanges { target: gradientStop0 color: Qt.tint("#444", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#333", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#222", item.tint) } } ] } diff --git a/src/qml/keyboard/KeyLabel.qml b/src/qml/keyboard/KeyLabel.qml index ed11262..45a9b48 100644 --- a/src/qml/keyboard/KeyLabel.qml +++ b/src/qml/keyboard/KeyLabel.qml @@ -1,67 +1,67 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Text { id: root property AbstractKey key property variant position function specialKeyLabel(type) { switch (key.type) { case SpecialKey.Other: return key.label case SpecialKey.Tab: return "\u21B9" case SpecialKey.Capslock: return "\u21E9" case SpecialKey.Shift: return "\u21E7" case SpecialKey.Backspace: return "\u2190" case SpecialKey.Return: return "\u21B5" case SpecialKey.Space: return ""; } } function keyChar(position, key) { for (var i = 0; i < key.keyCharCount; i++) { var keyChar = key.keyChar(i); if (position === keyChar.position) { return keyChar } } return null; } function keyLabel(position, key) { var keyChar = root.keyChar(position, key) return keyChar? keyChar.value: "" } color: key.state === "normal"? "#333": "#222" smooth: true font.pixelSize: referenceKey.height * Math.min(horizontalScaleFactor, verticalScaleFactor) / 3 text: key.keyType() === "specialKey"? position === KeyChar.TopLeft? specialKeyLabel(key.type): "": keyLabel(position, key) } diff --git a/src/qml/keyboard/Keyboard.qml b/src/qml/keyboard/Keyboard.qml index 295257a..c3cc693 100644 --- a/src/qml/keyboard/Keyboard.qml +++ b/src/qml/keyboard/Keyboard.qml @@ -1,97 +1,97 @@ /* * Copyright 2013 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: keyboard signal keyboardUpdate property KeyboardLayout keyboardLayout property real aspectRatio: keyboardLayout.width / keyboardLayout.height property real horizontalScaleFactor: width / keyboardLayout.width property real verticalScaleFactor: height / keyboardLayout.height function keyItems() { var items = [] for (var i = 0; i < keys.count; i++) { items.push(keys.itemAt(i)) } return items } function findKeyItems(keyChar) { var matchingKeys = [] for (var i = 0; i < keys.count; i++) { var key = keys.itemAt(i); if (key.match(keyChar)) matchingKeys.push(key) } return matchingKeys } function findModifierKeyItem(modifierId) { for (var i = 0; i < keys.count; i++) { var key = keys.itemAt(i); if (key.key.keyType() === "specialKey" && key.key.modifierId === modifierId) return key } return null } function handleKeyPress(event) { var eventKeys = findKeyItems(event) for (var i = 0; i < eventKeys.length; i++) { eventKeys[i].pressed = true } } function handleKeyRelease(event) { var eventKeys = findKeyItems(event) for (var i = 0; i < eventKeys.length; i++) { eventKeys[i].pressed = false } } Item { id: keyContainer width: childrenRect.width height: childrenRect.height anchors.centerIn: parent Repeater { id: keys model: keyboard.visible && keyboardLayout.isValid? keyboard.keyboardLayout.keyCount: 0 onModelChanged: keyboard.keyboardUpdate() KeyItem { keyboardLayout: keyboard.keyboardLayout; keyIndex: index } } } } diff --git a/src/qml/keyboard/KeyboardLayoutEditor.qml b/src/qml/keyboard/KeyboardLayoutEditor.qml index 3f6d9c7..98abed7 100644 --- a/src/qml/keyboard/KeyboardLayoutEditor.qml +++ b/src/qml/keyboard/KeyboardLayoutEditor.qml @@ -1,114 +1,114 @@ /* * Copyright 2012 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import "../common" Item { id: root Preferences { id: preferences } property real scaleFactor: Math.pow(2, keyboardLayoutEditor.zoomLevel / 2) property real horizontalScaleFactor: scaleFactor property real verticalScaleFactor: scaleFactor property KeyboardLayout layout: keyboardLayoutEditor.keyboardLayout property int lastZIndex: 0 width: keyContainer.width + 40 height: keyContainer.height + 40 MouseArea { anchors.fill: parent onPressed: { if (mouse.button == Qt.LeftButton) { keyboardLayoutEditor.selectedKey = null mouse.accepted = true } } } LineGrid { id: keyContainer anchors.centerIn: parent width: Math.round(layout.width * scaleFactor) height: Math.round(layout.height * scaleFactor) lineDistance: 10.0 * scaleFactor color: "#121286" backgroundColor: "#cccccc" Repeater { id: keys model: layout.isValid? layout.keyCount: 0 KeyItem { property bool manipulated: false id: keyItem keyboardLayout: layout; keyIndex: index isHighlighted: keyItem.key == keyboardLayoutEditor.selectedKey animateHighlight: false opacity: manipulated? 0.7: 1.0 MouseArea { anchors.fill: parent cursorShape: keyItem.manipulated? Qt.SizeAllCursor: Qt.ArrowCursor onPressed: { if (mouse.button == Qt.LeftButton) { keyboardLayoutEditor.selectedKey = layout.key(index) root.lastZIndex++ keyItem.z = root.lastZIndex } } drag { target: !keyboardLayoutEditor.readOnly? keyItem: undefined axis: Drag.XandYAxis minimumX: 0 maximumX: keyContainer.width - keyItem.width minimumY: 0 maximumY: keyContainer.height - keyItem.height onActiveChanged: { keyItem.manipulated = drag.active if (!drag.active) { var left = 10 * Math.round(keyItem.x / scaleFactor / 10) var top = 10 * Math.round(keyItem.y / scaleFactor / 10) keyboardLayoutEditor.setKeyGeometry(keyIndex, left, top, keyItem.key.width, keyItem.key.height) } } } } Behavior on opacity { NumberAnimation { duration: 150 } } } } SelectionRectangle { keyboardLayout: layout; target: keyboardLayoutEditor.selectedKey z: root.lastZIndex + 1 interactive: !keyboardLayoutEditor.readOnly } } } diff --git a/src/qml/main.qml b/src/qml/main.qml index 3c8b59f..fe9e6cb 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,239 +1,239 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import "./common" import "./meters" import "./homescreen" import "./trainingscreen" import "./scorescreen" Rectangle { id: main color: activePallete.normalBackground property Item appContent: appContentItem function switchScreen(from, to) { switchScreenAnimation.from = from switchScreenAnimation.to = to switchScreenAnimation.start() } KColorScheme { id: activePallete colorGroup: KColorScheme.Active colorSet: KColorScheme.Window } DataAccess { id: dataAccess } QtObject { id: helper property string name: ktouch.keyboardLayoutName property int keyboardLayoutCount: ktouch.globalDataIndex.keyboardLayoutCount property int courseCount: ktouch.globalDataIndex.courseCount onNameChanged: { activeKeyboardLayout.update() } onKeyboardLayoutCountChanged: { if (ktouch.globalDataIndex.isValid) activeKeyboardLayout.update() } } ResourceModel { id: resourceModel dataIndex: ktouch.globalDataIndex } ProfileDataAccess { id: profileDataAccess } Preferences { id: preferences } KeyboardLayout { id: activeKeyboardLayout Component.onCompleted: { if (ktouch.globalDataIndex.isValid) { activeKeyboardLayout.update() } } function update() { isValid = false var name = ktouch.keyboardLayoutName; // first pass - exact match for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) { var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) if (dataIndexLayout.name === name) { dataAccess.loadKeyboardLayout(dataIndexLayout, activeKeyboardLayout) return } } // second pass - substring match for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) { var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) if (name.search(dataIndexLayout.name) === 0) { dataAccess.loadKeyboardLayout(dataIndexLayout, activeKeyboardLayout) return } } } } Course { id: selectedCourse property Lesson selectedLesson } Lesson { id: customLessonCopy } Item { anchors.fill: parent id: appContentItem layer.enabled: true HomeScreen { id: homeScreen anchors.fill: parent activeKeyboardLayoutName: activeKeyboardLayout.isValid? activeKeyboardLayout.name: helper.name visible: false focus: true onLessonSelected: { trainingScreen.profile = profile var lessonIndex = -1; for (var i = 0; i < course.lessonCount; i++) { if (lesson === course.lesson(i)) { lessonIndex = i break } } selectedCourse.copyFrom(course) if (lessonIndex !== -1) { selectedCourse.selectedLesson = selectedCourse.lesson(lessonIndex) } else { customLessonCopy.copyFrom(lesson) selectedCourse.selectedLesson = customLessonCopy } main.switchScreen(homeScreen, trainingScreen) } Component.onCompleted: { homeScreen.reset() homeScreen.visible = true homeScreen.start() } } TrainingScreen { id: trainingScreen anchors.fill: parent visible: false keyboardLayout: homeScreen.selectedKeyboardLayout course: selectedCourse lesson: selectedCourse.selectedLesson onRestartRequested: main.switchScreen(trainingScreen, trainingScreen) onAbortRequested: main.switchScreen(trainingScreen, homeScreen) onFinished: main.switchScreen(trainingScreen, scoreScreen) } ScoreScreen { id: scoreScreen anchors.fill: parent visible: false course: trainingScreen.course lesson: trainingScreen.lesson stats: trainingScreen.stats profile: trainingScreen.profile referenceStats: trainingScreen.referenceStats onHomeScreenRequested: main.switchScreen(scoreScreen, homeScreen) onLessonRepetionRequested: main.switchScreen(scoreScreen, trainingScreen) onNextLessonRequested: { selectedCourse.selectedLesson = lesson main.switchScreen(scoreScreen, trainingScreen) } } } Rectangle { id: curtain anchors.fill: parent color: "#000" opacity: 0 } SequentialAnimation { id: switchScreenAnimation property Item from property Item to NumberAnimation { target: curtain property: "opacity" to: 1 duration: switchScreenAnimation.to == homeScreen? 250: 750 easing.type: Easing.OutQuad } PropertyAction { target: switchScreenAnimation.from property: "visible" value: false } ScriptAction { script: switchScreenAnimation.to.reset() } PropertyAction { target: switchScreenAnimation.to property: "visible" value: true } ScriptAction { script: { switchScreenAnimation.to.start() switchScreenAnimation.to.forceActiveFocus() } } NumberAnimation { target: curtain property: "opacity" to: 0 duration: switchScreenAnimation.to == homeScreen? 250: 750 easing.type: Easing.InQuad } } } diff --git a/src/qml/meters/AccuracyMeter.qml b/src/qml/meters/AccuracyMeter.qml index e3658df..f8b00db 100644 --- a/src/qml/meters/AccuracyMeter.qml +++ b/src/qml/meters/AccuracyMeter.qml @@ -1,90 +1,90 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property real accuracy: 1.0 property real referenceAccuracy: 1.0 label: i18n("Accuracy") value: strFormatter.formatAccuracy(meter.accuracy) referenceValue: strFormatter.formatAccuracyDiff(meter.referenceAccuracy, meter.accuracy) valueStatus: Math.round(1000 * meter.accuracy) >= Math.round(10 * preferences.requiredAccuracy)? "good": "bad" analogPartContent: Image { anchors.centerIn: parent source: utils.findImage("accuracymeter-background.png") ScaleBackgroundItem { anchors.centerIn: parent anchors.verticalCenterOffset: 25 width: scale.width height: scale.height startAngle: Math.min(135, Math.max(45, 135 - (preferences.requiredAccuracy - 90) * 9)) stopAngle: 45 scaleMarkHeight: 8 color: "#88ff00"; } Image { id: scale anchors.centerIn: parent anchors.verticalCenterOffset: 25 source: utils.findImage("accuracymeter-scale.png") } Text { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 20 } text: "90" font.pixelSize: 10 } Text { anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "100" font.pixelSize: 10 } Image { id: hand anchors.centerIn: parent anchors.verticalCenterOffset: 25 source: utils.findImage("accuracymeter-hand.png") transform: Rotation { origin.x: hand.width / 2 origin.y: hand.height / 2 angle: Math.min(90, Math.max(0, accuracy - 0.9) * 900) Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/CharactersPerMinuteMeter.qml b/src/qml/meters/CharactersPerMinuteMeter.qml index df01d41..2762042 100644 --- a/src/qml/meters/CharactersPerMinuteMeter.qml +++ b/src/qml/meters/CharactersPerMinuteMeter.qml @@ -1,94 +1,94 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property int charactersPerMinute: 0 property int referenceCharactersPerMinute: 0 property int minimumCharactersPerMinute: preferences.requiredStrokesPerMinute property int diff: meter.charactersPerMinute - meter.referenceCharactersPerMinute label: i18n("Characters per Minute") value: meter.charactersPerMinute referenceValue: strFormatter.formatSign(diff) + " " + (diff > 0? diff: -diff) valueStatus: meter.charactersPerMinute >= minimumCharactersPerMinute? "good": "bad" analogPartContent: Image { anchors.centerIn: parent source: utils.findImage("charactersperminutemeter-background.png") ScaleBackgroundItem { anchors.centerIn: parent anchors.verticalCenterOffset: 25 width: scale.width height: scale.height startAngle: Math.min(135, Math.max(45, 135 - (minimumCharactersPerMinute * 90 / 360))) stopAngle: 45 scaleMarkHeight: 8 color: "#88ff00"; } Image { id: scale anchors.centerIn: parent anchors.verticalCenterOffset: 25 source: utils.findImage("charactersperminutemeter-scale.png") } Text { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 20 } text: "0" font.pixelSize: 10 } Text { anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "360" font.pixelSize: 10 } Image { id: hand anchors.centerIn: parent anchors.verticalCenterOffset: 25 source: utils.findImage("charactersperminutemeter-hand.png") smooth: true transform: Rotation { origin.x: hand.width / 2 origin.y: hand.height / 2 angle: Math.min(90, charactersPerMinute * 90 / 360) Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/ElapsedTimeMeter.qml b/src/qml/meters/ElapsedTimeMeter.qml index 052a717..585ffe1 100644 --- a/src/qml/meters/ElapsedTimeMeter.qml +++ b/src/qml/meters/ElapsedTimeMeter.qml @@ -1,66 +1,66 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property variant elapsedTime property variant referenceElapsedTime label: i18n("Elapsed time") value: strFormatter.formatTime(meter.elapsedTime) referenceValue: strFormatter.formatTimeDiff(meter.referenceElapsedTime, meter.elapsedTime) positiveDiffIsGood: false analogPartContent: Image { anchors.centerIn: parent source: utils.findImage("elapsedtimemeter-background.png") Image { id: minuteHand anchors.centerIn: parent source: utils.findImage("elapsedtimemeter-minute-hand.png") smooth: true transform: Rotation { origin.x: minuteHand.width / 2 origin.y: minuteHand.height / 2 angle: elapsedTime? 6 * utils.getMinutesOfQTime(elapsedTime): 0 Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } Image { id: secondHand anchors.centerIn: parent source: utils.findImage("elapsedtimemeter-second-hand.png") transform: Rotation { origin.x: secondHand.width / 2 origin.y: secondHand.height / 2 angle: elapsedTime? 6 * utils.getSecondsOfQTime(elapsedTime): 0 Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/Meter.qml b/src/qml/meters/Meter.qml index 5b197e1..d01569c 100644 --- a/src/qml/meters/Meter.qml +++ b/src/qml/meters/Meter.qml @@ -1,121 +1,121 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 RowLayout { id: meter property alias label: label.text property alias value: value.text; property alias referenceValue: referenceValue.text property bool positiveDiffIsGood: true property string valueStatus: "none" property alias analogPartContent: analogPart.data height: 112 width: 304 spacing: 0 BorderImage { id: analogPart Layout.preferredWidth: height Layout.fillHeight: true border { left: 6 top: 6 right:6 bottom: 6 } source: utils.findImage("meterbox-left.png") } BorderImage { id: digitalPart Layout.fillHeight: true Layout.fillWidth: true border { left: 6 top: 6 right:6 bottom: 6 } source: utils.findImage("meterbox-right.png") Column { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 12 right: parent.right rightMargin: 12 } spacing: 0 Row { width: parent.width spacing: 5 Text { id: label width: Math.min(implicitWidth, parent.width - statusLed.width - parent.spacing) color: "#555" font.pixelSize: 15 elide: Text.ElideRight } Rectangle { id: statusLed height: 15 width: 15 radius: height / 2 visible: valueStatus !== "none" color: valueStatus === "good"? "#88ff00": "#424b35" onColorChanged: statusLedAnimaton.restart() SequentialAnimation { id: statusLedAnimaton NumberAnimation { target: statusLed; property: "scale"; to: 1.3; duration: 150; easing.type: Easing.InOutQuad } NumberAnimation { target: statusLed; property: "scale"; to: 1.0; duration: 150; easing.type: Easing.InOutQuad } } } } Text { id: value font.pixelSize: 30 font.bold: true } Text { id: referenceValue font.pixelSize: 15 color: { if (text[0] === "+") return positiveDiffIsGood? "#006E28": "#BF0303" if (text[0] === "-") return positiveDiffIsGood? "#BF0303": "#006E28" return "#555" } } } } } diff --git a/src/qml/meters/StatBox.qml b/src/qml/meters/StatBox.qml index d5277da..0d958c4 100644 --- a/src/qml/meters/StatBox.qml +++ b/src/qml/meters/StatBox.qml @@ -1,50 +1,50 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 RowLayout { property TrainingStats stats property TrainingStats referenceStats height: childrenRect.height spacing: 10 ElapsedTimeMeter { id: elapsedTimeMeter Layout.fillWidth: true elapsedTime: stats.elapsedTime referenceElapsedTime: referenceStats.isValid? referenceStats.elapsedTime: stats.elapsedTime } CharactersPerMinuteMeter { id: charactersPerMinuteMeter Layout.fillWidth: true charactersPerMinute: stats.charactersPerMinute referenceCharactersPerMinute: referenceStats.isValid? referenceStats.charactersPerMinute: stats.charactersPerMinute } AccuracyMeter { id: accuracyMeter Layout.fillWidth: true accuracy: stats.accuracy referenceAccuracy: referenceStats.isValid? referenceStats.accuracy: stats.accuracy } } diff --git a/src/qml/scorescreen/ScoreScreen.qml b/src/qml/scorescreen/ScoreScreen.qml index 9d81696..535b22b 100644 --- a/src/qml/scorescreen/ScoreScreen.qml +++ b/src/qml/scorescreen/ScoreScreen.qml @@ -1,533 +1,533 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2016 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.charts 0.1 as Charts import "../common" import "../meters" FocusScope { id: screen function start() {} function reset() { internal.lessonPassed = Math.round(1000 * stats.accuracy) >= Math.round(10 * preferences.requiredAccuracy) && stats.charactersPerMinute >= preferences.requiredStrokesPerMinute var lessonIndex = 0; for (var i = 0; i < course.lessonCount; i++) { if (lesson === course.lesson(i)) { lessonIndex = i break } } var lastUnlockedLessonIndex = 0 if (profile.skillLevel === Profile.Advanced) { lastUnlockedLessonIndex = course.lessonCount - 1; internal.nextLessonUnlocked = false } else { var lastUnlockedLessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastUnlockedLesson); if (lastUnlockedLessonId !== "") { for (var index = 0; index < course.lessonCount; index++) { if (course.lesson(index).id === lastUnlockedLessonId) { lastUnlockedLessonIndex = index; break; } } } internal.nextLessonUnlocked = internal.lessonPassed && lessonIndex === lastUnlockedLessonIndex && lessonIndex + 1 < course.lessonCount if (internal.nextLessonUnlocked) { lastUnlockedLessonIndex++ } } internal.nextLesson = null; if (lessonIndex + 1 < course.lessonCount && lessonIndex + 1 <= lastUnlockedLessonIndex) { internal.nextLesson = course.lesson(lessonIndex + 1) } if (internal.nextLessonUnlocked) { profileDataAccess.saveCourseProgress(internal.nextLesson.id, profile, course.id, ProfileDataAccess.LastUnlockedLesson) } learningProgressModel.update() } function forceActiveFocus() { if (internal.lessonPassed && internal.nextLesson) { nextLessonButton.forceActiveFocus() } else { repeatLessonButton.forceActiveFocus() } } property Profile profile property Lesson lesson property Course course property TrainingStats stats property TrainingStats referenceStats signal homeScreenRequested signal nextLessonRequested(variant lesson) signal lessonRepetionRequested QtObject { id: internal property bool lessonPassed: false property bool nextLessonUnlocked: false property Lesson nextLesson: null } SystemPalette { id: palette colorGroup: SystemPalette.Active } LearningProgressModel { property bool filterByLesson: false id: learningProgressModel profile: screen.visible? screen.profile: null courseFilter: screen.visible? screen.course: null lessonFilter: screen.visible && filterByLesson? screen.lesson: null } ErrorsModel { id: errorsModel trainingStats: screen.visible? screen.stats: null } Balloon { id: chartTypeDialog visualParent: chartTypeButton Column { id: chartTypeDialogContents Button { id: progressChartButton width: Math.max(implicitWidth, errorsChartButton.implicitWidth) text: i18n("Progress") iconName: "office-chart-area" onClicked: { chartTypeDialog.close() tabGroup.currentIndex = 0 } } Button { id: errorsChartButton width: Math.max(implicitWidth, progressChartButton.implicitWidth) text: i18n("Errors") iconName: "office-chart-bar" onClicked: { chartTypeDialog.close() tabGroup.currentIndex = 1 } } } } Balloon { id: learningProgressDialog visualParent: learningProgressFilterButton Column { id: learningProgressDialogContents Button { id: allLessonsButton width: Math.max(implicitWidth, thisLessonButton.implicitWidth) text: i18n("All Lessons") iconName: "view-filter" onClicked: { learningProgressModel.filterByLesson = false learningProgressDialog.close() } } Button { id: thisLessonButton width: Math.max(implicitWidth, allLessonsButton.implicitWidth) text: i18n("This Lesson") iconName: "view-filter" onClicked: { learningProgressModel.filterByLesson = true learningProgressDialog.close() } } } } Balloon { id: learningProgressPointTooltip visualParent: parent property int row: -1 function findLessonTitle(id) { var course = screen.course for (var i = 0; i < course.lessonCount; i++) { if (course.lesson(i).id === id) { return course.lesson(i).title } } return i18n("Unknown") } InformationTable { property list infoModel: [ InfoItem { title: i18nc("Statistics on lesson:", "On:") text: learningProgressPointTooltip.row !== -1? learningProgressPointTooltip.findLessonTitle(learningProgressModel.lessonId(learningProgressPointTooltip.row)): "" }, InfoItem { title: i18n("Accuracy:") text: learningProgressPointTooltip.row !== -1? strFormatter.formatAccuracy(learningProgressModel.accuracy(learningProgressPointTooltip.row)): "" }, InfoItem { title: i18n("Characters per Minute:") text: learningProgressPointTooltip.row !== -1? learningProgressModel.charactersPerMinute(learningProgressPointTooltip.row): "" } ] width: 250 model: infoModel } } Balloon { id: errorsTooltip visualParent: parent property int row: -1 InformationTable { property list infoModel: [ InfoItem { title: i18n("Character:") text: errorsTooltip.row !== -1? errorsModel.character(errorsTooltip.row): "" }, InfoItem { title: i18n("Errors:") text: errorsTooltip.row !== -1? errorsModel.errors(errorsTooltip.row): "" } ] width: 250 model: infoModel } } ColumnLayout { anchors.fill: parent spacing: 0 ToolBar { id: header Layout.fillWidth: true RowLayout { anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 spacing: anchors.leftMargin Button { id: homeScreenButton anchors.verticalCenter: parent.verticalCenter text: i18n("Return to Home Screen") iconName: "go-home" onClicked: screen.homeScreenRequested() } Item { Layout.fillHeight: true Layout.fillWidth: true } Button { id: repeatLessonButton iconName: "view-refresh" text: i18n("Repeat Lesson") onClicked: screen.lessonRepetionRequested() } Button { id: nextLessonButton iconName: "go-next-view" text: i18n("Next Lesson") enabled: internal.nextLesson onClicked: screen.nextLessonRequested(internal.nextLesson) } } } Rectangle { Layout.fillHeight: true Layout.fillWidth: true color: palette.base ColumnLayout { id: content anchors.fill: parent anchors.margins: 20 spacing: 15 Rectangle { id: caption Layout.fillWidth: true Layout.preferredHeight: captionContent.height + 30 radius: 15 color: "#eee4be" Item { id: captionContent anchors.centerIn: parent width: Math.max(mainCaption.width, subCaption.width) height: subCaption.visible? mainCaption.height + subCaption.height + 5: mainCaption.height Item { id: mainCaption anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter width: captionIcon.width + captionLabel.width + 7 height: Math.max(captionIcon.height, captionLabel.height) QIconItem { id: captionIcon anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter icon: internal.lessonPassed? "dialog-ok-apply": "dialog-cancel" width: height height: captionLabel.height } Label { id: captionLabel anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: internal.lessonPassed? i18n("Congratulations! You have passed the lesson."): i18n("You have not passed the lesson.") font.pointSize: 1.5 * Qt.font({'family': 'sansserif'}).pointSize color: "#000000" } } Label { id: subCaption anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter visible: text !== "" text: { if (!internal.lessonPassed) return i18n("Repeat the lesson. Your skills will improve automatically over time.") if (internal.nextLessonUnlocked) return i18n("Next lesson unlocked") return "" } color: "#000000" } } } Rectangle { id: statsBox Layout.fillWidth: true Layout.preferredHeight: 140 radius: 15 color: "#cccccc" StatBox { anchors.centerIn: parent width: parent.width - 26 stats: screen.stats referenceStats: screen.referenceStats } } Item { id: contentSpacer Layout.preferredHeight: 10 Layout.fillWidth: true } RowLayout { id: chartControls spacing: 5 Layout.fillWidth: true Label { id: showLabel color: "#888" anchors.verticalCenter: parent.verticalCenter text: i18nc("Show a specific type of statistic", "Show") } Button { id: chartTypeButton anchors.verticalCenter: parent.verticalCenter text: tabGroup.currentTab.title iconName: tabGroup.currentTab.iconName Layout.preferredWidth: chartTypeDialogContents.width checked: chartTypeDialog.status === 'open' || chartTypeDialog.status === 'opening' onClicked: { if (checked) { chartTypeDialog.close() } else { chartTypeDialog.open() } } } Label { id: overLabel color: "#888" anchors.verticalCenter: parent.verticalCenter text: i18nc("Show a statistic over one or more lessons", "Over") opacity: tabGroup.currentTab === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Button { id: learningProgressFilterButton anchors.verticalCenter: parent.verticalCenter text: learningProgressModel.filterByLesson? thisLessonButton.text: allLessonsButton.text iconName: "view-filter" checked: chartTypeDialog.status === 'open' || chartTypeDialog.status === 'opening' opacity: tabGroup.currentTab === learningProgressTab? 1: 0 onClicked: { if (checked) { learningProgressDialog.close() } else { learningProgressDialog.open() } } Behavior on opacity { NumberAnimation {duration: 150} } } Item { Layout.fillHeight: true Layout.fillWidth: true } Charts.LegendItem { id: accuracyLegend anchors.verticalCenter: parent.verticalCenter opacity: tabGroup.currentTab === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Charts.LegendItem { id: charactersPerMinuteLegend anchors.verticalCenter: parent.verticalCenter opacity: tabGroup.currentTab === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } } TabView { id: tabGroup Layout.fillHeight: true Layout.fillWidth: true frameVisible: false tabsVisible: false property Tab currentTab: count > 0 && currentIndex != -1? getTab(currentIndex): null Tab { id: learningProgressTab title: i18n("Progress") property string iconName: "office-chart-area" LearningProgressChart { id: learningProgressChart anchors.fill: parent model: learningProgressModel backgroundColor: palette.base onElemEntered: { learningProgressPointTooltip.visualParent = elem learningProgressPointTooltip.row = row learningProgressPointTooltip.open() } onElemExited: { learningProgressPointTooltip.close() } Component.onCompleted: { accuracyLegend.dimension = learningProgressChart.accuracy charactersPerMinuteLegend.dimension = learningProgressChart.charactersPerMinute } Component.onDestruction: { accuracyLegend.dimension = 0 charactersPerMinuteLegend.dimension = 0 } } } Tab { id: errorsTab title: i18n("Errors") property string iconName: "office-chart-bar" Charts.BarChart{ anchors.fill: parent model: errorsModel pitch: 60 textRole: 3 // Qt::ToolTipRole backgroundColor: palette.base dimensions: [ Charts.Dimension { dataColumn: 0 color: "#ffb12d" maximumValue: Math.max(4, Math.ceil(errorsModel.maximumErrorCount / 4) * 4) label: i18n("Errors") } ] onElemEntered: { errorsTooltip.visualParent = elem; errorsTooltip.row = row errorsTooltip.open() } onElemExited: { errorsTooltip.close() } } } } } } } } diff --git a/src/qml/trainingscreen/KeyboardUnavailableNotice.qml b/src/qml/trainingscreen/KeyboardUnavailableNotice.qml index 9e4f094..8ec9c1f 100644 --- a/src/qml/trainingscreen/KeyboardUnavailableNotice.qml +++ b/src/qml/trainingscreen/KeyboardUnavailableNotice.qml @@ -1,53 +1,53 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import org.kde.kquickcontrolsaddons 2.0 import ktouch 1.0 Item { id: root height: row.height + 20 Row { id: row anchors.centerIn: parent width: parent.width - 60 height: Math.max(icon.height, label.height) spacing: 3 QIconItem { id: icon anchors.verticalCenter: parent.verticalCenter width: 22 height: 22 icon: "dialog-warning" } Label { id: label anchors.verticalCenter: parent.verticalCenter width: parent.width - icon.width - parent.spacing text: i18n("No visualization available for your keyboard layout.") color: "white" } } } diff --git a/src/qml/trainingscreen/TrainingScreen.qml b/src/qml/trainingscreen/TrainingScreen.qml index e5ec84c..f05f527 100644 --- a/src/qml/trainingscreen/TrainingScreen.qml +++ b/src/qml/trainingscreen/TrainingScreen.qml @@ -1,331 +1,331 @@ /* * Copyright 2013 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.5 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../keyboard" import "../meters" FocusScope { id: screen property KeyboardLayout keyboardLayout property Profile profile property Course course property Lesson lesson property alias stats: stats property alias referenceStats: referenceStats signal restartRequested() signal abortRequested() signal finished() property bool trainingStarted: false property bool trainingFinished: true property bool isActive: Qt.application.active function setLessonKeys() { if (!lesson) return; var chars = lesson.characters; var keyItems = keyboard.keyItems() var modifierItems = [] var usedModifiers = {} for (var i = 0; i < keyItems.length; i++) { var key = keyItems[i].key if (key.keyType() == "key") { keyItems[i].enabled = false; for (var j = 0; j < key.keyCharCount; j++) { var keyChar = key.keyChar(j) if (chars.indexOf(keyChar.value) != -1) { keyItems[i].enabled = true; if (keyChar.modifier !== "") { usedModifiers[keyChar.modifier] = true } } } } else { var type = keyItems[i].key.type if (type != SpecialKey.Return && type != SpecialKey.Backspace && type != SpecialKey.Space) { modifierItems.push(keyItems[i]) keyItems[i].enabled = false } else if (type == SpecialKey.Return && preferences.nextLineWithSpace) { keyItems[i].enabled = false } } } for (i = 0; i < modifierItems.length; i++) { var modifierItem = modifierItems[i] modifierItem.enabled = !!usedModifiers[modifierItem.key.modifierId] } } function reset() { toolbar.reset() trainingWidget.reset() screen.trainingStarted = false screen.trainingFinished = true profileDataAccess.loadReferenceTrainingStats(referenceStats, screen.profile, screen.course.id, screen.lesson.id) profileDataAccess.saveCourseProgress(lesson.id, profile, course.id, ProfileDataAccess.LastSelectedLesson) } function start() { screen.trainingFinished = false screen.trainingStarted = true } function forceActiveFocus() { trainingWidget.forceActiveFocus() } onLessonChanged: setLessonKeys() onIsActiveChanged: { if (!screen.isActive) { stats.stopTraining() } } TrainingStats { id: stats onTimeIsRunningChanged: { if (timeIsRunning) { screen.trainingStarted = false } } } TrainingStats { id: referenceStats } Shortcut { sequence: "Escape" enabled: screen.visible onActivated: { if (menuOverlay.active) { menuOverlay.hide() } else { menuOverlay.show() } } } ColumnLayout { id: screenContent anchors.fill: parent spacing: 0 BorderImage { Layout.fillWidth: true Layout.preferredHeight: 41 border { top: 1 bottom: 1 } cache: false source: utils.findImage("trainingscreen-toolbar.png") horizontalTileMode: BorderImage.Repeat verticalTileMode: BorderImage.Repeat TrainingScreenToolbar { id: toolbar anchors.fill: parent trainingStarted: screen.trainingStarted trainingFinished: screen.trainingFinished stats: stats menuOverlayItem: menuOverlay } } BorderImage { id: header Layout.fillWidth: true Layout.preferredHeight: visible? 130: 0 visible: preferences.showStatistics border { top: 1 bottom: 1 } source: utils.findImage("trainingscreen-header.png") cache: false StatBox { anchors.centerIn: parent width: parent.width - 60 stats: stats referenceStats: referenceStats } } BorderImage { id: body Layout.fillWidth: true Layout.fillHeight: true border { top: 1 bottom: 1 } source: utils.findImage("trainingscreen-viewport.png") cache: false TrainingWidget { id: trainingWidget anchors.fill: parent lesson: screen.lesson keyboardLayout: screen.keyboardLayout trainingStats: stats overlayContainer: trainingOverlayContainer onKeyPressed: keyboard.handleKeyPress(event) onKeyReleased: keyboard.handleKeyRelease(event) onNextCharChanged: keyboard.updateKeyHighlighting() onIsCorrectChanged: keyboard.updateKeyHighlighting() onFinished: { profileDataAccess.saveTrainingStats(stats, screen.profile, screen.course.id, screen.lesson.id) screen.finished(stats) screen.trainingFinished = true } } BorderImage { anchors.fill: parent border { top: 3 bottom: 3 } source: utils.findImage("trainingscreen-viewport-shadow.png") cache: false } } BorderImage { id: footer visible: preferences.showKeyboard Layout.fillWidth: true Layout.preferredHeight: visible? screen.keyboardLayout.isValid? Math.round(Math.min((parent.height - toolbar.height - header.height) / 2, parent.width / keyboard.aspectRatio)): keyboardUnavailableNotice.height: 0 border { top: 1 bottom: 1 } source: utils.findImage("trainingscreen-footer.png") cache: false Keyboard { id: keyboard property variant highlightedKeys: [] function highlightKey(which) { for (var i = 0; i < highlightedKeys.length; i++) highlightedKeys[i].isHighlighted = false var keys = findKeyItems(which) var newHighlightedKeys = [] for (var index = 0; index < keys.length ; index++) { var key = keys[index] if (key) { key.isHighlighted = true newHighlightedKeys.push(key) if (typeof which == "string") { for (var i = 0; i < key.key.keyCharCount; i++) { var keyChar = key.key.keyChar(i) if (keyChar.value == which && keyChar.modifier != "") { var modifier = findModifierKeyItem(keyChar.modifier) if (modifier) { modifier.isHighlighted = true newHighlightedKeys.push(modifier) } break } } } } } highlightedKeys = newHighlightedKeys } function updateKeyHighlighting() { if (!visible) return; if (trainingWidget.isCorrect) { if (trainingWidget.nextChar !== "") { highlightKey(trainingWidget.nextChar) } else { highlightKey(preferences.nextLineWithSpace? Qt.Key_Space: Qt.Key_Return) } } else { highlightKey(Qt.Key_Backspace) } } keyboardLayout: screen.keyboardLayout anchors { fill: parent leftMargin: 30 rightMargin: 30 topMargin: 10 bottomMargin: 10 } onKeyboardUpdate: { setLessonKeys() highlightedKeys = [] updateKeyHighlighting() } } KeyboardUnavailableNotice { id: keyboardUnavailableNotice visible: !screen.keyboardLayout.isValid } } } Item { id: trainingOverlayContainer anchors.fill: parent } TrainingScreenMenuOverlay { id: menuOverlay blurSource: screenContent anchors.fill: parent onClosed: trainingWidget.forceActiveFocus() onRestartRequested: screen.restartRequested() onAbortRequested: screen.abortRequested() } } diff --git a/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml b/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml index a3a07da..0732814 100644 --- a/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml +++ b/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml @@ -1,182 +1,182 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import ktouch 1.0 Loader { id: loader property Item blurSource; signal closed() signal restartRequested() signal abortRequested() active: false function show() { loader.active = true } function hide() { item.opacity = 0 } sourceComponent: Component { FocusScope { id: item anchors.fill: parent opacity: loader.status == Loader.Ready? 1: 0 Component.onCompleted: resumeButton.forceActiveFocus() Behavior on opacity { SequentialAnimation { NumberAnimation { duration: 300 easing.type: Easing.InOutQuad } ScriptAction { script: { if (opacity == 0) { loader.active = false; closed() } } } } } ShaderEffectSource { id: effectSource sourceItem: blurSource anchors.fill: parent hideSource: false } HueSaturation { id: desaturatedBackground source: effectSource anchors.fill: parent lightness: -0.3 saturation: -0.5 visible: false } FastBlur { anchors.fill: parent source: desaturatedBackground radius: 50 } Rectangle { anchors.fill: parent color: "#55000000" } /* swallow all mouse events */ MouseArea { anchors.fill: parent enabled: false hoverEnabled: enabled } GroupBox { id: groupBox anchors.centerIn: parent width: column.width + 30 height: column.height + 30 Column { id: column focus: true anchors.centerIn: parent spacing: 15 width: Math.max(resumeButton.implicitWidth, restartButton.implicitWidth, returnButton.implicitWidth) Button { id: resumeButton iconName: "go-next-view" text: i18n("Resume Training") width: parent.width onClicked: hide() KeyNavigation.backtab: returnButton KeyNavigation.tab: restartButton KeyNavigation.down: restartButton } Button { id: restartButton iconName: "view-refresh" text: i18n("Restart Lesson") width: parent.width onClicked: { restartRequested() hide() } KeyNavigation.backtab: resumeButton KeyNavigation.tab: returnButton KeyNavigation.up: resumeButton KeyNavigation.down: returnButton } Button { id: returnButton iconName: "go-home" text: i18n("Return to Home Screen") width: parent.width onClicked: { abortRequested() hide() } KeyNavigation.backtab: restartButton KeyNavigation.tab: resumeButton KeyNavigation.up: restartButton } Keys.onDownPressed: { if (resumeButton.focus) restartButton.focus = true; else if (restartButton.focus) returnButton.focus = true; } Keys.onUpPressed: { if (restartButton.focus) resumeButton.focus = true; else if (returnButton.focus) restartButton.focus = true; } Keys.onEscapePressed: { hide() } } } } } } diff --git a/src/qml/trainingscreen/TrainingScreenToolbar.qml b/src/qml/trainingscreen/TrainingScreenToolbar.qml index 265d7ee..95340ca 100644 --- a/src/qml/trainingscreen/TrainingScreenToolbar.qml +++ b/src/qml/trainingscreen/TrainingScreenToolbar.qml @@ -1,81 +1,81 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" Item { id: item property TrainingStats stats property bool trainingStarted: false property bool trainingFinished: true property Item menuOverlayItem function setMessage() { if (!stats.timeIsRunning && !trainingFinished) { var msg = trainingStarted? i18n("Training session started. Time begins running with the first keystroke."): i18n("Training session interrupted. Time begins running again with next keystroke.") messageBox.showMessage(msg, "media-playback-pause") } else { messageBox.clearMessage() } } function reset() { messageBox.clearMessageImmediately() } onTrainingStartedChanged: setMessage() onTrainingFinishedChanged: setMessage() Connections { target: stats onTimeIsRunningChanged: setMessage() } Row { anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: 30 rightMargin: 30 verticalCenterOffset: -1 } spacing: 3 height: menuButton.height ToolButton { id: menuButton iconName: "go-home" onClicked: item.menuOverlayItem.show() } MessageBox { id: messageBox } } } diff --git a/src/qml/trainingscreen/TrainingWidget.qml b/src/qml/trainingscreen/TrainingWidget.qml index 4852549..bc5b56e 100644 --- a/src/qml/trainingscreen/TrainingWidget.qml +++ b/src/qml/trainingscreen/TrainingWidget.qml @@ -1,246 +1,246 @@ /* * Copyright 2013 Sebastian Gottfried * * 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, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtQuick.Controls 1.3 import ktouch 1.0 import "../keyboard" FocusScope { id: trainingWidget property Lesson lesson property KeyboardLayout keyboardLayout property TrainingStats trainingStats property Item overlayContainer property alias nextChar: trainingLine.nextCharacter property alias isCorrect: trainingLine.isCorrect property int position: -1 signal finished signal keyPressed(variant event) signal keyReleased(variant event) function reset() { stats.reset() lessonPainter.reset() sheetFlick.scrollToCurrentLine() trainingLine.active = true } function forceActiveFocus() { trainingLine.forceActiveFocus() } Timer { id: stopTimer interval: 5000 onTriggered: { stats.stopTraining() } } MouseArea { anchors.fill: parent onClicked: trainingLine.forceActiveFocus() } ScrollView { anchors.fill: parent Flickable { id: sheetFlick anchors.fill: parent contentWidth: parent.width contentHeight: sheet.height + 60 clip: true flickableDirection: Flickable.VerticalFlick function currentLineY() { var cursorRect = lessonPainter.cursorRectangle var y = cursorRect.y + sheet.y + (cursorRect.height / 2) return Math.max(Math.min((y - (height / 2)), contentHeight - height), 0) } function scrollToCurrentLine() { scrollAnimation.to = currentLineY() scrollAnimation.start() } onHeightChanged: { contentY = currentLineY() } NumberAnimation { target: sheetFlick id: scrollAnimation duration: 150 property: "contentY" } Rectangle { id: sheet color: "#fff" x: 30 y: 30 width: trainingWidget.width - 60 height: lessonPainter.height border { width: 1 color: "#000" } LessonPainter { id: lessonPainter anchors.centerIn: sheet lesson: trainingWidget.lesson maximumWidth: parent.width trainingLineCore: trainingLine onDone: { trainingLine.active = false trainingWidget.finished(); stats.stopTraining(); } TrainingLineCore { id: trainingLine anchors.fill: parent focus: true trainingStats: stats cursorItem: cursor onActiveFocusChanged: { if (!trainingLine.activeFocus) { trainingStats.stopTraining() } } Keys.onPressed: { if (!trainingLine.active) return cursorAnimation.restart() trainingStats.startTraining() stopTimer.restart() if (!event.isAutoRepeat) { trainingWidget.keyPressed(event) } } Keys.onReleased: { if (!trainingLine.active) return if (!event.isAutoRepeat) { trainingWidget.keyReleased(event) } } KeyNavigation.backtab: trainingLine KeyNavigation.tab: trainingLine } Rectangle { id: cursor color: "#000" x: Math.floor(lessonPainter.cursorRectangle.x) y: lessonPainter.cursorRectangle.y width: lessonPainter.cursorRectangle.width height: lessonPainter.cursorRectangle.height onYChanged: sheetFlick.scrollToCurrentLine() SequentialAnimation { id: cursorAnimation running: trainingLine.active && trainingLine.activeFocus && Qt.application.active loops: Animation.Infinite PropertyAction { target: cursor property: "opacity" value: 1 } PauseAnimation { duration: 500 } PropertyAction { target: cursor property: "opacity" value: 0 } PauseAnimation { duration: 500 } } } } } } } KeyItem { id: hintKey parent: trainingWidget.overlayContainer anchors.horizontalCenter: parent.horizontalCenter y: Math.max(height, Math.min(parent.height - 2 * height, sheetFlick.mapToItem(parent, 0, cursor.y + 3 * cursor.height - sheetFlick.contentY).y)) property real horizontalScaleFactor: 1 property real verticalScaleFactor: 1 property Key defaultKey: Key {} property KeyboardLayout defaultKeyboardLayout: KeyboardLayout {} key: { var specialKeyType switch (trainingLine.hintKey) { case Qt.Key_Return: specialKeyType = SpecialKey.Return break case Qt.Key_Backspace: specialKeyType = SpecialKey.Backspace break case Qt.Key_Space: specialKeyType = SpecialKey.Space break default: specialKeyType = -1 } for (var i = 0; i < keyboardLayout.keyCount; i++) { var key = keyboardLayout.key(i) if (key.keyType() === "specialKey" && key.type === specialKeyType) { return key; } } return defaultKey } opacity: trainingLine.hintKey !== -1? 1: 0 isHighlighted: opacity == 1 keyboardLayout: screen.keyboardLayout || defaultKeyboardLayout Behavior on opacity { NumberAnimation { duration: 150 } } } }