diff --git a/CMakeLists.txt b/CMakeLists.txt index f17a4b6..fae1c68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,64 +1,64 @@ cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(minuet) set(CMAKE_AUTOMOC ON) # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "17") -set (KDE_APPLICATIONS_VERSION_MINOR "03") -set (KDE_APPLICATIONS_VERSION_MICRO "90") +set (KDE_APPLICATIONS_VERSION_MINOR "04") +set (KDE_APPLICATIONS_VERSION_MICRO "00") #set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") -set (KDE_APPLICATIONS_VERSION "0.3.80") +set (KDE_APPLICATIONS_VERSION "0.4.0") set(ECM_MIN_VERSION "5.15.0") set(QT_MIN_VERSION "5.7.0") find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMInstallIcons) include(ECMAddAppIcon) IF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") include(KDEInstallDirs) include(KDECMakeSettings) ENDIF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") include(FeatureSummary) ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX MINUET VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/app/minuet_version.h" ) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui Qml Quick QuickControls2 Svg ) IF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons I18n Crash DocTools ) ENDIF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include_directories(${minuet_SOURCE_DIR}/src/ ${minuet_BINARY_DIR}/src/) add_subdirectory(src) add_subdirectory(data) IF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") add_subdirectory(doc) ENDIF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") IF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") install(FILES org.kde.minuet.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) ENDIF(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") diff --git a/src/app/qml/ExerciseView.qml b/src/app/qml/ExerciseView.qml index c13a8dc..b4755ae 100644 --- a/src/app/qml/ExerciseView.qml +++ b/src/app/qml/ExerciseView.qml @@ -1,477 +1,479 @@ /**************************************************************************** ** ** Copyright (C) 2016 by Sandro S. Andrade ** ** 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) version 3 or any later version ** accepted by the membership of KDE e.V. (or its successor approved ** by the membership of KDE e.V.), which shall act as a proxy ** defined in Section 14 of version 3 of the license. ** ** 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.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 import QtQuick.Window 2.0 Item { id: exerciseView visible: currentExercise != undefined property var currentExercise QtObject { id: internal property int currentAnswer property var colors: ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f", "#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928"] property Item rightAnswerRectangle property variant userAnswers: [] property bool answersAreRight property bool giveUp property bool isTest: false property int correctAnswers: 0 property int currentExercise: 0 property int maximumExercises: 10 onCurrentAnswerChanged: { for (var i = 0; i < yourAnswersParent.children.length; ++i) yourAnswersParent.children[i].destroy() yourAnswersParent.children = "" for (var i = 0; i < currentAnswer; ++i) answerOption.createObject(yourAnswersParent, {"model": userAnswers[i].model, "index": userAnswers[i].index, "position": i, "color": userAnswers[i].color, "border.width": 2}) } } onCurrentExerciseChanged: { clearUserAnswers() for (var i = 0; i < answerGrid.children.length; ++i) answerGrid.children[i].destroy() answerGrid.children = "" if (currentExercise != undefined) { var currentExerciseOptions = currentExercise["options"]; if (currentExerciseOptions != undefined) { var length = currentExerciseOptions.length for (var i = 0; i < length; ++i) answerOption.createObject(answerGrid, {"model": currentExerciseOptions[i], "index": i, "color": internal.colors[i%24]}) } sheetMusicView.spaced = (currentExercise["playMode"] == "chord") ? false:true messageText.text = i18n("Click 'New Question' to start!") exerciseView.state = "waitingForNewQuestion" } } function clearUserAnswers() { pianoView.clearAllMarks() sheetMusicView.clearAllMarks() for (var i = 0; i < yourAnswersParent.children.length; ++i) yourAnswersParent.children[i].destroy() yourAnswersParent.children = "" internal.currentAnswer = 0 internal.userAnswers = [] } function checkAnswers() { var rightAnswers = core.exerciseController.selectedExerciseOptions internal.answersAreRight = true for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { if (internal.userAnswers[i].name != rightAnswers[i].name) { yourAnswersParent.children[i].border.color = "red" internal.answersAreRight = false } else { yourAnswersParent.children[i].border.color = "green" if (internal.isTest) internal.correctAnswers++ } } messageText.text = (internal.giveUp) ? i18n("Here is the answer") : (internal.answersAreRight) ? i18n("Congratulations, you answered correctly!"):i18n("Oops, not this time! Try again!") if (internal.currentExercise == internal.maximumExercises) { messageText.text = i18n("You answered correctly %1%", internal.correctAnswers * 100 / internal.maximumExercises) resetTest() } if (currentExercise.numberOfSelectedOptions == 1) highlightRightAnswer() else exerciseView.state = "waitingForNewQuestion" internal.giveUp = false } function highlightRightAnswer() { var chosenExercises = core.exerciseController.selectedExerciseOptions for (var i = 0; i < answerGrid.children.length; ++i) { if (answerGrid.children[i].model.name != chosenExercises[0].name) { answerGrid.children[i].opacity = 0.25 } else { internal.rightAnswerRectangle = answerGrid.children[i] answerGrid.children[i].opacity = 1 } } var array = [core.exerciseController.chosenRootNote()] internal.rightAnswerRectangle.model.sequence.split(' ').forEach(function(note) { pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.rightAnswerRectangle.color) array.push(core.exerciseController.chosenRootNote() + parseInt(note)) }) sheetMusicView.model = array animation.start() } function resetTest() { internal.isTest = false internal.correctAnswers = 0 internal.currentExercise = 0 } function nextTestExercise() { for (var i = 0; i < answerGrid.children.length; ++i) answerGrid.children[i].opacity = 1 pianoView.clearAllMarks() sheetMusicView.clearAllMarks() clearUserAnswers() generateNewQuestion(true) core.soundController.play() } function generateNewQuestion () { clearUserAnswers() if (internal.isTest) messageText.text = i18n("Question %1 out of %2", internal.currentExercise + 1, internal.maximumExercises) else messageText.text = "" core.exerciseController.randomlySelectExerciseOptions() var chosenExercises = core.exerciseController.selectedExerciseOptions core.soundController.prepareFromExerciseOptions(chosenExercises) if (currentExercise["playMode"] != "rhythm") { pianoView.noteMark(0, core.exerciseController.chosenRootNote(), 0, "white") + pianoView.scrollToNote(core.exerciseController.chosenRootNote()) sheetMusicView.model = [core.exerciseController.chosenRootNote()] sheetMusicView.clef.type = (core.exerciseController.chosenRootNote() >= 60) ? 0:1 } exerciseView.state = "waitingForAnswer" if (internal.isTest) internal.currentExercise++ } ColumnLayout { anchors.fill: parent spacing: Screen.width >= 1024 ? 20:10 Text { id: userMessage Layout.preferredWidth: parent.width horizontalAlignment: Text.AlignHCenter font.pointSize: Screen.width >= 1024 ? 18:14 anchors.horizontalCenter: parent.horizontalCenter wrapMode: Text.WordWrap text: (currentExercise != undefined) ? i18nc("technical term, do you have a musician friend?", currentExercise["userMessage"]):"" } Text { id: messageText font.pointSize: Screen.width >= 1024 ? 18:14 Layout.preferredWidth: parent.width horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 10 Button { id: newPlayQuestionButton width: 120; height: 40 text: (exerciseView.state == "waitingForNewQuestion") ? i18n("New Question"):i18n("Play Question") enabled: !animation.running onClicked: { if (exerciseView.state == "waitingForNewQuestion") { generateNewQuestion() } core.soundController.play() } } Button { id: giveUpButton width: 120; height: 40 text: i18n("Give Up") enabled: exerciseView.state == "waitingForAnswer" && !animation.running onClicked: { if (internal.isTest) internal.correctAnswers-- internal.giveUp = true var rightAnswers = core.exerciseController.selectedExerciseOptions internal.userAnswers = [] for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { for (var j = 0; j < answerGrid.children.length; ++j) { if (answerGrid.children[j].model.name == rightAnswers[i].name) { internal.userAnswers.push({"name": rightAnswers[i].name, "model": answerGrid.children[j].model, "index": j, "color": internal.colors[j]}) break } } } internal.currentAnswer = currentExercise.numberOfSelectedOptions checkAnswers() } } Button { id: testButton width: 120; height: 40 text: internal.isTest ? i18n("Stop Test") : i18n("Start Test") enabled: true onClicked: { if (!internal.isTest) { resetTest() internal.isTest = true generateNewQuestion() if (internal.isTest) core.soundController.play() } else { resetTest() exerciseView.state = "waitingForNewQuestion" messageText.text = i18n("Click 'New Question' to start") } } } } GroupBox { id: availableAnswers title: i18n("Available Answers") anchors.horizontalCenter: parent.horizontalCenter Layout.preferredWidth: parent.width Layout.fillHeight: true Flickable { anchors.fill: parent contentHeight: answerGrid.height clip: true Grid { id: answerGrid anchors.centerIn: parent spacing: 10 columns: Math.max(1, parent.width / (((currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 120:119) + spacing)) Component { id: answerOption Rectangle { id: answerRectangle property var model property int index property int position width: (currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 120:119 height: (currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 40:59 Text { id: option property string originalText: model.name visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" text: i18nc("technical term, do you have a musician friend?", model.name) width: parent.width - 4 anchors.centerIn: parent horizontalAlignment: Qt.AlignHCenter color: "black" wrapMode: Text.Wrap } Image { id: rhythmImage anchors.centerIn: parent visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" source: (currentExercise != undefined && currentExercise["playMode"] == "rhythm") ? "exercise-images/" + model.name + ".png":"" fillMode: Image.Pad } MouseArea { anchors.fill: parent onClicked: { if (exerciseView.state == "waitingForAnswer" && !animation.running) { onExited() internal.userAnswers.push({"name": option.originalText, "model": answerRectangle.model, "index": answerRectangle.index, "color": answerRectangle.color}) internal.currentAnswer++ if (internal.currentAnswer == currentExercise.numberOfSelectedOptions) checkAnswers() } } hoverEnabled: Qt.platform.os != "android" && !animation.running onEntered: { answerRectangle.color = Qt.darker(answerRectangle.color, 1.1) if (currentExercise["playMode"] != "rhythm" && exerciseView.state == "waitingForAnswer") { if (parent.parent == answerGrid) { var array = [core.exerciseController.chosenRootNote()] model.sequence.split(' ').forEach(function(note) { array.push(core.exerciseController.chosenRootNote() + parseInt(note)) pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.colors[answerRectangle.index]) }) sheetMusicView.model = array } } else { var rightAnswers = core.exerciseController.selectedExerciseOptions if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { parent.border.color = "green" for (var i = 0; i < answerGrid.children.length; ++i) { if (answerGrid.children[i].model.name == rightAnswers[position].name) { parent.color = answerGrid.children[i].color break } } rhythmImage.source = "exercise-images/" + rightAnswers[position].name + ".png" } } } onExited: { answerRectangle.color = internal.colors[answerRectangle.index] if (currentExercise["playMode"] != "rhythm") { if (parent.parent == answerGrid) { if (!animation.running) model.sequence.split(' ').forEach(function(note) { pianoView.noteUnmark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0) }) sheetMusicView.model = [core.exerciseController.chosenRootNote()] } } else { var rightAnswers = core.exerciseController.selectedExerciseOptions if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { parent.border.color = "red" parent.color = internal.userAnswers[position].color rhythmImage.source = "exercise-images/" + internal.userAnswers[position].name + ".png" } } } } } } } ScrollIndicator.vertical: ScrollIndicator { active: true } } } GroupBox { id: yourAnswers title: i18n("Your Answer(s)") Layout.preferredWidth: parent.width anchors.horizontalCenter: parent.horizontalCenter contentHeight: ((currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 40:59) Flickable { width: (currentExercise != undefined) ? Math.min(parent.width, internal.currentAnswer*130):0; height: parent.height anchors.horizontalCenter: parent.horizontalCenter contentWidth: (currentExercise != undefined) ? internal.currentAnswer*130:0 boundsBehavior: Flickable.StopAtBounds clip: true Row { id: yourAnswersParent anchors.centerIn: parent spacing: Screen.width >= 1024 ? 10:5 } ScrollIndicator.horizontal: ScrollIndicator { active: true } } } Button { id: backspaceButton text: i18n("Backspace") anchors.horizontalCenter: parent.horizontalCenter visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" enabled: internal.currentAnswer > 0 && internal.currentAnswer < currentExercise.numberOfSelectedOptions onClicked: { internal.userAnswers.pop() internal.currentAnswer-- } } - Flickable { - - id: pianoViewFlickable - Layout.preferredWidth: parent.width/2 - 10 - anchors { left: parent.left; bottom: parent.bottom; bottomMargin: 10 } + Row { + anchors.horizontalCenter: parent.horizontalCenter + Layout.preferredWidth: parent.width + spacing: (parent.width/2 - sheetMusicView.width)/2 PianoView { id: pianoView + width: parent.width/2 - 10 + visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" + ScrollIndicator.horizontal: ScrollIndicator { active: true } + } + SheetMusicView { + id: sheetMusicView + + height: pianoView.height + anchors { bottom: parent.bottom; bottomMargin: 15 } visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" } - ScrollIndicator.horizontal: ScrollIndicator { active: true } - } - - SheetMusicView { - id: sheetMusicView - anchors { right: parent.right; verticalCenter: pianoViewFlickable.verticalCenter } - visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" } } states: [ State { name: "waitingForNewQuestion" }, State { name: "waitingForAnswer" StateChangeScript { script: { for (var i = 0; i < answerGrid.children.length; ++i) { answerGrid.children[i].opacity = 1 } } } } ] ParallelAnimation { id: animation loops: 2 SequentialAnimation { PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: -45; duration: 200 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 45; duration: 200 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 0; duration: 200 } } SequentialAnimation { PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.2; duration: 300 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.0; duration: 300 } } onStopped: { exerciseView.state = internal.isTest ? "waitingForAnswer" : "waitingForNewQuestion" if (internal.isTest) { nextTestExercise() if (internal.currentExercise == internal.maximumExercises+1) internal.isTest = false } } } Connections { target: core.exerciseController onSelectedExerciseOptionsChanged: pianoView.clearAllMarks() } Connections { target: core.exerciseController onSelectedExerciseOptionsChanged: sheetMusicView.clearAllMarks() } } diff --git a/src/app/qml/PianoView/PianoView.qml b/src/app/qml/PianoView/PianoView.qml index c843a85..90b3218 100644 --- a/src/app/qml/PianoView/PianoView.qml +++ b/src/app/qml/PianoView/PianoView.qml @@ -1,144 +1,147 @@ /**************************************************************************** ** ** Copyright (C) 2016 by Sandro S. Andrade ** ** 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) version 3 or any later version ** accepted by the membership of KDE e.V. (or its successor approved ** by the membership of KDE e.V.), which shall act as a proxy ** defined in Section 14 of version 3 of the license. ** ** 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.7 import QtQuick.Controls 2.0 Flickable { id: flickable - width: Math.min(parent.width, piano.width); height: keyHeight + 30 - anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom }//; margins: exerciseContainer.anchors.margins } + width: Math.min(parent.width, piano.width) + height: keyHeight + 30 contentWidth: piano.width boundsBehavior: Flickable.OvershootBounds clip: true property int keyWidth: Math.max(16, (parent.width - 80) / 52) property int keyHeight: 3.4 * keyWidth function noteOn(chan, pitch, vel) { if (vel > 0) highlightKey(pitch, "#778692") else noteOff(chan, pitch, vel) } function noteOff(chan, pitch, vel) { highlightKey(pitch, ([1,3,6,8,10].indexOf(pitch % 12) > -1) ? "black":"white") } function noteMark(chan, pitch, vel, color) { noteMark.createObject(itemForPitch(pitch), { color: color }) } function noteUnmark(chan, pitch, vel, color) { if(itemForPitch(pitch)!= undefined){ var item = itemForPitch(pitch).children[0] if (item != undefined) item.destroy() } } function clearAllMarks() { for (var index = 21; index <= 108; ++index) { noteOff(0, index, 0) var markItem = itemForPitch(index).children[0] if (markItem != undefined) markItem.destroy() } } + function scrollToNote(pitch) { + flickable.contentX = flickable.contentWidth/88*(pitch-21) - flickable.width/2 + } function highlightKey(pitch, color) { itemForPitch(pitch).color = color } function itemForPitch(pitch) { var noteItem if (pitch < 24) { noteItem = keyboard.children[pitch-21] } else if (pitch == 108) { noteItem = whiteKeyC } else { var note = (pitch - 24) % 12 var octave = (pitch - 24 - note) / 12 noteItem = keyboard.children[3+octave].children[note] } return noteItem } Rectangle { id: piano width: 3 * keyWidth + 7 * (7 * keyWidth); height: parent.height anchors.horizontalCenter: parent.horizontalCenter radius: 5 color: "#141414" Row { id: octaveNumber width: parent.width; height: 18 anchors.left: parent.left anchors.leftMargin: 2 * keyWidth Repeater { model: 7 Label { text: i18nc("technical term, do you have a musician friend?", "Octave") + " " + (1 + modelData) width: 7 * keyWidth color: "white" height: parent.height } } } Item { id: keyboard anchors { top: octaveNumber.bottom; horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: 5 } width: 3 * keyWidth + 7 * (7 * keyWidth); height: keyHeight - octaveNumber.height WhiteKey { id: whiteKeyA } BlackKey { anchor: whiteKeyA } WhiteKey { id: whiteKeyB; anchor: whiteKeyA } Octave { id: octave1; initialAnchor: whiteKeyB } Octave { id: octave2; initialAnchor: octave1 } Octave { id: octave3; initialAnchor: octave2 } Octave { id: octave4; initialAnchor: octave3 } Octave { id: octave5; initialAnchor: octave4 } Octave { id: octave6; initialAnchor: octave5 } Octave { id: octave7; initialAnchor: octave6 } WhiteKey { id: whiteKeyC; anchor: octave7 } Rectangle { width: 3 * keyWidth + 7 * (7 * keyWidth); height: 2 anchors { left: whiteKeyA.left; bottom: whiteKeyA.top } color: "#A40E09" } } Component { id: noteMark Rectangle { width: keyWidth - 4; height: keyWidth - 4 radius: (keyWidth - 4) / 2 border.color: "black" anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: 2 } } } } ScrollIndicator.horizontal: ScrollIndicator { active: true } } diff --git a/src/app/qml/SheetMusicView/Note.qml b/src/app/qml/SheetMusicView/Note.qml index 4b0fcd4..c4091f5 100644 --- a/src/app/qml/SheetMusicView/Note.qml +++ b/src/app/qml/SheetMusicView/Note.qml @@ -1,101 +1,101 @@ import QtQuick 2.7 BravuraText { id: note property int number; property int octave; property int midiKey; property int rhythm: 4; property int accident: 0 // [-2 double flat, -1 flat, 1 sharp, 2 double sharp] property bool spaced: true objectName: "symbol" onNumberChanged: { if (!internal.updating) { internal.updating = true; midiKey = 24 + (12*(octave-1))+number+accident; internal.updating = false } } onOctaveChanged: { if (!internal.updating) { internal.updating = true; midiKey = 24 + (12*(octave-1))+number+accident; internal.updating = false } } onAccidentChanged: { if (!internal.updating) { internal.updating = true; midiKey = 24 + (12*(octave-1))+number+accident; internal.updating = false } } onMidiKeyChanged: { if (!internal.updating) { internal.updating = true; number = (midiKey % 12); octave = (((midiKey-24) - number) / 12) + 1; accident = internal.accidentMap[number][1]; internal.updating = false } } QtObject { id: internal property bool updating: false; property var accidentMap: [ // [vertical offset, accident] [0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [3, 0], [3, 1], [4, 0], [4, 1], [5, 0], [5, 1], [6, 0], ] function itemIndex(item) { if (item.parent == null) return -1 var siblings = item.parent.children for (var i = siblings.length - 1; i >=0 ; i--) if (siblings[i] == item) return i return -1 // will never happen } function previousItem(item) { if (item.parent == null) return null var siblings = item.parent.children for (var i = itemIndex(item) - 1; i >=0 ; i--) if (siblings[i].objectName == "symbol") return item.parent.children[i] return null } property var rhythmTable: { "1": "\ue1d2", "2": "\ue1d3", "4": "\ue1d5", "8": "\ue1d7", "16": "\ue1d9", "32": "\ue1db", "64": "\ue1dd" } } anchors { bottom: parent.children[0].bottom; bottomMargin: { (parent.clef.type == 0) ? ((internal.accidentMap[number][0])*5)+(octave-4)*35 : -10+((internal.accidentMap[number][0])*5)+(octave-2)*35; } left: spaced ? internal.previousItem(note).right:internal.previousItem(note).left; leftMargin: spaced ? parent.spacing:0 } - text: internal.rhythmTable[rhythm] + ((accident == -1) ? "\ue260":(accident == 1) ? "\ue262":(accident == -2) ? "\ue264":(accident == 2) ? "\ue263":"") + text: ((accident == -1) ? "\ue260":(accident == 1) ? "\ue262":(accident == -2) ? "\ue264":(accident == 2) ? "\ue263":" ") + internal.rhythmTable[rhythm] font.pixelSize: 35 } diff --git a/src/app/qml/SheetMusicView/Score.qml b/src/app/qml/SheetMusicView/Score.qml index 46299ee..551d0c2 100644 --- a/src/app/qml/SheetMusicView/Score.qml +++ b/src/app/qml/SheetMusicView/Score.qml @@ -1,16 +1,16 @@ import QtQuick 2.7 Item { property int spacing property Clef clef objectName: "score" Row { spacing: 0 Repeater { - model: 25 + model: 20 BravuraText { text: "\ue014" } } } } diff --git a/src/app/qml/SheetMusicView/SheetMusicView.qml b/src/app/qml/SheetMusicView/SheetMusicView.qml index d25814c..a89a88a 100644 --- a/src/app/qml/SheetMusicView/SheetMusicView.qml +++ b/src/app/qml/SheetMusicView/SheetMusicView.qml @@ -1,22 +1,21 @@ import QtQuick 2.7 Score { property alias model: sequence.model property alias clef: clef property alias spaced: sequence.spaced function clearAllMarks() { clef.type = 0 sequence.model = [] } - width: childrenRect.width; height: childrenRect.height + width: childrenRect.width FontLoader { id: bravura; source: "Bravura.otf" } antialiasing: true spacing: 10 -// clef: clef Clef { id: clef; type: 1 } Sequence { id: sequence } }