Changeset View
Standalone View
src/activities/note_names/NoteNames.qml
- This file was added.
1 | /* GCompris - NoteNames.qml | ||||
---|---|---|---|---|---|
2 | * | ||||
3 | * Copyright (C) 2018 Aman Kumar Gupta <gupta2140@gmail.com> | ||||
4 | * | ||||
5 | * Authors: | ||||
6 | * Aman Kumar Gupta <gupta2140@gmail.com> | ||||
7 | * | ||||
8 | * This program is free software; you can redistribute it and/or modify | ||||
9 | * it under the terms of the GNU General Public License as published by | ||||
10 | * the Free Software Foundation; either version 3 of the License, or | ||||
11 | * (at your option) any later version. | ||||
12 | * | ||||
13 | * This program is distributed in the hope that it will be useful, | ||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
16 | * GNU General Public License for more details. | ||||
17 | * | ||||
18 | * You should have received a copy of the GNU General Public License | ||||
19 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
20 | */ | ||||
21 | import QtQuick 2.6 | ||||
22 | import QtQuick.Controls 1.5 | ||||
23 | import GCompris 1.0 | ||||
24 | | ||||
25 | import "../../core" | ||||
26 | import "../piano_composition" | ||||
27 | import "note_names.js" as Activity | ||||
28 | | ||||
29 | ActivityBase { | ||||
30 | id: activity | ||||
31 | | ||||
32 | onStart: focus = true | ||||
33 | onStop: {} | ||||
34 | | ||||
35 | property bool horizontalLayout: width > height | ||||
36 | | ||||
37 | pageComponent: Rectangle { | ||||
38 | id: background | ||||
39 | anchors.fill: parent | ||||
40 | color: "#ABCDEF" | ||||
41 | | ||||
42 | signal start | ||||
43 | signal stop | ||||
44 | | ||||
45 | Component.onCompleted: { | ||||
46 | activity.start.connect(start) | ||||
47 | activity.stop.connect(stop) | ||||
48 | } | ||||
49 | | ||||
50 | Keys.onPressed: { | ||||
51 | if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { | ||||
52 | // If the key pressed matches the note, pass the correct answer as parameter, else pass a wrong answer. | ||||
53 | if(event.key === Qt.Key_1) { | ||||
54 | isCorrectKey('C') | ||||
jjazeix: code should be factorised | |||||
jjazeix: you can use a map of <Qt.Key_*, key> to simplify more | |||||
55 | } | ||||
56 | else if(event.key === Qt.Key_2) { | ||||
57 | isCorrectKey('D') | ||||
58 | } | ||||
jjazeix: why Z? Isn't there a cleaner way to have an error? | |||||
59 | else if(event.key === Qt.Key_3) { | ||||
60 | isCorrectKey('E') | ||||
61 | } | ||||
62 | else if(event.key === Qt.Key_4) { | ||||
63 | isCorrectKey('F') | ||||
64 | } | ||||
65 | else if(event.key === Qt.Key_5) { | ||||
66 | isCorrectKey('G') | ||||
67 | } | ||||
68 | else if(event.key === Qt.Key_6) { | ||||
69 | isCorrectKey('A') | ||||
70 | } | ||||
71 | else if(event.key === Qt.Key_7) { | ||||
72 | isCorrectKey('B') | ||||
73 | } | ||||
74 | else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { | ||||
75 | doubleOctave.currentOctaveNb-- | ||||
76 | } | ||||
77 | else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { | ||||
78 | doubleOctave.currentOctaveNb++ | ||||
79 | } | ||||
80 | } | ||||
81 | } | ||||
82 | | ||||
83 | function isCorrectKey(key) { | ||||
84 | if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) | ||||
85 | Activity.correctAnswer() | ||||
86 | else | ||||
87 | items.displayNoteNameTimer.start() | ||||
88 | } | ||||
89 | | ||||
90 | // Add here the QML items you need to access in javascript | ||||
91 | QtObject { | ||||
92 | id: items | ||||
93 | property Item main: activity.main | ||||
94 | property alias background: background | ||||
95 | property GCSfx audioEffects: activity.audioEffects | ||||
96 | property alias bar: bar | ||||
97 | property alias multipleStaff: multipleStaff | ||||
98 | property alias doubleOctave: doubleOctave | ||||
99 | property alias bonus: bonus | ||||
100 | property alias iAmReady: iAmReady | ||||
101 | property alias messageBox: messageBox | ||||
102 | property alias addNoteTimer: addNoteTimer | ||||
103 | property alias dataset: dataset | ||||
104 | property alias progressBar: progressBar | ||||
105 | property alias introMessage: introMessage | ||||
106 | property bool isTutorialMode: true | ||||
107 | property alias displayNoteNameTimer: displayNoteNameTimer | ||||
108 | } | ||||
109 | | ||||
110 | Loader { | ||||
111 | id: dataset | ||||
112 | asynchronous: false | ||||
113 | source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" | ||||
114 | } | ||||
115 | | ||||
116 | onStart: { Activity.start(items) } | ||||
117 | onStop: { Activity.stop() } | ||||
118 | | ||||
119 | property string clefType: "Treble" | ||||
120 | | ||||
121 | Timer { | ||||
122 | id: displayNoteNameTimer | ||||
123 | interval: 2000 | ||||
124 | onRunningChanged: { | ||||
125 | if(running) { | ||||
126 | multipleStaff.pauseNoteAnimation() | ||||
127 | addNoteTimer.pause() | ||||
128 | messageBox.visible = true | ||||
129 | } | ||||
130 | else { | ||||
131 | messageBox.visible = false | ||||
132 | if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { | ||||
133 | Activity.wrongAnswer() | ||||
134 | addNoteTimer.resume() | ||||
135 | } | ||||
136 | } | ||||
137 | } | ||||
138 | } | ||||
139 | | ||||
140 | Rectangle { | ||||
141 | id: messageBox | ||||
142 | width: label.width + 20 | ||||
143 | height: label.height + 20 | ||||
144 | border.width: 5 | ||||
145 | border.color: "black" | ||||
146 | anchors.centerIn: multipleStaff | ||||
147 | radius: 10 | ||||
148 | z: 11 | ||||
149 | visible: false | ||||
150 | onVisibleChanged: text = Activity.targetNotes[0] == undefined ? "" | ||||
151 | : items.isTutorialMode ? qsTr("New note: %1").arg(Activity.targetNotes[0]) | ||||
152 | : Activity.newNotesSequence[Activity.currentNoteIndex] | ||||
153 | property string text | ||||
154 | | ||||
155 | GCText { | ||||
156 | id: label | ||||
157 | anchors.centerIn: parent | ||||
158 | fontSize: mediumSize | ||||
159 | text: parent.text | ||||
160 | } | ||||
161 | | ||||
162 | MouseArea { | ||||
163 | anchors.fill: parent | ||||
164 | enabled: items.isTutorialMode | ||||
165 | onClicked: { | ||||
166 | items.multipleStaff.pauseNoteAnimation() | ||||
167 | items.multipleStaff.musicElementModel.remove(1) | ||||
168 | Activity.showTutorial() | ||||
169 | } | ||||
170 | } | ||||
171 | } | ||||
172 | | ||||
173 | Rectangle { | ||||
174 | id: colorLayer | ||||
175 | anchors.fill: parent | ||||
176 | color: "black" | ||||
177 | opacity: 0.3 | ||||
178 | visible: iAmReady.visible | ||||
179 | z: 10 | ||||
180 | MouseArea { | ||||
181 | anchors.fill: parent | ||||
182 | } | ||||
183 | } | ||||
184 | | ||||
185 | ReadyButton { | ||||
186 | id: iAmReady | ||||
187 | focus: true | ||||
188 | z: 10 | ||||
189 | visible: !introMessage.visible | ||||
190 | onVisibleChanged: { | ||||
191 | messageBox.visible = false | ||||
192 | } | ||||
193 | onClicked: { | ||||
194 | Activity.initLevel() | ||||
195 | } | ||||
196 | } | ||||
197 | | ||||
198 | IntroMessage { | ||||
199 | id: introMessage | ||||
200 | anchors { | ||||
201 | top: parent.top | ||||
202 | topMargin: 10 | ||||
203 | right: parent.right | ||||
204 | rightMargin: 5 | ||||
205 | left: parent.left | ||||
206 | leftMargin: 5 | ||||
207 | } | ||||
208 | z: 10 | ||||
209 | } | ||||
210 | | ||||
211 | AdvancedTimer { | ||||
212 | id: addNoteTimer | ||||
213 | onTriggered: { | ||||
214 | Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length | ||||
215 | Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) | ||||
216 | } | ||||
217 | } | ||||
218 | | ||||
219 | ProgressBar { | ||||
220 | id: progressBar | ||||
221 | height: 20 * ApplicationInfo.ratio | ||||
222 | width: parent.width / 4 | ||||
223 | | ||||
224 | property int percentage: 0 | ||||
225 | | ||||
jjazeix: can it be an alias of the value? | |||||
amankumargupta: I had tried but it doesn't allow. | |||||
226 | value: percentage | ||||
227 | maximumValue: 100 | ||||
228 | visible: !items.isTutorialMode | ||||
229 | anchors { | ||||
230 | top: parent.top | ||||
231 | topMargin: 10 | ||||
232 | right: parent.right | ||||
233 | rightMargin: 10 | ||||
234 | } | ||||
235 | | ||||
236 | GCText { | ||||
237 | anchors.centerIn: parent | ||||
238 | fontSize: mediumSize | ||||
239 | font.bold: true | ||||
240 | color: "black" | ||||
241 | //: %1 is replaced by a number (parent.value) and is followed by a % symbol. | ||||
jjazeix: need a translator comment to understand the string | |||||
you can just say it's a percentage, translators won't know what is parent.value jjazeix: you can just say it's a percentage, translators won't know what is parent.value | |||||
242 | text: qsTr("%1%").arg(parent.value) | ||||
it still needs qsTr(). In French, there is a space between the number and the % jjazeix: it still needs qsTr(). In French, there is a space between the number and the % | |||||
243 | z: 2 | ||||
244 | } | ||||
245 | } | ||||
246 | | ||||
247 | MultipleStaff { | ||||
248 | id: multipleStaff | ||||
249 | width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 | ||||
250 | height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 | ||||
251 | nbStaves: 1 | ||||
252 | clef: clefType | ||||
253 | notesColor: "red" | ||||
254 | isFlickable: false | ||||
255 | anchors.horizontalCenter: parent.horizontalCenter | ||||
256 | anchors.top: parent.top | ||||
257 | anchors.topMargin: progressBar.height + 20 | ||||
258 | flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 | ||||
259 | noteAnimationEnabled: true | ||||
260 | onNoteAnimationFinished: { | ||||
261 | if(!items.isTutorialMode) | ||||
262 | displayNoteNameTimer.start() | ||||
263 | } | ||||
264 | } | ||||
265 | | ||||
266 | // We present a pair of two joint piano keyboard octaves. | ||||
267 | Item { | ||||
268 | id: doubleOctave | ||||
269 | width: parent.width * 0.95 | ||||
270 | height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 | ||||
271 | anchors.horizontalCenter: parent.horizontalCenter | ||||
272 | anchors.bottom: bar.top | ||||
273 | anchors.bottomMargin: 30 | ||||
274 | | ||||
275 | readonly property int nbJointKeyboards: 2 | ||||
276 | readonly property int maxNbOctaves: 3 | ||||
277 | property int currentOctaveNb: 0 | ||||
278 | property var coloredKeyLabels: [] | ||||
279 | | ||||
280 | Repeater { | ||||
281 | id: octaveRepeater | ||||
I have a problem when I want to play a B3. I should have two keyboards present on the screen. C3-B3 keyboard under C4-B4, I can this way play a C4 and B3 without using the arrows. At the moment I have just the C3-B3 alone on the top position and this is not playable. Same problem in horizontal mode where I have only C3-B3 on the right of the screen. I should see the two octaves C3-B3 and C4-B4. echarruau: I have a problem when I want to play a B3. I should have two keyboards present on the screen. | |||||
282 | anchors.fill: parent | ||||
283 | model: doubleOctave.nbJointKeyboards | ||||
284 | PianoOctaveKeyboard { | ||||
echarruau: It would be good to rename Piano element as PianoOctaveKeyboard | |||||
285 | id: pianoKeyboard | ||||
286 | width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width | ||||
287 | height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 | ||||
288 | blackLabelsVisible: false | ||||
289 | blackKeysEnabled: blackLabelsVisible | ||||
290 | whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 | ||||
291 | onNoteClicked: Activity.checkAnswer(note) | ||||
292 | currentOctaveNb: doubleOctave.currentOctaveNb | ||||
293 | anchors.top: (index === 1) ? octaveRepeater.top : undefined | ||||
294 | anchors.topMargin: horizontalLayout ? 0 : -15 | ||||
295 | anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined | ||||
296 | anchors.right: (index === 1) ? octaveRepeater.right : undefined | ||||
297 | coloredKeyLabels: doubleOctave.coloredKeyLabels | ||||
298 | labelsColor: "red" | ||||
299 | // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. | ||||
300 | whiteKeyNoteLabelsBass: { | ||||
301 | if(index === 0) { | ||||
302 | return [ | ||||
303 | whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 | ||||
304 | whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 | ||||
305 | whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 | ||||
306 | ] | ||||
307 | } | ||||
308 | else { | ||||
309 | return [ | ||||
310 | whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 | ||||
311 | whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 | ||||
312 | whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 | ||||
313 | ] | ||||
314 | } | ||||
should have a better id than piano2. jjazeix: should have a better id than piano2.
And a comment to tell why you need 2 pianos, and not only… | |||||
315 | } | ||||
316 | whiteKeyNoteLabelsTreble: { | ||||
317 | if(index === 0) { | ||||
318 | return [ | ||||
319 | whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 | ||||
320 | whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 | ||||
321 | whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 | ||||
322 | ] | ||||
323 | } | ||||
324 | else { | ||||
325 | return [ | ||||
326 | whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 | ||||
327 | whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 | ||||
328 | whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 | ||||
329 | ] | ||||
330 | } | ||||
331 | } | ||||
332 | } | ||||
333 | } | ||||
334 | } | ||||
335 | | ||||
336 | Image { | ||||
337 | id: shiftKeyboardLeft | ||||
338 | source: "qrc:/gcompris/src/core/resource/bar_previous.svg" | ||||
339 | sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 | ||||
340 | width: sourceSize.width | ||||
341 | height: width | ||||
342 | fillMode: Image.PreserveAspectFit | ||||
jjazeix: shouldn't it be bar_previous? | |||||
343 | visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible | ||||
344 | anchors { | ||||
345 | bottom: doubleOctave.top | ||||
346 | left: doubleOctave.left | ||||
347 | leftMargin: -37 | ||||
348 | bottomMargin: horizontalLayout ? 10 : 25 | ||||
349 | } | ||||
350 | MouseArea { | ||||
351 | enabled: !messageBox.visible | ||||
352 | anchors.fill: parent | ||||
353 | onClicked: { | ||||
354 | doubleOctave.currentOctaveNb-- | ||||
355 | } | ||||
356 | } | ||||
357 | } | ||||
358 | | ||||
359 | Image { | ||||
360 | id: shiftKeyboardRight | ||||
361 | source: "qrc:/gcompris/src/core/resource/bar_next.svg" | ||||
362 | sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 | ||||
363 | width: sourceSize.width | ||||
can you try to make the arrows bigger and put them over the keyboard, you gain some place to extend the size of the keyboardon the left and on the right, bigger the keys will be better it will be. echarruau: can you try to make the arrows bigger and put them over the keyboard, you gain some place to… | |||||
amankumargupta: you mean above the keyboard? | |||||
echarruau: Yes above. Three is enough Place. | |||||
364 | height: width | ||||
365 | fillMode: Image.PreserveAspectFit | ||||
366 | visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible | ||||
367 | anchors { | ||||
368 | bottom: doubleOctave.top | ||||
369 | right: doubleOctave.right | ||||
370 | rightMargin: -37 | ||||
371 | bottomMargin: horizontalLayout ? 10 : 25 | ||||
372 | } | ||||
373 | MouseArea { | ||||
374 | enabled: !messageBox.visible | ||||
375 | anchors.fill: parent | ||||
376 | onClicked: { | ||||
377 | doubleOctave.currentOctaveNb++ | ||||
378 | } | ||||
379 | } | ||||
380 | } | ||||
381 | | ||||
382 | OptionsRow { | ||||
383 | id: optionsRow | ||||
384 | visible: false | ||||
385 | } | ||||
386 | | ||||
387 | DialogHelp { | ||||
388 | id: dialogHelp | ||||
389 | onClose: home() | ||||
390 | } | ||||
391 | | ||||
392 | Bar { | ||||
393 | id: bar | ||||
394 | content: BarEnumContent { value: help | home | level | reload } | ||||
395 | onHelpClicked: { | ||||
396 | displayDialog(dialogHelp) | ||||
397 | } | ||||
398 | onPreviousLevelClicked: Activity.previousLevel() | ||||
399 | onNextLevelClicked: Activity.nextLevel() | ||||
400 | onHomeClicked: activity.home() | ||||
401 | onReloadClicked: { | ||||
402 | multipleStaff.eraseAllNotes() | ||||
403 | iAmReady.visible = true | ||||
404 | } | ||||
405 | } | ||||
406 | | ||||
407 | Bonus { | ||||
408 | id: bonus | ||||
409 | Component.onCompleted: win.connect(Activity.nextLevel) | ||||
410 | } | ||||
411 | } | ||||
412 | } |
code should be factorised