diff --git a/src/qml/common/DetailedRadioButton.qml b/src/qml/common/DetailedRadioButton.qml index 041d9dd..ef40bdb 100644 --- a/src/qml/common/DetailedRadioButton.qml +++ b/src/qml/common/DetailedRadioButton.qml @@ -1,80 +1,80 @@ /* * Copyright 2014 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 1.3 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.1 ColumnLayout { id: root property alias checked: radioButton.checked property alias enabled: radioButton.enabled property alias label: label.text property alias hint: hint.text spacing: 10 Row { id: radioButtonRow Layout.fillWidth: true spacing: Math.round(label.height / 4) RadioButton { id: radioButton anchors.verticalCenter: parent.verticalCenter } Label { id: label anchors.verticalCenter: parent.verticalCenter /* * The text wrapping of the label doesn't work if it is invible * (wrapped at every character), hence the following hack. */ width: visible? parent.width - radioButton.width - parent.spacing: 0 wrapMode: Text.Wrap opacity: radioButton.opacity MouseArea { anchors.fill: parent enabled: radioButton.enabled onClicked: { radioButton.checked = true } } } } Row { Layout.fillWidth: true spacing: radioButtonRow.spacing Item { width: radioButton.width height: hint.height } Label { id: hint font.italic: true width: visible? parent.width - radioButton.width - parent.spacing: 0 wrapMode: Text.Wrap opacity: radioButton.opacity } } } diff --git a/src/qml/common/IconButton.qml b/src/qml/common/IconButton.qml index e9292f3..2d38d77 100644 --- a/src/qml/common/IconButton.qml +++ b/src/qml/common/IconButton.qml @@ -1,68 +1,68 @@ /* * 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.Controls 2.0 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 } background: Rectangle { id: bg - color: button.activeFocus? buttonColorScheme.focusDecoration: buttonColorScheme.normalBackground + color: button.activeFocus? buttonColorScheme.focusDecoration: 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 } } } } } diff --git a/src/qml/common/IconLabel.qml b/src/qml/common/IconLabel.qml index 630acd3..76c0ac9 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.Controls 2.0 +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) + 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 + 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/IconToolButton.qml b/src/qml/common/IconToolButton.qml index ef8a4ad..b920198 100644 --- a/src/qml/common/IconToolButton.qml +++ b/src/qml/common/IconToolButton.qml @@ -1,45 +1,46 @@ /* * 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.Controls 2.0 +import QtQuick.Controls 2.2 ToolButton { id: button property alias color: content.color property alias icon: content.icon property color backgroundColor: "#ffffff" padding: 0 hoverEnabled: true contentItem: IconLabel { id: content text: button.text + elide: "ElideNone" } background: Rectangle { opacity: (checked? 0.6: 0) + (hovered? 0.3: 0) color: button.backgroundColor Behavior on opacity { NumberAnimation { duration: 150 } } } } diff --git a/src/qml/common/InformationTable.qml b/src/qml/common/InformationTable.qml index 6538a78..e321e0c 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.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 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/homescreen/ProfileComboBox.qml b/src/qml/homescreen/ProfileComboBox.qml index 2594a86..9713991 100644 --- a/src/qml/homescreen/ProfileComboBox.qml +++ b/src/qml/homescreen/ProfileComboBox.qml @@ -1,175 +1,176 @@ /* * 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.Controls 2.2 import QtQuick.Controls.impl 2.2 import QtGraphicalEffects 1.0 import ktouch 1.0 import "../common" ComboBox { id: root property Profile profile property KColorScheme colorScheme property color manageProfileButtonBgColor KColorScheme { id: listColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } background: Rectangle { opacity: (popup.visible? 0.6: 0.0) + (hovered || visualFocus? 0.3: 0.0) color: colorScheme.normalBackground Behavior on opacity { NumberAnimation { duration: 150 } } } hoverEnabled: true contentItem: IconLabel { icon: "user-identity" text: profile !== null? profile.name: "" color: colorScheme.normalText } indicator: MonochromeIcon { color: colorScheme.normalText icon: "arrow-down-double" x: root.width - width - root.contentItem.padding y: root.topPadding + (root.availableHeight - height) / 2 rotation: root.popup.opacity * -180 } model: profileDataAccess.profileCount onActivated: { profile = index < profileDataAccess.profileCount? profileDataAccess.profile(index): null } onProfileChanged: { for (var i = 0; i < profileDataAccess.profileCount; i++) { if (profile === profileDataAccess.profile(i)) { if (i != currentIndex) { currentIndex = i; } break; } } } delegate: ListItem { width: root.width text: index < profileDataAccess.profileCount? profileDataAccess.profile(index).name: "" highlighted: root.currentIndex === index } popup: Popup { y: root.height width: root.width implicitHeight: contentItem.implicitHeight padding: 0 opacity: 0 enter: Transition { NumberAnimation { property: "opacity" to: 1.0 duration: 150 } } exit: Transition { NumberAnimation { property: "opacity" to: 0.0 duration: 150 } } contentItem: ListView { id: list clip: true implicitHeight: contentHeight width: parent.width model: root.popup.visible ? root.delegateModel : null currentIndex: root.highlightedIndex ScrollIndicator.vertical: ScrollIndicator { } footer: IconButton { id: manageProfileButton width: parent.width color: root.colorScheme.normalText bgColor: root.manageProfileButtonBgColor text: i18n("Manage Profiles") icon: "user-properties" MouseArea { anchors.fill: parent hoverEnabled: false onPressed: { mouse.accepted = true } onClicked: { root.popup.close() mouse.accepted = true manageProfileDialog.open() } } } } background: Rectangle { color: listColorScheme.normalBackground layer.enabled: true layer.effect: DropShadow { samples: 24 horizontalOffset: 0 verticalOffset: 0 } } } PopupDialog { id: manageProfileDialog modal: true focus: true title: i18n("Manage Profiles") closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + padding: 0 contentItem: ProfileSelector { id: profileSelector onProfileChosen: { root.profile = profile manageProfileDialog.close() } } onOpened: { if (profileComboBox.profile) { var index = profileDataAccess.indexOfProfile(profileComboBox.profile) profileSelector.selectProfile(index) } } } } diff --git a/src/qml/homescreen/ProfileDetailsItem.qml b/src/qml/homescreen/ProfileDetailsItem.qml index 891cfc4..14420da 100644 --- a/src/qml/homescreen/ProfileDetailsItem.qml +++ b/src/qml/homescreen/ProfileDetailsItem.qml @@ -1,273 +1,276 @@ /* * 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.Controls 1.3 -import QtQuick.Layouts 1.1 +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: [ - ToolButton { - iconName: "document-edit" + IconToolButton { + icon: "document-edit" text: i18n("Edit") onClicked: root.state = "editor" }, - ToolButton { - iconName: "edit-delete" + 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 height: childrenRect.height 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 - ToolButton { - iconName: "edit-delete" + IconButton { + icon: "edit-delete" text: i18n("Delete") + bgColor: colorScheme.negativeBackground onClicked: root.deletionRequest() } - ToolButton { + 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 c3993bc..72b7190 100644 --- a/src/qml/homescreen/ProfileForm.qml +++ b/src/qml/homescreen/ProfileForm.qml @@ -1,93 +1,93 @@ /* * 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 1.3 -import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 import ktouch 1.0 import "../common" Column { id: root property alias name: nameTextField.text property int skillLevel: 0 property bool skillLevelSelectionEnabled: true property alias showWelcomeLabel: welcomeLabel.visible - property alias doneButtonIconSource: doneBtn.iconSource + 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 width: parent.width text: i18n("Before you start training, please introduce yourself:") } TextField { id: nameTextField width: parent.width placeholderText: i18n("Name") } DetailedRadioButton { id: beginnerRadioButton width: parent.width enabled: root.skillLevelSelectionEnabled label: i18n("I have no or only very little experience in machine typing") hint: i18n("Lessons are unlocked as your typing skills improve over time.") onCheckedChanged: { if (checked) { root.skillLevel = Profile.Beginner advancedRadioButton.checked = false } } } DetailedRadioButton { id: advancedRadioButton width: parent.width enabled: root.skillLevelSelectionEnabled label: i18n("I am an experienced machine typist and want to improve my skills") hint: i18n("All lessons are unlocked immediately.") onCheckedChanged: { if (checked) { root.skillLevel = Profile.Advanced beginnerRadioButton.checked = false } } } - Button { + IconButton { id: doneBtn anchors.horizontalCenter: parent.horizontalCenter text: i18n("Done") enabled: nameTextField.text !== "" && (beginnerRadioButton.checked || advancedRadioButton.checked) - iconName: "dialog-ok" + icon: "dialog-ok" onClicked: done() } } diff --git a/src/qml/homescreen/ProfileSelector.qml b/src/qml/homescreen/ProfileSelector.qml index c263b94..a51509d 100644 --- a/src/qml/homescreen/ProfileSelector.qml +++ b/src/qml/homescreen/ProfileSelector.qml @@ -1,114 +1,121 @@ /* * 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.Controls 1.3 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.1 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 - anchors.bottomMargin: 10 - spacing: 10 + spacing: 0 RowLayout { Layout.fillWidth: true Layout.fillHeight: true - spacing: 10 - Item { + Rectangle { id: listContainer Layout.fillWidth: true Layout.fillHeight: true + color: listColorScheme.normalBackground ScrollView { anchors.fill: parent ListView { id: list anchors.fill: parent 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)) } } } } } } 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)) } } } - Button { + IconButton { id: selectButton - anchors.horizontalCenter: parent.horizontalCenter - iconName: "go-next-view" + 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)) } } }