diff --git a/src/qml/common/Balloon.qml b/src/qml/common/Balloon.qml index 9a5aae0..9e18360 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.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 variant parentPos: root.visualParent? root.visualParent.mapToItem(null, 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 + // bindings don't work for anchor 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/LearningProgressChart.qml b/src/qml/common/LearningProgressChart.qml index ef4e22e..0dd4071 100644 --- a/src/qml/common/LearningProgressChart.qml +++ b/src/qml/common/LearningProgressChart.qml @@ -1,62 +1,109 @@ /* * 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.9 import QtQuick.Layouts 1.3 import org.kde.charts 0.1 as Charts import ktouch 1.0 Charts.LineChart { - id: chart + id: root 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: [ Charts.Dimension { id: accuracyDimension dataColumn: 5 color: "#ffb12d" - minimumValue: chart.minAccuracy(model.minAccuracy) + minimumValue: root.minAccuracy(model.minAccuracy) maximumValue: 1.0 label: i18n("Accuracy") unit: "%" unitFactor: 100 }, Charts.Dimension { id: charactersPerMinuteDimension dataColumn: 6 color: "#38aef4" maximumValue: Math.max(Math.ceil(model.maxCharactersTypedPerMinute / 120) * 120, 120) label: i18n("Characters per Minute") } ] + + onElemEntered: { + learningProgressPointTooltip.visualParent = elem + learningProgressPointTooltip.row = row + learningProgressPointTooltip.open() + } + + onElemExited: { + learningProgressPointTooltip.close() + } + + Balloon { + id: learningProgressPointTooltip + property int row: -1 + + function findLessonTitle(id) { + var course = model.courseFilter + if (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 + } + } + } diff --git a/src/qml/homescreen/LessonSelector.qml b/src/qml/homescreen/LessonSelector.qml index e383301..3cf130e 100644 --- a/src/qml/homescreen/LessonSelector.qml +++ b/src/qml/homescreen/LessonSelector.qml @@ -1,391 +1,391 @@ /* * 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.9 import QtQuick.Layouts 1.3 import ktouch 1.0 import QtGraphicalEffects 1.0 import "../common" FocusScope { 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() } 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 + course: courseItem lesson: root.selectedLesson } LessonEditorDialog { id: lessonEditorDialog profile: root.profile keyboardLayout: root.selectedKeyboardLayout lesson: root.selectedLesson } ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.fillWidth: true Layout.preferredHeight: header.height z: 2 Column { id: header width: parent.width ToolBar { id: toolbar width: parent.width dimFactor: 1.5 RowLayout { anchors.fill: parent anchors.leftMargin: 20 spacing: 5 Label { text: root.course? root.course.title: "" font.bold: true color: toolbar.colorScheme.normalText } IconToolButton { id: toggleCourseDesciptionButton iconName: "help-about" checkable: true color: toolbar.colorScheme.normalText backgroundColor: toolbar.colorScheme.normalBackground Layout.fillHeight: true Layout.preferredWidth: toolbar.height } ToolSeparator { visible: courseItem.editable } IconToolButton { id: newLessonButton iconName: "document-new" text: "Add New Lesson" color: toolbar.colorScheme.normalText backgroundColor: toolbar.colorScheme.normalBackground visible: courseItem.editable Layout.fillHeight: true onClicked: { course.createNewCustomLesson() } } Item { Layout.fillWidth: true } IconToolButton { id: configureButton iconName: "application-menu" color: toolbar.colorScheme.normalText backgroundColor: toolbar.colorScheme.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 iconName: "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/scorescreen/ScoreScreen.qml b/src/qml/scorescreen/ScoreScreen.qml index 914d181..cd08019 100644 --- a/src/qml/scorescreen/ScoreScreen.qml +++ b/src/qml/scorescreen/ScoreScreen.qml @@ -1,467 +1,422 @@ /* * Copyright 2018 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.9 import QtQuick.Layouts 1.3 import ktouch 1.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 } KColorScheme { id: colorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } LearningProgressModel { property bool filterByLesson: learningProgressFilterComboBox.currentIndex == 1 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: 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: 20 anchors.rightMargin: 20 spacing: anchors.leftMargin IconToolButton { id: homeScreenButton anchors.verticalCenter: parent.verticalCenter text: i18n("Return to Home Screen") iconName: "go-home" colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.homeScreenRequested() } Item { Layout.fillHeight: true Layout.fillWidth: true } IconToolButton { id: repeatLessonButton iconName: "view-refresh" text: i18n("Repeat Lesson") colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.lessonRepetionRequested() } IconToolButton { id: nextLessonButton iconName: "go-next-view" text: i18n("Next Lesson") enabled: internal.nextLesson colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.nextLessonRequested(internal.nextLesson) } } } Rectangle { Layout.fillHeight: true Layout.fillWidth: true color: colorScheme.normalBackground 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) Icon { 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 anchors.verticalCenter: parent.verticalCenter text: i18nc("Show a specific type of statistic", "Show") opacity: 0.7 } ComboBox { id: chartTypeComboBox anchors.verticalCenter: parent.verticalCenter model: ListModel { Component.onCompleted: { append({"text": i18n("Progress"), "icon": "office-chart-area"}); append({"text": i18n("Errors"), "icon": "office-chart-bar"}); } } textRole: "text" currentIndex: 0 } Label { id: overLabel anchors.verticalCenter: parent.verticalCenter text: i18nc("Show a statistic over one or more lessons", "Over") opacity: tabGroup.currentItem === learningProgressTab? 0.7: 0 Behavior on opacity { NumberAnimation {duration: 150} } } ComboBox { id: learningProgressFilterComboBox anchors.verticalCenter: parent.verticalCenter model: ListModel { Component.onCompleted: { append({"text": i18n("All Lessons"), "icon": "view-filter"}); append({"text": i18n("This Lessons"), "icon": "view-filter"}); } } textRole: "text" currentIndex: 0 opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Item { Layout.fillWidth: true } Charts.LegendItem { id: accuracyLegend anchors.verticalCenter: parent.verticalCenter opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Charts.LegendItem { id: charactersPerMinuteLegend anchors.verticalCenter: parent.verticalCenter opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } } Item { Layout.fillHeight: true Layout.fillWidth: true StackLayout { anchors.fill: parent id: tabGroup currentIndex: chartTypeComboBox.currentIndex property Item currentItem: currentIndex != -1? children[currentIndex]: null LearningProgressChart { id: learningProgressTab property string title: i18n("Progress") property string iconName: "office-chart-area" anchors.fill: parent model: learningProgressModel backgroundColor: colorScheme.normalBackground - onElemEntered: { - learningProgressPointTooltip.visualParent = elem - learningProgressPointTooltip.row = row - learningProgressPointTooltip.open() - } - - onElemExited: { - learningProgressPointTooltip.close() - } - Component.onCompleted: { accuracyLegend.dimension = learningProgressTab.accuracy charactersPerMinuteLegend.dimension = learningProgressTab.charactersPerMinute } Component.onDestruction: { accuracyLegend.dimension = null charactersPerMinuteLegend.dimension = null } } Item { id: errorsTab property string title: i18n("Errors") property string iconName: "office-chart-bar" Charts.BarChart{ anchors.fill: parent model: errorsModel pitch: 60 textRole: 3 // Qt::ToolTipRole backgroundColor: colorScheme.normalBackground 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() } } } } } } } } }