diff --git a/src/activities/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index c2e74aff3..cba2316c2 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,497 +1,498 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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 3 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 1.5 import GCompris 1.0 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity property int speedSetting: 5 property int timerNormalInterval: (13500 / speedSetting) + isMusicalActivity: true onStart: focus = true onStop: {} property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyNoteBindings = {} keyNoteBindings[Qt.Key_1] = 'C' keyNoteBindings[Qt.Key_2] = 'D' keyNoteBindings[Qt.Key_3] = 'E' keyNoteBindings[Qt.Key_4] = 'F' keyNoteBindings[Qt.Key_5] = 'G' keyNoteBindings[Qt.Key_6] = 'A' keyNoteBindings[Qt.Key_7] = 'B' if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { if(keyNoteBindings[event.key]) { // If the key pressed matches the note, pass the correct answer as parameter. isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items, activity.timerNormalInterval) } onStop: { Activity.stop() } property string clefType: "Treble" DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias speedSlider: speedSlider height: column.height Column { id: column spacing: 10 * ApplicationInfo.ratio width: parent.width GCText { id: speedSliderText text: qsTr("Speed") fontSize: mediumSize wrapMode: Text.WordWrap } Flow { width: dialogActivityConfig.width spacing: 5 GCSlider { id: speedSlider width: 250 * ApplicationInfo.ratio value: activity.speedSetting maximumValue: 5 minimumValue: 1 scrollEnabled: false } } } } } onStart: { if(!introMessage.visible || !iAmReady.visible) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() } } onClose: { home(); introMessage.visible = false; iAmReady.visible = true; } onLoadData: { if(dataToSave) { if(dataToSave["speedSetting"]) { activity.speedSetting = dataToSave["speedSetting"]; } } } onSaveData: { var oldSpeed = activity.speedSetting activity.speedSetting = dialogActivityConfig.configItem.speedSlider.value if(oldSpeed != activity.speedSetting) { dataToSave = {"speedSetting": activity.speedSetting}; background.stop(); introMessage.visible = false; iAmReady.visible = true; } } } Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false function getTranslatedNoteName(noteName) { for(var i = 0; i < doubleOctave.keyNames.length; i++) { if(doubleOctave.keyNames[i][0] == noteName) return doubleOctave.keyNames[i][1] } return "" } onVisibleChanged: { if(Activity.targetNotes[0] === undefined) text = "" else if(items.isTutorialMode) text = qsTr("New note: %1").arg(getTranslatedNoteName(Activity.targetNotes[0])) else text = getTranslatedNoteName(Activity.newNotesSequence[Activity.currentNoteIndex]) } property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } ProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage maximumValue: 100 visible: !items.isTutorialMode anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" softColorOpacity: 0 isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true noteAnimationDuration: items.isTutorialMode ? 9000 : 45000 / activity.speedSetting onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] property var keyNames: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } Component.onCompleted: doubleOctave.keyNames = whiteKeyNoteLabelsArray } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow iconsWidth: 0 visible: false } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: (help | home | level | reload | config) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/core/ActivityBase.qml b/src/core/ActivityBase.qml index 68063ff98..738f4e7d6 100644 --- a/src/core/ActivityBase.qml +++ b/src/core/ActivityBase.qml @@ -1,254 +1,246 @@ /* GCompris - ActivityBase.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 3 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 GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core /** * The base QML component for activities in GCompris. * @ingroup components * * Each activity should be derived from this component. It is responsible for * * * basic common key handling, * * unified audio handling, * * screen switching dynamics (from/to Menu/DialogHelp/etc.) * * The following common keys are handled so far: * * * @c Ctrl+q: Exit the application. * * @c Ctrl+b: Toggle the bar. * * @c Ctrl+f: Toggle fullscreen. * * @c Ctrl+m: Toggle audio effects. * * @c Ctrl+w: Exit the current activity and return to the menu. * * @c Ctrl+p: Make a screenshot. * * @c Back: Return to the home screen (corresponds to the 'Back' button on * Android). * * Cf. Template.qml for a sample skeleton activity. * * Cf. * [the wiki](https://gcompris.net/wiki/Qt_Quick_development_process#Adding_a_new_activity) * for further information about creating a new activity. * * @inherit QtQuick.Item */ Item { id: page /** * type:Item * Parent object. */ property Item main: parent; /** * type:Component * The top-level component containing the visible viewport of an activity. * * Put all you want to present the user into this container. Mostly * implemented using a Rectangle or Image component, itself * containing further graphical elements. You are pretty free of doing * whatever you want inside this component. * * Also common elements as Bar, Score, DialogHelp, etc. should be placed * inside this element. */ property Component pageComponent /** * type:QtObject * Reference to the menu activity. * * Populated automatically during activity-loading. */ property QtObject menu /** * type:QtObject * Reference to the ActivityInfo object of the activity. * * Populated automatically during activity-loading. */ property QtObject activityInfo /** * type:GCAudio * The global audio item for voices. * * Because of problems synchronizing multiple Audio objects between * global/menu/main and individual activities, activities should refrain * from implementing additional Audio elements. * * Instead append to this global object to play your voices after the * intro music. * @sa GCAudio audioVoices */ property GCAudio audioVoices /** * type:GCSfx * The global audio item for audio effects. * * Use it to play your effects. * @sa GCSfx audioEffects */ property GCSfx audioEffects /** * type:GCAudio * The global audio item for background music. * * @sa GCAudio backgroundMusic */ property GCAudio backgroundMusic /** * type: bool * It tells whether the activity is a musical activity or not(if the activity contains it's own audio effects). * * If the activity is a musical activity, on starting it the background music pauses and when the activity is quit, background music resumes. * * Set it as true if the activity is musical. */ property bool isMusicalActivity: false /** * type:Loading * The global loading object. * * Start it to signal heavy computation in case of GUI freezes. * @sa Loading */ property Loading loading - /** - * type: bool - * This variable stores if the activity is a musical activity. - * - * If it is a musical activity and the audioEffects is disabled, we temporarily unmute the GCSfx audioEffects for that activity and mute again on exiting it in main.qml. - */ - property bool isMusicalActivity: false - /** * Emitted when the user wants to return to the Home/Menu screen. */ signal home /** * Emitted when the user wants to return several views back in the * page stack. */ signal back(Item to) /** * Emitted every time the activity has been started. * * Initialize your activity upon this signal. */ signal start /** * Emitted when the activity is about to stop * * Shutdown whatever you need to upon this signal. */ signal stop /** * Emitted when dialog @p dialog should be shown * * Emit this signal when you want to show another dialog, e.g. on * Bar.onHelpClicked * * @param dialog Dialog to show. */ signal displayDialog(Item dialog) /** * Emitted when multiple @p dialogs should be pushed on the page-stack * * Emit this signal when you want to stack >1 views. The last one will be * shown the intermediated ones will be kept on the page stack for later * pop() calls. * * @param dialogs Array of dialogs to push; */ signal displayDialogs(var dialogs) onBack: menu ? menu.back(to) : "" onHome: menu ? menu.home() : "" onDisplayDialog: menu ? menu.displayDialog(dialog) : "" onDisplayDialogs: menu ? menu.displayDialogs(dialogs) : "" Keys.forwardTo: activity.children Keys.onEscapePressed: home(); Keys.onPressed: { if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Q) { // Ctrl+Q exit the application Core.quit(main); } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_B) { // Ctrl+B toggle the bar ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden; } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_F) { // Ctrl+F toggle fullscreen ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_M) { // Ctrl+M toggle sound // We mute / unmute both channels in sync ApplicationSettings.isAudioVoicesEnabled = !ApplicationSettings.isAudioVoicesEnabled ApplicationSettings.isAudioEffectsEnabled = ApplicationSettings.isAudioVoicesEnabled ApplicationSettings.isBackgroundMusicEnabled = ApplicationSettings.isAudioVoicesEnabled } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_W) { // Ctrl+W exit the current activity home() } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_P) { // Ctrl+P Screenshot ApplicationInfo.screenshot("/tmp/" + activityInfo.name.split('/')[0] + ".png") } } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true home() } } Loader { id: activity sourceComponent: pageComponent anchors.fill: parent } Loader { id: demoPageLoader source: ApplicationSettings.activationMode == 1 ? "BuyMeOverlayInapp.qml" : "BuyMeOverlay.qml" anchors.fill: parent active: !activityInfo.demo && ApplicationSettings.isDemoMode } } diff --git a/src/core/ApplicationSettings.cpp b/src/core/ApplicationSettings.cpp index 9b48f6cb6..64eac6499 100644 --- a/src/core/ApplicationSettings.cpp +++ b/src/core/ApplicationSettings.cpp @@ -1,548 +1,544 @@ /* GCompris - ApplicationSettings.cpp * * Copyright (C) 2014-2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 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 . */ #include "ApplicationSettings.h" #include "ApplicationInfo.h" #include "DownloadManager.h" #include #include #include #include #include #include #include #include #define GC_DEFAULT_FONT QLatin1String("Andika-R.otf") #define GC_DEFAULT_FONT_CAPITALIZATION 0 // Font.MixedCase #define GC_DEFAULT_FONT_LETTER_SPACING 0 static const char *GENERAL_GROUP_KEY = "General"; static const char *ADMIN_GROUP_KEY = "Admin"; static const char *INTERNAL_GROUP_KEY = "Internal"; static const char *FAVORITE_GROUP_KEY = "Favorite"; static const char *FULLSCREEN_KEY = "fullscreen"; static const char *PREVIOUS_HEIGHT_KEY = "previousHeight"; static const char *PREVIOUS_WIDTH_KEY = "previousWidth"; static const char *SHOW_LOCKED_ACTIVITIES_KEY = "showLockedActivities"; static const char *ENABLE_AUDIO_VOICES_KEY = "enableAudioVoices"; static const char *ENABLE_AUDIO_EFFECTS_KEY = "enableAudioEffects"; static const char *ENABLE_BACKGROUND_MUSIC_KEY = "enableBackgroundMusic"; static const char *VIRTUALKEYBOARD_KEY = "virtualKeyboard"; static const char *LOCALE_KEY = "locale"; static const char *FONT_KEY = "font"; static const char *IS_CURRENT_FONT_EMBEDDED = "isCurrentFontEmbedded"; static const char *ENABLE_AUTOMATIC_DOWNLOADS = "enableAutomaticDownloads"; static const char *FILTERED_BACKGROUND_MUSIC_KEY = "filteredBackgroundMusic"; static const char *BACKGROUND_MUSIC_VOLUME_KEY = "backgroundMusicVolume"; static const char *AUDIO_EFFECTS_VOLUME_KEY = "audioEffectsVolume"; -static const char *FILTERED_BACKGROUND_MUSIC_KEY = "filteredBackgroundMusic"; -static const char *BACKGROUND_MUSIC_VOLUME_KEY = "backgroundMusicVolume"; -static const char *AUDIO_EFFECTS_VOLUME_KEY = "audioEffectsVolume"; - static const char *DOWNLOAD_SERVER_URL_KEY = "downloadServerUrl"; static const char *CACHE_PATH_KEY = "cachePath"; static const char *USERDATA_PATH_KEY = "userDataPath"; static const char *RENDERER_KEY = "renderer"; static const char *EXE_COUNT_KEY = "exeCount"; static const char *LAST_GC_VERSION_RAN = "lastGCVersionRan"; static const char *FILTER_LEVEL_MIN = "filterLevelMin"; static const char *FILTER_LEVEL_MAX = "filterLevelMax"; static const char *BASE_FONT_SIZE_KEY = "baseFontSize"; static const char *FONT_CAPITALIZATION = "fontCapitalization"; static const char *FONT_LETTER_SPACING = "fontLetterSpacing"; static const char *DEFAULT_CURSOR = "defaultCursor"; static const char *NO_CURSOR = "noCursor"; static const char *DEMO_KEY = "demo"; static const char *CODE_KEY = "key"; static const char *KIOSK_KEY = "kiosk"; static const char *SECTION_VISIBLE = "sectionVisible"; static const char *WORDSET = "wordset"; static const char *PROGRESS_KEY = "progress"; static const char *DEFAULT_DOWNLOAD_SERVER = "https://cdn.kde.org/gcompris"; ApplicationSettings *ApplicationSettings::m_instance = nullptr; ApplicationSettings::ApplicationSettings(const QString &configPath, QObject *parent): QObject(parent), m_baseFontSizeMin(-7), m_baseFontSizeMax(7), m_fontLetterSpacingMin(0.0), m_fontLetterSpacingMax(8.0), m_config(configPath, QSettings::IniFormat) { const QRect &screenSize = QGuiApplication::screens().at(0)->availableGeometry(); // initialize from settings file or default // general group m_config.beginGroup(GENERAL_GROUP_KEY); m_isAudioEffectsEnabled = m_config.value(ENABLE_AUDIO_EFFECTS_KEY, true).toBool(); m_isBackgroundMusicEnabled = m_config.value(ENABLE_BACKGROUND_MUSIC_KEY, true).toBool(); m_isFullscreen = m_config.value(FULLSCREEN_KEY, true).toBool(); m_previousHeight = m_config.value(PREVIOUS_HEIGHT_KEY, screenSize.height()).toUInt(); m_previousWidth = m_config.value(PREVIOUS_WIDTH_KEY, screenSize.width()).toUInt(); m_isAudioVoicesEnabled = m_config.value(ENABLE_AUDIO_VOICES_KEY, true).toBool(); m_isVirtualKeyboard = m_config.value(VIRTUALKEYBOARD_KEY, ApplicationInfo::getInstance()->isMobile()).toBool(); m_locale = m_config.value(LOCALE_KEY, GC_DEFAULT_LOCALE).toString(); m_font = m_config.value(FONT_KEY, GC_DEFAULT_FONT).toString(); if(m_font == QLatin1String("Andika-R.ttf")) m_font = "Andika-R.otf"; m_fontCapitalization = m_config.value(FONT_CAPITALIZATION, GC_DEFAULT_FONT_CAPITALIZATION).toUInt(); m_fontLetterSpacing = m_config.value(FONT_LETTER_SPACING, GC_DEFAULT_FONT_LETTER_SPACING).toReal(); m_isEmbeddedFont = m_config.value(IS_CURRENT_FONT_EMBEDDED, true).toBool(); m_filteredBackgroundMusic = m_config.value(FILTERED_BACKGROUND_MUSIC_KEY, ApplicationInfo::getInstance()->getBackgroundMusicFromRcc()).toStringList(); m_backgroundMusicVolume = m_config.value(BACKGROUND_MUSIC_VOLUME_KEY, 0.4).toReal(); m_audioEffectsVolume = m_config.value(AUDIO_EFFECTS_VOLUME_KEY, 0.7).toReal(); // Init the activation mode if(QLatin1String(ACTIVATION_MODE) == "no") m_activationMode = 0; else if(QLatin1String(ACTIVATION_MODE) == "inapp") m_activationMode = 1; else if(QLatin1String(ACTIVATION_MODE) == "internal") m_activationMode = 2; else qFatal("Unknown activation mode"); // Set the demo mode if(QLatin1String(ACTIVATION_MODE) != "no") m_isDemoMode = m_config.value(DEMO_KEY, true).toBool(); else m_isDemoMode = false; m_codeKey = m_config.value(CODE_KEY, "").toString(); #if defined(WITH_KIOSK_MODE) m_isKioskMode = m_config.value(KIOSK_KEY, true).toBool(); #else m_isKioskMode = m_config.value(KIOSK_KEY, false).toBool(); #endif // Option only useful if we are in demo mode (else all the activities are available and unlocked) // By default, all the activities are displayed (even locked ones) m_showLockedActivities = m_config.value(SHOW_LOCKED_ACTIVITIES_KEY, m_isDemoMode).toBool(); m_sectionVisible = m_config.value(SECTION_VISIBLE, true).toBool(); m_wordset = m_config.value(WORDSET, "").toString(); m_isAutomaticDownloadsEnabled = m_config.value(ENABLE_AUTOMATIC_DOWNLOADS, !ApplicationInfo::getInstance()->isMobile() && ApplicationInfo::isDownloadAllowed()).toBool(); m_filterLevelMin = m_config.value(FILTER_LEVEL_MIN, 1).toUInt(); m_filterLevelMax = m_config.value(FILTER_LEVEL_MAX, 6).toUInt(); m_defaultCursor = m_config.value(DEFAULT_CURSOR, false).toBool(); m_noCursor = m_config.value(NO_CURSOR, false).toBool(); m_baseFontSize = m_config.value(BASE_FONT_SIZE_KEY, 0).toInt(); m_config.sync(); // make sure all defaults are written back m_config.endGroup(); // admin group m_config.beginGroup(ADMIN_GROUP_KEY); m_downloadServerUrl = m_config.value(DOWNLOAD_SERVER_URL_KEY, QLatin1String(DEFAULT_DOWNLOAD_SERVER)).toString(); if(m_downloadServerUrl == "http://gcompris.net") { setDownloadServerUrl(DEFAULT_DOWNLOAD_SERVER); } m_cachePath = m_config.value(CACHE_PATH_KEY, QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).toString(); m_userDataPath = m_config.value(USERDATA_PATH_KEY, QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/GCompris")).toString(); m_renderer = m_config.value(RENDERER_KEY, GRAPHICAL_RENDERER).toString(); m_config.endGroup(); // internal group m_config.beginGroup(INTERNAL_GROUP_KEY); m_exeCount = m_config.value(EXE_COUNT_KEY, 0).toUInt(); m_lastGCVersionRan = m_config.value(LAST_GC_VERSION_RAN, 0).toUInt(); m_config.endGroup(); // no group m_isBarHidden = false; connect(this, &ApplicationSettings::showLockedActivitiesChanged, this, &ApplicationSettings::notifyShowLockedActivitiesChanged); connect(this, &ApplicationSettings::audioVoicesEnabledChanged, this, &ApplicationSettings::notifyAudioVoicesEnabledChanged); connect(this, &ApplicationSettings::audioEffectsEnabledChanged, this, &ApplicationSettings::notifyAudioEffectsEnabledChanged); connect(this, &ApplicationSettings::backgroundMusicEnabledChanged, this, &ApplicationSettings::notifyBackgroundMusicEnabledChanged); connect(this, &ApplicationSettings::filteredBackgroundMusicChanged, this, &ApplicationSettings::notifyFilteredBackgroundMusicChanged); connect(this, &ApplicationSettings::fullscreenChanged, this, &ApplicationSettings::notifyFullscreenChanged); connect(this, &ApplicationSettings::previousHeightChanged, this, &ApplicationSettings::notifyPreviousHeightChanged); connect(this, &ApplicationSettings::previousWidthChanged, this, &ApplicationSettings::notifyPreviousWidthChanged); connect(this, &ApplicationSettings::localeChanged, this, &ApplicationSettings::notifyLocaleChanged); connect(this, &ApplicationSettings::fontChanged, this, &ApplicationSettings::notifyFontChanged); connect(this, &ApplicationSettings::virtualKeyboardChanged, this, &ApplicationSettings::notifyVirtualKeyboardChanged); connect(this, &ApplicationSettings::automaticDownloadsEnabledChanged, this, &ApplicationSettings::notifyAutomaticDownloadsEnabledChanged); connect(this, &ApplicationSettings::filterLevelMinChanged, this, &ApplicationSettings::notifyFilterLevelMinChanged); connect(this, &ApplicationSettings::filterLevelMaxChanged, this, &ApplicationSettings::notifyFilterLevelMaxChanged); connect(this, &ApplicationSettings::sectionVisibleChanged, this, &ApplicationSettings::notifySectionVisibleChanged); connect(this, &ApplicationSettings::wordsetChanged, this, &ApplicationSettings::notifyWordsetChanged); connect(this, &ApplicationSettings::demoModeChanged, this, &ApplicationSettings::notifyDemoModeChanged); connect(this, &ApplicationSettings::codeKeyChanged, this, &ApplicationSettings::notifyCodeKeyChanged); connect(this, &ApplicationSettings::kioskModeChanged, this, &ApplicationSettings::notifyKioskModeChanged); connect(this, &ApplicationSettings::downloadServerUrlChanged, this, &ApplicationSettings::notifyDownloadServerUrlChanged); connect(this, &ApplicationSettings::cachePathChanged, this, &ApplicationSettings::notifyCachePathChanged); connect(this, &ApplicationSettings::userDataPathChanged, this, &ApplicationSettings::notifyUserDataPathChanged); connect(this, &ApplicationSettings::rendererChanged, this, &ApplicationSettings::notifyRendererChanged); connect(this, &ApplicationSettings::exeCountChanged, this, &ApplicationSettings::notifyExeCountChanged); connect(this, &ApplicationSettings::barHiddenChanged, this, &ApplicationSettings::notifyBarHiddenChanged); connect(this, &ApplicationSettings::lastGCVersionRanChanged, this, &ApplicationSettings::notifyLastGCVersionRanChanged); connect(this, &ApplicationSettings::backgroundMusicVolumeChanged, this, &ApplicationSettings::notifyBackgroundMusicVolumeChanged); connect(this, &ApplicationSettings::audioEffectsVolumeChanged, this, &ApplicationSettings::notifyAudioEffectsVolumeChanged); } ApplicationSettings::~ApplicationSettings() { // make sure settings file is up2date: // general group m_config.beginGroup(GENERAL_GROUP_KEY); m_config.setValue(SHOW_LOCKED_ACTIVITIES_KEY, m_showLockedActivities); m_config.setValue(ENABLE_AUDIO_VOICES_KEY, m_isAudioVoicesEnabled); m_config.setValue(ENABLE_BACKGROUND_MUSIC_KEY, m_isBackgroundMusicEnabled); m_config.setValue(FILTERED_BACKGROUND_MUSIC_KEY, m_filteredBackgroundMusic); m_config.setValue(BACKGROUND_MUSIC_VOLUME_KEY, m_backgroundMusicVolume); m_config.setValue(AUDIO_EFFECTS_VOLUME_KEY, m_audioEffectsVolume); m_config.setValue(LOCALE_KEY, m_locale); m_config.setValue(FONT_KEY, m_font); m_config.setValue(IS_CURRENT_FONT_EMBEDDED, m_isEmbeddedFont); m_config.setValue(FULLSCREEN_KEY, m_isFullscreen); m_config.setValue(PREVIOUS_HEIGHT_KEY, m_previousHeight); m_config.setValue(PREVIOUS_WIDTH_KEY, m_previousWidth); m_config.setValue(VIRTUALKEYBOARD_KEY, m_isVirtualKeyboard); m_config.setValue(ENABLE_AUTOMATIC_DOWNLOADS, m_isAutomaticDownloadsEnabled); m_config.setValue(FILTER_LEVEL_MIN, m_filterLevelMin); m_config.setValue(FILTER_LEVEL_MAX, m_filterLevelMax); m_config.setValue(DEMO_KEY, m_isDemoMode); m_config.setValue(CODE_KEY, m_codeKey); m_config.setValue(KIOSK_KEY, m_isKioskMode); m_config.setValue(SECTION_VISIBLE, m_sectionVisible); m_config.setValue(WORDSET, m_wordset); m_config.setValue(DEFAULT_CURSOR, m_defaultCursor); m_config.setValue(NO_CURSOR, m_noCursor); m_config.setValue(BASE_FONT_SIZE_KEY, m_baseFontSize); m_config.setValue(FONT_CAPITALIZATION, m_fontCapitalization); m_config.setValue(FONT_LETTER_SPACING, m_fontLetterSpacing); m_config.endGroup(); // admin group m_config.beginGroup(ADMIN_GROUP_KEY); m_config.setValue(DOWNLOAD_SERVER_URL_KEY, m_downloadServerUrl); m_config.setValue(CACHE_PATH_KEY, m_cachePath); m_config.setValue(USERDATA_PATH_KEY, m_userDataPath); m_config.setValue(RENDERER_KEY, m_renderer); m_config.endGroup(); // internal group m_config.beginGroup(INTERNAL_GROUP_KEY); m_config.setValue(EXE_COUNT_KEY, m_exeCount); m_config.setValue(LAST_GC_VERSION_RAN, m_lastGCVersionRan); m_config.endGroup(); m_config.sync(); m_instance = nullptr; } void ApplicationSettings::notifyShowLockedActivitiesChanged() { updateValueInConfig(GENERAL_GROUP_KEY, SHOW_LOCKED_ACTIVITIES_KEY, m_showLockedActivities); qDebug() << "notifyShowLockedActivitiesChanged: " << m_showLockedActivities; } void ApplicationSettings::notifyAudioVoicesEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUDIO_VOICES_KEY, m_isAudioVoicesEnabled); qDebug() << "notifyAudioVoices: " << m_isAudioVoicesEnabled; } void ApplicationSettings::notifyAudioEffectsEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUDIO_EFFECTS_KEY, m_isAudioEffectsEnabled); qDebug() << "notifyAudioEffects: " << m_isAudioEffectsEnabled; } void ApplicationSettings::notifyBackgroundMusicEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_BACKGROUND_MUSIC_KEY, m_isBackgroundMusicEnabled); qDebug() << "notifyBackgroundMusic: " << m_isBackgroundMusicEnabled; } void ApplicationSettings::notifyFilteredBackgroundMusicChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FILTERED_BACKGROUND_MUSIC_KEY, m_filteredBackgroundMusic); qDebug()<<"filteredBackgroundMusic: " << m_filteredBackgroundMusic; } void ApplicationSettings::notifyBackgroundMusicVolumeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, BACKGROUND_MUSIC_VOLUME_KEY, m_backgroundMusicVolume); qDebug()<<"backgroundMusicVolume: " << m_backgroundMusicVolume; } void ApplicationSettings::notifyAudioEffectsVolumeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, AUDIO_EFFECTS_VOLUME_KEY, m_audioEffectsVolume); qDebug()<<"audioEffectsVolume: " << m_audioEffectsVolume; } void ApplicationSettings::notifyLocaleChanged() { updateValueInConfig(GENERAL_GROUP_KEY, LOCALE_KEY, m_locale); qDebug() << "new locale: " << m_locale; } void ApplicationSettings::notifyFontChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_KEY, m_font); qDebug() << "new font: " << m_font; } void ApplicationSettings::notifyEmbeddedFontChanged() { updateValueInConfig(GENERAL_GROUP_KEY, IS_CURRENT_FONT_EMBEDDED, m_isEmbeddedFont); qDebug() << "new font is embedded: " << m_isEmbeddedFont; } void ApplicationSettings::notifyFontCapitalizationChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_CAPITALIZATION, m_fontCapitalization); qDebug() << "new fontCapitalization: " << m_fontCapitalization; } void ApplicationSettings::notifyFontLetterSpacingChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_LETTER_SPACING, m_fontLetterSpacing); qDebug() << "new fontLetterSpacing: " << m_fontLetterSpacing; } void ApplicationSettings::notifyFullscreenChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FULLSCREEN_KEY, m_isFullscreen); qDebug() << "fullscreen set to: " << m_isFullscreen; } void ApplicationSettings::notifyPreviousHeightChanged() { updateValueInConfig(GENERAL_GROUP_KEY, PREVIOUS_HEIGHT_KEY, m_previousHeight); qDebug() << "previous height set to: " << m_previousHeight; } void ApplicationSettings::notifyPreviousWidthChanged() { updateValueInConfig(GENERAL_GROUP_KEY, PREVIOUS_WIDTH_KEY, m_previousWidth); qDebug() << "previous width set to: " << m_previousWidth; } void ApplicationSettings::notifyVirtualKeyboardChanged() { updateValueInConfig(GENERAL_GROUP_KEY, VIRTUALKEYBOARD_KEY, m_isVirtualKeyboard); qDebug() << "virtualkeyboard set to: " << m_isVirtualKeyboard; } bool ApplicationSettings::isAutomaticDownloadsEnabled() const { return m_isAutomaticDownloadsEnabled && ApplicationInfo::isDownloadAllowed(); } void ApplicationSettings::setIsAutomaticDownloadsEnabled(const bool newIsAutomaticDownloadsEnabled) { if(ApplicationInfo::isDownloadAllowed()) { m_isAutomaticDownloadsEnabled = newIsAutomaticDownloadsEnabled; emit automaticDownloadsEnabledChanged(); } } void ApplicationSettings::notifyAutomaticDownloadsEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUTOMATIC_DOWNLOADS, m_isAutomaticDownloadsEnabled); qDebug() << "enableAutomaticDownloads set to: " << m_isAutomaticDownloadsEnabled; } void ApplicationSettings::notifyFilterLevelMinChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FILTER_LEVEL_MIN, m_filterLevelMin); qDebug() << "filterLevelMin set to: " << m_filterLevelMin; } void ApplicationSettings::notifyFilterLevelMaxChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FILTER_LEVEL_MAX, m_filterLevelMax); qDebug() << "filterLevelMax set to: " << m_filterLevelMax; } void ApplicationSettings::notifyDemoModeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, DEMO_KEY, m_isDemoMode); qDebug() << "notifyDemoMode: " << m_isDemoMode; } void ApplicationSettings::notifyCodeKeyChanged() { checkPayment(); if(!m_isDemoMode) updateValueInConfig(GENERAL_GROUP_KEY, CODE_KEY, m_codeKey); qDebug() << "notifyCodeKey: " << m_codeKey; } void ApplicationSettings::notifyKioskModeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, KIOSK_KEY, m_isKioskMode); qDebug() << "notifyKioskMode: " << m_isKioskMode; } void ApplicationSettings::notifySectionVisibleChanged() { updateValueInConfig(GENERAL_GROUP_KEY, SECTION_VISIBLE, m_sectionVisible); qDebug() << "notifySectionVisible: " << m_sectionVisible; } void ApplicationSettings::notifyWordsetChanged() { if(!m_wordset.isEmpty() && DownloadManager::getInstance()->haveLocalResource(m_wordset) && !DownloadManager::getInstance()->isDataRegistered("words")) { // words.rcc is there -> register old file first // then try to update in the background DownloadManager::getInstance()->updateResource(m_wordset); } updateValueInConfig(GENERAL_GROUP_KEY, WORDSET, m_wordset); qDebug() << "notifyWordset: " << m_wordset; } void ApplicationSettings::notifyDownloadServerUrlChanged() { updateValueInConfig(ADMIN_GROUP_KEY, DOWNLOAD_SERVER_URL_KEY, m_downloadServerUrl); qDebug() << "downloadServerUrl set to: " << m_downloadServerUrl; } void ApplicationSettings::notifyCachePathChanged() { updateValueInConfig(ADMIN_GROUP_KEY, CACHE_PATH_KEY, m_cachePath); qDebug() << "cachePath set to: " << m_cachePath; } void ApplicationSettings::notifyUserDataPathChanged() { updateValueInConfig(ADMIN_GROUP_KEY, USERDATA_PATH_KEY, m_userDataPath); qDebug() << "userDataPath set to: " << m_userDataPath; } void ApplicationSettings::notifyRendererChanged() { updateValueInConfig(ADMIN_GROUP_KEY, RENDERER_KEY, m_renderer); qDebug() << "renderer set to: " << m_renderer; } void ApplicationSettings::notifyExeCountChanged() { updateValueInConfig(INTERNAL_GROUP_KEY, EXE_COUNT_KEY, m_exeCount); qDebug() << "exeCount set to: " << m_exeCount; } void ApplicationSettings::notifyLastGCVersionRanChanged() { updateValueInConfig(INTERNAL_GROUP_KEY, LAST_GC_VERSION_RAN, m_lastGCVersionRan); qDebug() << "lastVersionRan set to: " << m_lastGCVersionRan; } void ApplicationSettings::notifyBarHiddenChanged() { qDebug() << "is bar hidden: " << m_isBarHidden; } void ApplicationSettings::saveBaseFontSize() { updateValueInConfig(GENERAL_GROUP_KEY, BASE_FONT_SIZE_KEY, m_baseFontSize); } void ApplicationSettings::saveActivityConfiguration(const QString &activity, const QVariantMap &data) { qDebug() << "save configuration for:" << activity; QMapIterator i(data); while (i.hasNext()) { i.next(); updateValueInConfig(activity, i.key(), i.value()); } } QVariantMap ApplicationSettings::loadActivityConfiguration(const QString &activity) { qDebug() << "load configuration for:" << activity; m_config.beginGroup(activity); QStringList keys = m_config.childKeys(); QVariantMap data; for(const QString &key : keys) { data[key] = m_config.value(key); } m_config.endGroup(); return data; } void ApplicationSettings::setFavorite(const QString &activity, bool favorite) { updateValueInConfig(FAVORITE_GROUP_KEY, activity, favorite); } bool ApplicationSettings::isFavorite(const QString &activity) { m_config.beginGroup(FAVORITE_GROUP_KEY); bool favorite = m_config.value(activity, false).toBool(); m_config.endGroup(); return favorite; } template void ApplicationSettings::updateValueInConfig(const QString& group, const QString& key, const T& value) { m_config.beginGroup(group); m_config.setValue(key, value); m_config.endGroup(); m_config.sync(); } int ApplicationSettings::loadActivityProgress(const QString &activity) { int progress = 0; m_config.beginGroup(activity); progress = m_config.value(PROGRESS_KEY, 0).toInt(); m_config.endGroup(); qDebug() << "loaded progress for activity" << activity << ":" << progress; return progress; } void ApplicationSettings::saveActivityProgress(const QString &activity, int progress) { updateValueInConfig(activity, PROGRESS_KEY, progress); } bool ApplicationSettings::useExternalWordset() { return !m_wordset.isEmpty() && DownloadManager::getInstance()->isDataRegistered("words"); } QObject *ApplicationSettings::applicationSettingsProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) ApplicationSettings* appSettings = getInstance(); return appSettings; } diff --git a/src/core/GCAudio.qml b/src/core/GCAudio.qml index 87ce996fc..762e3134e 100644 --- a/src/core/GCAudio.qml +++ b/src/core/GCAudio.qml @@ -1,270 +1,272 @@ /* GCompris - GCAudio.qml * * Copyright (C) 2014 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 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 QtMultimedia 5.0 import GCompris 1.0 /** * A QML component for audio playback. * @ingroup components * * Wrapper component around QtQuick's Audio element, handling all audio * playback in GCompris uniformly. * * It maintains a queue of audio-sources (@ref files) that are played back * sequentially, to which the user can enqueue files that should be scheduled * for playback. * * To make sure an audio voice will be localized, replace the locale part * of the file by '$LOCALE'. * * To makes sure that all audio sources are normalized with respect to * ApplicationInfo.CompressedAudio replace the 'ogg' part of the file by * '$CA'. * * @inherit QtQuick.Item */ Item { id: gcaudio /** * type:bool * Whether audio should be muted. */ property bool muted /** * type:url * URL to the audio source to be played back. */ property alias source: audio.source /** * type: positive real number less than 1 * Determines intensity of the audio. */ property alias volume: audio.volume /** * type:bool * Whether the audio element contains audio. */ property bool hasAudio: audio.hasAudio /** * type:string * Detailed error message in case of playback errors. */ property alias errorString: audio.errorString /** * type:bool * check if the player is for background music */ property bool isBackgroundMusic: false /** * type:array * background music metadata */ property var metaDataMusic: ["", "", "", ""] /** * Trigger this signal externally to play the next audio in the "files". This, in turn, stops the currently playing audio and check the necessary * conditions (see onStopped signal in "audio" element) and decides what needs to be done for the next audio. */ signal nextAudio() onNextAudio: stop() /** * type:var * Current playback state. * * Possible values taken from Audio.status */ property var playbackState: (audio.error == Audio.NoError) ? audio.playbackState : Audio.StoppedState; /** * type:list * Playback queue. */ property var files: [] /** * Emitted in case of error. */ signal error /** * Emitted when playback of all scheduled audio sources has finished. */ signal done //Pauses the currently playing audio function pause() { if(playbackState === Audio.PlayingState) audio.pause() } //Resumes the current audio if it had been paused function resume() { if(playbackState === Audio.PausedState || playbackState === Audio.StoppedState) audio.play() } /** * Plays back the audio resource @p file. * * @param type:string file [optional] URL to an audio source. * @returns @c true if playback has been started, @c false if file does not * exist or audio is muted */ function play(file) { if(!fileId.exists(file) || muted) return false if(file) { // Setting the source to "" on Linux fix a case where the sound is no more played if you play twice the same sound in a row source = "" source = file } if(!muted) { audio.play() } return true } /** * Stops audio playback. */ function stop() { if(audio.playbackState != Audio.StoppedState) audio.stop() } /** * Schedules a @p file for audio playback. * * If there is no playback currently running, the new source will be * played back immediately. Otherwise it is appended to the file queue of * sources. * * @param type:string file File to the audio file to be played back. * @returns @c true upon success, or @c false if @p file does not exist or * audio is muted */ function append(file) { if(!fileId.exists(file) || muted) return false if(audio.playbackState !== Audio.PlayingState || audio.status === Audio.EndOfMedia || audio.status === Audio.NoMedia || audio.status === Audio.InvalidMedia) { // Setting the source to "" on Linux fix a case where the sound is no more played source = "" source = file files.push(file) silenceTimer.start() } else { files.push(file) } return true } /** * Adds a pause of the given duration in ms before playing of the next file. * * @param type:int duration_ms Pause in milliseconds. */ function silence(duration_ms) { silenceTimer.interval = duration_ms } /** * Flushes the list of scheduled files. * @sa files */ function clearQueue() { while(files.length > 0) { files.pop(); } } /// @cond INTERNAL_DOCS function _playNextFile() { - if(files.length == 0) + if(files.length == 0) { + gcaudio.done() return + } var nextFile = files.shift() if(nextFile === '') { source = "" gcaudio.done() } else { source = "" source = nextFile if(!muted) audio.play() } } Audio { id: audio muted: gcaudio.muted onError: { // This file cannot be played, remove it from the source asap source = "" if(files.length) silenceTimer.start() else gcaudio.error() } onStopped: { if(files.length) silenceTimer.start() else gcaudio.done() } metaData.onMetaDataChanged: { if(isBackgroundMusic) { metaDataMusic = [metaData.title, metaData.contributingArtist, metaData.year, metaData.copyright] } } } Timer { id: silenceTimer repeat: false onTriggered: { interval = 0 _playNextFile() } } File { id: fileId } /// @endcond } diff --git a/src/core/main.qml b/src/core/main.qml index 50e9e7886..272a4d87f 100644 --- a/src/core/main.qml +++ b/src/core/main.qml @@ -1,451 +1,447 @@ /* GCompris - main.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 3 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 1.5 import QtQuick.Window 2.2 import QtQml 2.2 import GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core /** * GCompris' main QML file defining the top level window. * @ingroup infrastructure * * Handles application start (Component.onCompleted) and shutdown (onClosing) * on the QML layer. * * Contains the central GCAudio objects audio effects and audio voices. * * Contains the top level StackView presenting and animating GCompris' * full screen views. * * @sa BarButton, BarEnumContent * @inherit QtQuick.Window */ Window { id: main // Start in window mode at full screen size width: ApplicationSettings.previousWidth height: ApplicationSettings.previousHeight minimumWidth: 400 * ApplicationInfo.ratio minimumHeight: 400 * ApplicationInfo.ratio title: "GCompris" /// @cond INTERNAL_DOCS property var applicationState: Qt.application.state property var rccBackgroundMusic: ApplicationInfo.getBackgroundMusicFromRcc() property var filteredBackgroundMusic: ApplicationSettings.filteredBackgroundMusic property alias backgroundMusic: backgroundMusic /** * type: bool - * It tells whether the background music is enabled for an activity. + * It tells whether a musical activity is running. * - * It changes to false if the started activity is a musical activity and back to true when the activity is closed. + * It changes to true if the started activity is a musical activity and back to false when the activity is closed, allowing to play background music. */ - property bool isBackgroundMusicEnabledInActivity: true + property bool isMusicalActivityRunning: false /** - * When a musical activity is started, isBackgroundMusicEnabledInActivity changes to false and the backgroundMusic pauses. + * When a musical activity is started, the backgroundMusic pauses. * - * When returning back from the musical activity to menu, isBackgroundMusicEnabledInActivity changes to true and backgroundMusic resumes. + * When returning back from the musical activity to menu, backgroundMusic resumes. */ - onIsBackgroundMusicEnabledInActivityChanged: { - if(!isBackgroundMusicEnabledInActivity) { + onIsMusicalActivityRunningChanged: { + if(isMusicalActivityRunning) { backgroundMusic.pause() } else { backgroundMusic.resume() } } onApplicationStateChanged: { if (ApplicationInfo.isMobile && applicationState != Qt.ApplicationActive) { audioVoices.stop(); audioEffects.stop(); } } onClosing: Core.quit(main) - + GCAudio { id: audioVoices muted: !ApplicationSettings.isAudioVoicesEnabled Timer { id: delayedWelcomeTimer interval: 10000 /* Make sure, that playing welcome.ogg if delayed * because of not yet registered voices, will only * happen max 10sec after startup */ repeat: false onTriggered: { DownloadManager.voicesRegistered.disconnect(playWelcome); } function playWelcome() { audioVoices.append(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/misc/welcome.$CA")); } } Component.onCompleted: { if (DownloadManager.areVoicesRegistered()) delayedWelcomeTimer.playWelcome(); else { DownloadManager.voicesRegistered.connect( delayedWelcomeTimer.playWelcome); delayedWelcomeTimer.start(); } } } GCSfx { id: audioEffects muted: !ApplicationSettings.isAudioEffectsEnabled && !main.isMusicalActivityRunning volume: ApplicationSettings.audioEffectsVolume } GCAudio { id: backgroundMusic - isBackgroundMusic: true - muted: !ApplicationSettings.isBackgroundMusicEnabled - volume: ApplicationSettings.backgroundMusicVolume onMutedChanged: { if(!hasAudio && !files.length) { backgroundMusic.playBackgroundMusic() } } onDone: backgroundMusic.playBackgroundMusic() - function playBackgroundMusic() { rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() - + for(var i = 0; i < filteredBackgroundMusic.length; i++) { backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[i])) } - if(main.isMusicalActivityRunning) backgroundMusic.pause() } - + Component.onCompleted: { if(ApplicationSettings.isBackgroundMusicEnabled) backgroundMusic.append(ApplicationInfo.getAudioFilePath("qrc:/gcompris/src/core/resource/intro.$CA")) if(ApplicationSettings.isBackgroundMusicEnabled && DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) { - backgroundMusic.playBackgroundMusic() + backgroundMusic.playBackgroundMusic() } else { DownloadManager.backgroundMusicRegistered.connect(backgroundMusic.playBackgroundMusic) } } } function playIntroVoice(name) { name = name.split("/")[0] audioVoices.play(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/intro/" + name + ".$CA")) } function checkWordset() { var wordset = ApplicationSettings.wordset if(wordset == '') // Maybe the wordset has been bundled or copied manually // we have to register it if we find it. wordset = 'data2/words/words.rcc' // check for words.rcc: if (DownloadManager.isDataRegistered("words")) { // words.rcc is already registered -> nothing to do } else if(DownloadManager.haveLocalResource(wordset)) { // words.rcc is there -> register old file first // then try to update in the background if(DownloadManager.updateResource(wordset)) { ApplicationSettings.wordset = wordset } } else if(ApplicationSettings.wordset) { // Only if wordset specified // words.rcc has not been downloaded yet -> ask for download Core.showMessageDialog( main, qsTr("The images for several activities are not yet installed. " + "Do you want to download them now?"), qsTr("Yes"), function() { if (DownloadManager.downloadResource(wordset)) var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); }, qsTr("No"), null, function() { pageView.currentItem.focus = true } ); } } function checkBackgroundMusic() { var music = DownloadManager.getBackgroundMusicResources() if(rccBackgroundMusic == '') { rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() } if(music == '') { music = DownloadManager.getBackgroundMusicResources() } - // We have local music but it is not yet registered + // We have local music but it is not yet registered else if(!DownloadManager.isDataRegistered("backgroundMusic") && DownloadManager.haveLocalResource(music)) { - // We have music and automatic download is enabled. Download the music and register it - if(DownloadManager.updateResource(music) && DownloadManager.downloadIsRunning()) { - DownloadManager.registerResource(music) - rccBackgroundMusic = Core.shuffle(ApplicationInfo.getBackgroundMusicFromRcc()) - } - else { - rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() + // We have music and automatic download is enabled. Download the music and register it + if(DownloadManager.updateResource(music) && DownloadManager.downloadIsRunning()) { + DownloadManager.registerResource(music) + rccBackgroundMusic = Core.shuffle(ApplicationInfo.getBackgroundMusicFromRcc()) + } + else { + rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() } } else if(!DownloadManager.haveLocalResource(music)) { Core.showMessageDialog( - main, - qsTr("The background music is not yet downloaded. ") - + qsTr("Do you want to download it now?"), - qsTr("Yes"), - function() { - if(DownloadManager.downloadResource(DownloadManager.getBackgroundMusicResources())) { - var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); - } - }, - qsTr("No"), null, - function() { pageView.currentItem.focus = true } - ); + main, + qsTr("The background music is not yet downloaded. ") + + qsTr("Do you want to download it now?"), + qsTr("Yes"), + function() { + if(DownloadManager.downloadResource(DownloadManager.getBackgroundMusicResources())) { + var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); } + }, + qsTr("No"), null, + function() { pageView.currentItem.focus = true } + ); } + } + ChangeLog { id: changelog } Component.onCompleted: { console.log("enter main.qml (run #" + ApplicationSettings.exeCount + ", ratio=" + ApplicationInfo.ratio + ", fontRatio=" + ApplicationInfo.fontRatio + ", dpi=" + Math.round(Screen.pixelDensity*25.4) + ", userDataPath=" + ApplicationSettings.userDataPath + ")"); if (ApplicationSettings.exeCount === 1 && !ApplicationSettings.isKioskMode && ApplicationInfo.isDownloadAllowed) { // first run var dialog; dialog = Core.showMessageDialog( main, qsTr("Welcome to GCompris!") + '\n' + qsTr("You are running GCompris for the first time.") + '\n' + qsTr("You should verify that your application settings especially your language is set correctly, and that all language specific sound files are installed. You can do this in the Preferences Dialog.") + "\n" + qsTr("Have Fun!") + "\n" + qsTr("Your current language is %1 (%2).") .arg(Qt.locale(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)).nativeLanguageName) .arg(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)) + "\n" + qsTr("Do you want to download the corresponding sound files now?"), qsTr("Yes"), function() { if (DownloadManager.downloadResource( DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale))) var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); }, qsTr("No"), null, function() { pageView.currentItem.focus = true checkWordset() checkBackgroundMusic() } ); } else { // Register voices-resources for current locale, updates/downloads only if // not prohibited by the settings if (!DownloadManager.areVoicesRegistered()) { DownloadManager.updateResource( DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)); } checkWordset() checkBackgroundMusic() if(changelog.isNewerVersion(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode)) { // display log between ApplicationSettings.lastGCVersionRan and ApplicationInfo.GCVersionCode Core.showMessageDialog( main, qsTr("GCompris has been updated! Here are the new changes:
") + changelog.getLogBetween(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode), "", null, "", null, function() { pageView.currentItem.focus = true } ); // Store new version ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode; } } } Loading { id: loading } StackView { id: pageView anchors.fill: parent initialItem: { "item": "qrc:/gcompris/src/activities/" + ActivityInfoTree.rootMenu.name, "properties": { 'audioVoices': audioVoices, 'audioEffects': audioEffects, 'loading': loading, 'backgroundMusic': backgroundMusic } } focus: true delegate: StackViewDelegate { id: root function getTransition(properties) { audioVoices.clearQueue() audioVoices.stop() if(!properties.exitItem.isDialog && // if coming from menu and !properties.enterItem.isDialog) // going into an activity then playIntroVoice(properties.enterItem.activityInfo.name); // play intro if (!properties.exitItem.isDialog || // if coming from menu or properties.enterItem.alwaysStart) // start signal enforced (for special case like transition from config-dialog to editor) properties.enterItem.start(); if(properties.name === "pushTransition") { if(properties.enterItem.isDialog) { return pushVTransition } else { if(properties.enterItem.isMusicalActivity) main.isMusicalActivityRunning = true return pushHTransition } } else { if(properties.exitItem.isDialog) { return popVTransition } else { main.isMusicalActivityRunning = false return popHTransition } } } function transitionFinished(properties) { properties.exitItem.opacity = 1 if(!properties.enterItem.isDialog) { properties.exitItem.stop() } } property Component pushHTransition: StackViewTransition { PropertyAnimation { target: enterItem property: "x" from: target.width to: 0 duration: 500 easing.type: Easing.OutSine } PropertyAnimation { target: exitItem property: "x" from: 0 to: -target.width duration: 500 easing.type: Easing.OutSine } } property Component popHTransition: StackViewTransition { PropertyAnimation { target: enterItem property: "x" from: -target.width to: 0 duration: 500 easing.type: Easing.OutSine } PropertyAnimation { target: exitItem property: "x" from: 0 to: target.width duration: 500 easing.type: Easing.OutSine } } property Component pushVTransition: StackViewTransition { PropertyAnimation { target: enterItem property: "y" from: -target.height to: 0 duration: 500 easing.type: Easing.OutSine } PropertyAnimation { target: exitItem property: "y" from: 0 to: target.height duration: 500 easing.type: Easing.OutSine } } property Component popVTransition: StackViewTransition { PropertyAnimation { target: enterItem property: "y" from: target.height to: 0 duration: 500 easing.type: Easing.OutSine } PropertyAnimation { target: exitItem property: "y" from: 0 to: -target.height duration: 500 easing.type: Easing.OutSine } } property Component replaceTransition: pushHTransition } } /// @endcond }