diff --git a/src/activities/menu/ConfigurationItem.qml b/src/activities/menu/ConfigurationItem.qml index e54df44e8..8951305e6 100644 --- a/src/activities/menu/ConfigurationItem.qml +++ b/src/activities/menu/ConfigurationItem.qml @@ -1,850 +1,894 @@ /* GCompris - ConfigurationItem.qml * * 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 . */ import QtQuick 2.6 import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 import QtMultimedia 5.0 import "../../core" import "qrc:/gcompris/src/core/core.js" as Core Item { id: dialogConfig property var languages: allLangs.languages height: column.height LanguageList { id: allLangs } Column { id: column spacing: 10 width: parent.width move: Transition { NumberAnimation { properties: "x,y"; duration: 120 } } // Put configuration here Row { id: demoModeBox width: parent.width spacing: 10 property bool checked: !ApplicationSettings.isDemoMode Image { sourceSize.height: 50 * ApplicationInfo.ratio source: demoModeBox.checked ? "qrc:/gcompris/src/core/resource/apply.svg" : "qrc:/gcompris/src/core/resource/cancel.svg" MouseArea { anchors.fill: parent onClicked: { if(ApplicationSettings.isDemoMode) ApplicationSettings.isDemoMode = false } } } Button { width: parent.parent.width - 50 * ApplicationInfo.ratio - 10 * 2 height: parent.height enabled: ApplicationSettings.isDemoMode anchors.leftMargin: 10 anchors.verticalCenter: parent.verticalCenter text: demoModeBox.checked ? qsTr("You have the full version") : qsTr("Buy the full version").toUpperCase() style: ButtonStyle { background: Rectangle { implicitWidth: 100 implicitHeight: 25 border.width: control.activeFocus ? 4 : 2 border.color: "black" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: control.pressed ? "#87ff5c" : ApplicationSettings.isDemoMode ? "#ffe85c" : "#EEEEEE"} GradientStop { position: 1 ; color: control.pressed ? "#44ff00" : ApplicationSettings.isDemoMode ? "#f8d600" : "#AAAAAA"} } } label: GCText { text: control.text horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap } } onClicked: { if(ApplicationSettings.activationMode == 1) { if(ApplicationSettings.isDemoMode) ApplicationSettings.isDemoMode = false } else if(ApplicationSettings.activationMode == 2) { activationCodeEntry.visible = !activationCodeEntry.visible } } } } Column { id: activationCodeEntry width: parent.width spacing: 10 visible: false opacity: 0 Behavior on opacity { NumberAnimation { duration: 200 } } onVisibleChanged: { if(visible) { activationInput.forceActiveFocus() activationInput.cursorPosition = 0 opacity = 1 } else { activationInput.focus = false opacity = 0 } } GCText { id: activationInstruction fontSize: regularSize color: "black" style: Text.Outline styleColor: "white" horizontalAlignment: Text.AlignHCenter width: parent.width wrapMode: TextEdit.WordWrap text: qsTr("On https://gcompris.net " + "you will find the instructions to obtain an activation code.") Component.onCompleted: ApplicationInfo.isDownloadAllowed ? linkActivated.connect(Qt.openUrlExternally) : null } TextInput { id: activationInput width: parent.width focus: true font.weight: Font.DemiBold font.pointSize: ApplicationSettings.baseFontSize + 14 * ApplicationInfo.fontRatio color: 'black' horizontalAlignment: Text.AlignHCenter inputMask: '>HHHH-HHHH-HHHH;#' text: ApplicationSettings.codeKey onTextChanged: { var code = text.replace(/-/g,'') var codeValidity = ApplicationSettings.checkActivationCode(code); switch (codeValidity) { case 0: activationMsg.text = qsTr('Enter your activation code'); break; case 1: activationMsg.text = qsTr('Sorry, your code is too old for this version of GCompris'); break; case 2: activationMsg.text = qsTr('Your code is valid, thanks a lot for your support'); activationCodeEntry.visible = false ApplicationSettings.codeKey = code break; } } } GCText { id: activationMsg width: parent.width color: "black" fontSize: regularSize horizontalAlignment: TextInput.AlignHCenter wrapMode: TextEdit.WordWrap } } GCDialogCheckBox { id: displayLockedActivitiesBox text: qsTr("Show locked activities") visible: ApplicationSettings.isDemoMode checked: showLockedActivities onCheckedChanged: { showLockedActivities = checked; } } GCDialogCheckBox { id: enableAudioVoicesBox text: qsTr("Enable audio voices") checked: isAudioVoicesEnabled onCheckedChanged: { isAudioVoicesEnabled = checked; } } GCDialogCheckBox { id: enableAudioEffectsBox text: qsTr("Enable audio effects") checked: isAudioEffectsEnabled onCheckedChanged: { isAudioEffectsEnabled = checked; } } + Flow { + spacing: 5 + width: parent.width + GCSlider { + id: audioEffectsVolumeSlider + width: 250 * ApplicationInfo.ratio + maximumValue: 10 + minimumValue: 0 + value: audioEffectsVolume * 10 + onValueChanged: ApplicationSettings.audioEffectsVolume = value / 10; + scrollEnabled: false + } + GCText { + id: audioEffectsVolumeText + text: qsTr("Audio effects volume") + fontSize: mediumSize + wrapMode: Text.WordWrap + } + } + GCDialogCheckBox { id: enableBackgroundMusicBox text: qsTr("Enable background music") checked: isBackgroundMusicEnabled onCheckedChanged: { isBackgroundMusicEnabled = checked; } } Flow { width: parent.width spacing: 5 * ApplicationInfo.ratio GCText { text: qsTr("Background Music") fontSize: mediumSize height: 50 * ApplicationInfo.ratio } // Padding Item { height: 1 width: 10 * ApplicationInfo.ratio } Button { id: backgroundMusicName height: 30 * ApplicationInfo.ratio text: { if(backgroundMusic.playbackState != Audio.PlayingState) return qsTr("Not playing") return configItem.extractMusicNameFromPath(backgroundMusic.source) } style: GCButtonStyle {} onClicked: { dialogConfig.visible = false backgroundMusicList.visible = true } } // Padding Item { height: 1 width: 10 * ApplicationInfo.ratio } Image { source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.height: Math.min(50 * ApplicationInfo.ratio, parent.width / 8) MouseArea { anchors.fill: parent enabled: (backgroundMusic.playbackState == Audio.PlayingState && !backgroundMusic.muted) onClicked: backgroundMusic.nextAudio() } } } + Flow { + spacing: 5 + width: parent.width + GCSlider { + id: backgroundMusicVolumeSlider + width: 250 * ApplicationInfo.ratio + maximumValue: 10 + minimumValue: 0 + value: backgroundMusicVolume * 10 + onValueChanged: ApplicationSettings.backgroundMusicVolume = value / 10; + scrollEnabled: false + } + GCText { + id: backgroundMusicVolumeText + text: qsTr("Background music volume") + fontSize: mediumSize + wrapMode: Text.WordWrap + } + } + GCDialogCheckBox { id: enableFullscreenBox text: qsTr("Fullscreen") checked: isFullscreen onCheckedChanged: { isFullscreen = checked; } visible: !ApplicationInfo.isMobile } GCDialogCheckBox { id: enableVirtualKeyboardBox text: qsTr("Virtual Keyboard") checked: isVirtualKeyboard onCheckedChanged: { isVirtualKeyboard = checked; } } GCDialogCheckBox { id: enableAutomaticDownloadsBox checked: isAutomaticDownloadsEnabled text: qsTr("Enable automatic downloads/updates of sound files") visible: ApplicationInfo.isDownloadAllowed onCheckedChanged: { isAutomaticDownloadsEnabled = checked; } } /* Technically wordset config is a string that holds the wordset name or '' for the * internal wordset. But as we support only internal and words its best to show the * user a boolean choice. */ GCDialogCheckBox { id: wordsetBox checked: DownloadManager.isDataRegistered("words") text: enabled ? qsTr("Use full word image set") : qsTr("Download full word image set") visible: ApplicationInfo.isDownloadAllowed enabled: !DownloadManager.isDataRegistered("words") onCheckedChanged: { wordset = checked ? 'data2/words/words.rcc' : ''; } } GCDialogCheckBox { id: sectionVisibleBox checked: sectionVisible text: qsTr("The activity section menu is visible") onCheckedChanged: { sectionVisible = checked; } } Flow { spacing: 5 width: parent.width GCComboBox { id: fontBox model: fonts background: dialogActivityConfig label: qsTr("Font selector") } } Flow { spacing: 5 width: parent.width GCSlider { id: baseFontSizeSlider width: 250 * ApplicationInfo.ratio maximumValue: ApplicationSettings.baseFontSizeMax minimumValue: ApplicationSettings.baseFontSizeMin value: baseFontSize onValueChanged: ApplicationSettings.baseFontSize = value; scrollEnabled: false } GCText { id: baseFontSizeText text: qsTr("Font size") fontSize: mediumSize wrapMode: Text.WordWrap } Button { height: 30 * ApplicationInfo.ratio text: qsTr("Default"); style: GCButtonStyle {} onClicked: baseFontSizeSlider.value = 0.0 } } Flow { spacing: 5 width: parent.width GCComboBox { id: fontCapitalizationBox model: fontCapitalizationModel background: dialogActivityConfig label: qsTr("Font Capitalization") } } Flow { spacing: 5 width: parent.width GCSlider { id: fontLetterSpacingSlider width: 250 * ApplicationInfo.ratio maximumValue: ApplicationSettings.fontLetterSpacingMax minimumValue: ApplicationSettings.fontLetterSpacingMin value: fontLetterSpacing onValueChanged: ApplicationSettings.fontLetterSpacing = value; scrollEnabled: false } GCText { id: fontLetterSpacingText text: qsTr("Font letter spacing") fontSize: mediumSize wrapMode: Text.WordWrap } Button { height: 30 * ApplicationInfo.ratio text: qsTr("Default"); style: GCButtonStyle {} onClicked: fontLetterSpacingSlider.value = ApplicationSettings.fontLetterSpacingMin } } Flow { spacing: 5 width: parent.width GCComboBox { id: languageBox model: dialogConfig.languages background: dialogActivityConfig onCurrentIndexChanged: voicesRow.localeChanged(); label: qsTr("Language selector") } } Flow { id: voicesRow width: parent.width spacing: 5 * ApplicationInfo.ratio property bool haveLocalResource: false function localeChanged() { var language = dialogConfig.languages[languageBox.currentIndex].text; voicesRow.haveLocalResource = DownloadManager.isDataRegistered( "voices-" + ApplicationInfo.CompressedAudio + "/" + ApplicationInfo.getVoicesLocale(dialogConfig.languages[languageBox.currentIndex].locale) ) } Connections { target: DownloadManager onDownloadFinished: voicesRow.localeChanged() } GCText { id: voicesText text: qsTr("Localized voices") fontSize: mediumSize wrapMode: Text.WordWrap } Image { id: voicesImage sourceSize.height: 30 * ApplicationInfo.ratio source: voicesRow.haveLocalResource ? "qrc:/gcompris/src/core/resource/apply.svg" : "qrc:/gcompris/src/core/resource/cancel.svg" } Button { id: voicesButton height: 30 * ApplicationInfo.ratio visible: ApplicationInfo.isDownloadAllowed text: voicesRow.haveLocalResource ? qsTr("Check for updates") : qsTr("Download") style: GCButtonStyle {} onClicked: { if (DownloadManager.downloadResource( DownloadManager.getVoicesResourceForLocale(dialogConfig.languages[languageBox.currentIndex].locale))) { var downloadDialog = Core.showDownloadDialog(dialogConfig.parent.rootItem, {}); } } } } Flow { width: parent.width spacing: 5 * ApplicationInfo.ratio GCText { text: qsTr("Difficulty filter:") fontSize: mediumSize height: 50 * ApplicationInfo.ratio } // Padding Item { height: 1 width: 10 * ApplicationInfo.ratio } Image { source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.height: Math.min(50 * ApplicationInfo.ratio, parent.width / 8) MouseArea { anchors.fill: parent onClicked: { filterRepeater.setMin(filterRepeater.min + 1) } } } // Padding Item { height: 1 width: 5 * ApplicationInfo.ratio } // Level filtering Repeater { id: filterRepeater model: 6 property int min: ApplicationSettings.filterLevelMin property int max: ApplicationSettings.filterLevelMax function setMin(value) { var newMin if(min < 1) newMin = 1 else if(min > 6) newMin = 6 else if(max >= value) newMin = value if(newMin) ApplicationSettings.filterLevelMin = newMin } function setMax(value) { var newMax if(max < 1) newMax = 1 else if(max > 6) newMax = 6 else if(min <= value) newMax = value if(newMax) ApplicationSettings.filterLevelMax = newMax } Image { source: "qrc:/gcompris/src/core/resource/difficulty" + (modelData + 1) + ".svg"; sourceSize.width: Math.min(50 * ApplicationInfo.ratio, parent.width / 8) opacity: modelData + 1 >= filterRepeater.min && modelData + 1 <= filterRepeater.max ? 1 : 0.4 property int value: modelData + 1 MouseArea { anchors.fill: parent onClicked: { if(parent.value < filterRepeater.max) { if(parent.opacity == 1) filterRepeater.setMin(parent.value + 1) else filterRepeater.setMin(parent.value) } else if(parent.value > filterRepeater.min) { if(parent.opacity == 1) filterRepeater.setMax(parent.value - 1) else filterRepeater.setMax(parent.value) } } } } } // Padding Item { height: 1 width: 5 * ApplicationInfo.ratio } Image { source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.height: Math.min(50 * ApplicationInfo.ratio, parent.width / 8) MouseArea { anchors.fill: parent onClicked: { filterRepeater.setMax(filterRepeater.max - 1) } } } } } property bool showLockedActivities: ApplicationSettings.showLockedActivities property bool isAudioVoicesEnabled: ApplicationSettings.isAudioVoicesEnabled property bool isAudioEffectsEnabled: ApplicationSettings.isAudioEffectsEnabled property bool isBackgroundMusicEnabled: ApplicationSettings.isBackgroundMusicEnabled property bool isFullscreen: ApplicationSettings.isFullscreen property bool isVirtualKeyboard: ApplicationSettings.isVirtualKeyboard property bool isAutomaticDownloadsEnabled: ApplicationSettings.isAutomaticDownloadsEnabled property bool sectionVisible: ApplicationSettings.sectionVisible property string wordset: ApplicationSettings.wordset property var filteredBackgroundMusic: ApplicationSettings.filteredBackgroundMusic property var allBackgroundMusic: ApplicationInfo.getBackgroundMusicFromRcc() property int baseFontSize // don't bind to ApplicationSettings.baseFontSize property real fontLetterSpacing // don't bind to ApplicationSettings.fontLetterSpacing // or we get a binding loop warning + property real backgroundMusicVolume + property real audioEffectsVolume function extractMusicNameFromPath(musicPath) { var musicDirectoryPath = ApplicationInfo.getAudioFilePath("backgroundMusic/") var musicName = String(musicPath) musicName = musicName.slice(musicDirectoryPath.length, musicName.length) return musicName.slice(0, musicName.lastIndexOf('.')) } function loadFromConfig() { // Synchronize settings with data showLockedActivities = ApplicationSettings.showLockedActivities isAudioVoicesEnabled = ApplicationSettings.isAudioVoicesEnabled enableAudioVoicesBox.checked = isAudioVoicesEnabled isAudioEffectsEnabled = ApplicationSettings.isAudioEffectsEnabled enableAudioEffectsBox.checked = isAudioEffectsEnabled isBackgroundMusicEnabled = ApplicationSettings.isBackgroundMusicEnabled enableBackgroundMusicBox.checked = isBackgroundMusicEnabled isFullscreen = ApplicationSettings.isFullscreen enableFullscreenBox.checked = isFullscreen isVirtualKeyboard = ApplicationSettings.isVirtualKeyboard enableVirtualKeyboardBox.checked = isVirtualKeyboard isAutomaticDownloadsEnabled = ApplicationSettings.isAutomaticDownloadsEnabled enableAutomaticDownloadsBox.checked = isAutomaticDownloadsEnabled sectionVisible = ApplicationSettings.sectionVisible sectionVisibleBox.checked = sectionVisible wordset = ApplicationSettings.wordset wordsetBox.checked = DownloadManager.isDataRegistered("words") || ApplicationSettings.wordset == 'data2/words/words.rcc' wordsetBox.enabled = !DownloadManager.isDataRegistered("words") baseFontSize = ApplicationSettings.baseFontSize fontLetterSpacing = ApplicationSettings.fontLetterSpacing + backgroundMusicVolume = ApplicationSettings.backgroundMusicVolume + audioEffectsVolume = ApplicationSettings.audioEffectsVolume filteredBackgroundMusic = ApplicationSettings.filteredBackgroundMusic allBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() if(filteredBackgroundMusic.length === 0) filteredBackgroundMusic = allBackgroundMusic // Set locale for(var i = 0 ; i < dialogConfig.languages.length ; i ++) { if(dialogConfig.languages[i].locale === ApplicationSettings.locale) { languageBox.currentIndex = i; break; } } // Set font for(var i = 0 ; i < fonts.count ; i ++) { if(fonts.get(i).text == ApplicationSettings.font) { fontBox.currentIndex = i; break; } } // Set font capitalization for(var i = 0 ; i < fontCapitalizationModel.length ; i ++) { if(fontCapitalizationModel[i].value == ApplicationSettings.fontCapitalization) { fontCapitalizationBox.currentIndex = i; break; } } } function save() { ApplicationSettings.showLockedActivities = showLockedActivities ApplicationSettings.isAudioVoicesEnabled = isAudioVoicesEnabled ApplicationSettings.isAudioEffectsEnabled = isAudioEffectsEnabled ApplicationSettings.isBackgroundMusicEnabled = isBackgroundMusicEnabled ApplicationSettings.filteredBackgroundMusic = filteredBackgroundMusic ApplicationSettings.isFullscreen = isFullscreen ApplicationSettings.isVirtualKeyboard = isVirtualKeyboard ApplicationSettings.isAutomaticDownloadsEnabled = isAutomaticDownloadsEnabled ApplicationSettings.sectionVisible = sectionVisible ApplicationSettings.wordset = wordset ApplicationSettings.isEmbeddedFont = fonts.get(fontBox.currentIndex).isLocalResource; ApplicationSettings.font = fonts.get(fontBox.currentIndex).text ApplicationSettings.fontCapitalization = fontCapitalizationModel[fontCapitalizationBox.currentIndex].value ApplicationSettings.saveBaseFontSize(); ApplicationSettings.notifyFontLetterSpacingChanged(); if (ApplicationSettings.locale != dialogConfig.languages[languageBox.currentIndex].locale) { ApplicationSettings.locale = dialogConfig.languages[languageBox.currentIndex].locale if(ApplicationInfo.isDownloadAllowed && !DownloadManager.isDataRegistered( "voices-" + ApplicationInfo.CompressedAudio + "/" + ApplicationInfo.getVoicesLocale(dialogConfig.languages[languageBox.currentIndex].locale) )) { // ask for downloading new voices Core.showMessageDialog(main, qsTr("You selected a new locale. You need to restart GCompris to play in your new locale.
Do you want to download the corresponding sound files now?"), qsTr("Yes"), function() { // yes -> start download if (DownloadManager.downloadResource( DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale))) var downloadDialog = Core.showDownloadDialog(main, {}); }, qsTr("No"), null, null ); } else { // check for updates or/and register new voices DownloadManager.updateResource( DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)) } } // download words.rcc if needed if(ApplicationSettings.wordset != "") { // we want to use the external dataset, it is either in // words/words.rcc or full-${CA}.rcc if(DownloadManager.isDataRegistered("words")) { // we either have it, we try to update in the background // or we are downloading it if(DownloadManager.haveLocalResource(wordset)) DownloadManager.updateResource(wordset) } else { // download automatically if automatic download else ask for download if(isAutomaticDownloadsEnabled) { var prevAutomaticDownload = ApplicationSettings.isAutomaticDownloadsEnabled ApplicationSettings.isAutomaticDownloadsEnabled = true; DownloadManager.updateResource(wordset); ApplicationSettings.isAutomaticDownloadsEnabled = prevAutomaticDownload } else { Core.showMessageDialog(main, qsTr("The images for several activities are not yet installed. ") + qsTr("Do you want to download them now?"), qsTr("Yes"), function() { if (DownloadManager.downloadResource(wordset)) var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); }, qsTr("No"), function() { ApplicationSettings.wordset = '' }, null ); } } } // download backgroundMusic.rcc if needed if(DownloadManager.isDataRegistered("backgroundMusic")) { // we either have it, we try to update in the background // or we are downloading it if(DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) DownloadManager.updateResource(DownloadManager.getBackgroundMusicResources()) } else { // download automatically if automatic download else ask for download if(isAutomaticDownloadsEnabled) { var prevAutomaticDownload = ApplicationSettings.isAutomaticDownloadsEnabled ApplicationSettings.isAutomaticDownloadsEnabled = true; DownloadManager.updateResource(DownloadManager.getBackgroundMusicResources()); ApplicationSettings.isAutomaticDownloadsEnabled = prevAutomaticDownload } else { Core.showMessageDialog(main, qsTr("The background music is not yet installed. ") + 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 ); } } } ListModel { id: fonts Component.onCompleted: { var systemFonts = Qt.fontFamilies(); var rccFonts = ApplicationInfo.getFontsFromRcc(); // Remove explicitly all *symbol* and *ding* fonts var excludedFonts = ApplicationInfo.getSystemExcludedFonts(); excludedFonts.push("ding"); excludedFonts.push("symbol"); // first display fonts from rcc for(var i = 0 ; i < rccFonts.length ; ++ i) { // Append fonts from resources fonts.append({ "text": rccFonts[i], "isLocalResource": true }); } for(var i = 0 ; i < systemFonts.length ; ++ i) { var isExcluded = false; var systemFont = systemFonts[i].toLowerCase(); // Remove symbol fonts for(var j = 0 ; j < excludedFonts.length ; ++ j) { if(systemFont.indexOf(excludedFonts[j].toLowerCase()) != -1) { isExcluded = true; break; } } // Remove fonts from rcc (if you have a default font from rcc, Qt will add it to systemFonts) for(var j = 0 ; j < rccFonts.length ; ++ j) { if(rccFonts[j].toLowerCase().indexOf(systemFont) != -1) { isExcluded = true; break; } } // Finally, we know if we add this font or not if(!isExcluded) { fonts.append({ "text": systemFonts[i], "isLocalResource": false }); } } } } property var fontCapitalizationModel: [ { text: qsTr("Mixed case (default)"), value: Font.MixedCase }, { text: qsTr("All uppercase"), value: Font.AllUppercase }, { text: qsTr("All lowercase"), value: Font.AllLowercase } ] function isFilteredBackgroundMusicChanged() { initialFilteredMusic = ApplicationSettings.filteredBackgroundMusic if(initialFilteredMusic.length != filteredBackgroundMusic.length) return true for(var i = 0; i < initialFilteredMusic.length; i++) if(filteredBackgroundMusic.indexOf(initialFilteredMusic[i]) == -1) return true return false } function hasConfigChanged() { return (ApplicationSettings.locale !== dialogConfig.languages[languageBox.currentIndex].locale || (ApplicationSettings.sectionVisible != sectionVisible) || (ApplicationSettings.wordset != wordset) || (ApplicationSettings.font != fonts.get(fontBox.currentIndex).text) || (ApplicationSettings.isEmbeddedFont != fonts.get(fontBox.currentIndex).isLocalResource) || (ApplicationSettings.isEmbeddedFont != fonts.get(fontBox.currentIndex).isLocalResource) || (ApplicationSettings.fontCapitalization != fontCapitalizationModel[(fontcapitalizationBox.currentIndex)].value) || (ApplicationSettings.fontLetterSpacing != fontLetterSpacing) || (ApplicationSettings.isAudioVoicesEnabled != isAudioVoicesEnabled) || (ApplicationSettings.isAudioEffectsEnabled != isAudioEffectsEnabled) || (ApplicationSettings.isBackgroundMusicEnabled != isBackgroundMusicEnabled) || (ApplicationSettings.isFullscreen != isFullscreen) || (ApplicationSettings.isVirtualKeyboard != isVirtualKeyboard) || (ApplicationSettings.isAutomaticDownloadsEnabled != isAutomaticDownloadsEnabled) || (ApplicationSettings.baseFontSize != baseFontSize) || (ApplicationSettings.showLockedActivities != showLockedActivities) || isFilteredBackgroundMusicChanged() ); } } diff --git a/src/core/ApplicationSettings.cpp b/src/core/ApplicationSettings.cpp index f6da5b541..f0c54c03d 100644 --- a/src/core/ApplicationSettings.cpp +++ b/src/core/ApplicationSettings.cpp @@ -1,524 +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 *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, 1).toReal(); + m_audioEffectsVolume = m_config.value(AUDIO_EFFECTS_VOLUME_KEY, 1).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/ApplicationSettings.h b/src/core/ApplicationSettings.h index 64e95844a..93fa0512d 100644 --- a/src/core/ApplicationSettings.h +++ b/src/core/ApplicationSettings.h @@ -1,680 +1,708 @@ /* GCompris - ApplicationSettings.h * * 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 . */ #ifndef APPLICATIONSETTINGS_H #define APPLICATIONSETTINGS_H #include #include #include #include #include #include #include #define GC_DEFAULT_LOCALE "system" /** * @class ApplicationSettings * @short Singleton that contains GCompris' persistent settings. * @ingroup infrastructure * * Settings are persisted using QSettings, which stores them in platform * specific locations. * * The settings are subdivided in different groups of settings. * * [General] settings are mostly changeable by users in the DialogConfig * dialog. * * [Admin] and [Internal] settings are not changeable by the * user and used for internal purposes. Should only be changed if you really know * what you are doing. * * The [Favorite] group is auto-generated from the favorite activities * selected by a user. * * Besides these global settings there is one group for each activity that * stores persistent settings. * * Settings defaults are defined in the source code. * * @sa DialogActivityConfig */ class ApplicationSettings : public QObject { Q_OBJECT /* General group */ /** * Whether to show locked activities. * False if in Demo mode, true otherwise. */ Q_PROPERTY(bool showLockedActivities READ showLockedActivities WRITE setShowLockedActivities NOTIFY showLockedActivitiesChanged) /** * Whether audio voices/speech should be enabled. */ Q_PROPERTY(bool isAudioVoicesEnabled READ isAudioVoicesEnabled WRITE setIsAudioVoicesEnabled NOTIFY audioVoicesEnabledChanged) /** * Whether audio effects should be enabled. */ Q_PROPERTY(bool isAudioEffectsEnabled READ isAudioEffectsEnabled WRITE setIsAudioEffectsEnabled NOTIFY audioEffectsEnabledChanged) /** Whether background music should be enabled. */ Q_PROPERTY(bool isBackgroundMusicEnabled READ isBackgroundMusicEnabled WRITE setIsBackgroundMusicEnabled NOTIFY backgroundMusicEnabledChanged) /** * Whether GCompris should run in fullscreen mode. */ Q_PROPERTY(bool isFullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged) /** * Window Height on Application's Startup */ Q_PROPERTY(quint32 previousHeight READ previousHeight WRITE setPreviousHeight NOTIFY previousHeightChanged) /** * Window Width on Application's Startup */ Q_PROPERTY(quint32 previousWidth READ previousWidth WRITE setPreviousWidth NOTIFY previousWidthChanged) /** * Whether on-screen keyboard should be enabled per default in activities * that use it. */ Q_PROPERTY(bool isVirtualKeyboard READ isVirtualKeyboard WRITE setVirtualKeyboard NOTIFY virtualKeyboardChanged) /** * Locale string for currently active language. */ Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged) /** * Currently selected font. */ Q_PROPERTY(QString font READ font WRITE setFont NOTIFY fontChanged) /** * Whether currently active font is a shipped font (or a system font). * * Updated automatically. * @sa font */ Q_PROPERTY(bool isEmbeddedFont READ isEmbeddedFont WRITE setIsEmbeddedFont NOTIFY embeddedFontChanged) /** * Font Capitalization * * Force all texts to be rendered in UpperCase, LowerCase or MixedCase (default) * @sa font */ Q_PROPERTY(quint32 fontCapitalization READ fontCapitalization WRITE setFontCapitalization NOTIFY fontCapitalizationChanged) /** * Font letter spacing * * Change the letter spacing of all the texts * @sa font */ Q_PROPERTY(qreal fontLetterSpacing READ fontLetterSpacing WRITE setFontLetterSpacing NOTIFY fontLetterSpacingChanged) /** * Minimum allowed value for font spacing letter. * * Constant value: +0.0 */ Q_PROPERTY(qreal fontLetterSpacingMin READ fontLetterSpacingMin CONSTANT) /** * Maximum allowed value for font spacing letter. * * Constant value: +8.0 */ Q_PROPERTY(qreal fontLetterSpacingMax READ fontLetterSpacingMax CONSTANT) /** * Whether downloads/updates of resource files should be done automatically, * without user-interaction. * * Note, that on Android GCompris currently can't distinguish Wifi * from mobile data connections (cf. Qt ticket #30394). */ Q_PROPERTY(bool isAutomaticDownloadsEnabled READ isAutomaticDownloadsEnabled WRITE setIsAutomaticDownloadsEnabled NOTIFY automaticDownloadsEnabledChanged) /** * Minimum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMin READ filterLevelMin WRITE setFilterLevelMin NOTIFY filterLevelMinChanged) /** * Maximum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMax READ filterLevelMax WRITE setFilterLevelMax NOTIFY filterLevelMaxChanged) /** * Whether in demo mode. */ Q_PROPERTY(bool isDemoMode READ isDemoMode WRITE setDemoMode NOTIFY demoModeChanged) /** * Activation code key. */ Q_PROPERTY(QString codeKey READ codeKey WRITE setCodeKey NOTIFY codeKeyChanged) /** * Activation mode. */ Q_PROPERTY(quint32 activationMode READ activationMode CONSTANT) /** * Whether kiosk mode is currently active. */ Q_PROPERTY(bool isKioskMode READ isKioskMode WRITE setKioskMode NOTIFY kioskModeChanged) /** * Whether the section selection row is visible in the menu view. */ Q_PROPERTY(bool sectionVisible READ sectionVisible WRITE setSectionVisible NOTIFY sectionVisibleChanged) /** * The name of the default wordset to use. If empty then the internal sample wordset is used. */ Q_PROPERTY(QString wordset READ wordset WRITE setWordset NOTIFY wordsetChanged) /** * Current base font-size used for font scaling. * * This setting is the basis for application-wide font-scaling. A value * of 0 means to use the font-size as set by the application. Other values * between @ref baseFontSizeMin and @ref baseFontSizeMax enforce * font-scaling. * * @sa GCText.fontSize baseFontSizeMin baseFontSizeMax */ Q_PROPERTY(int baseFontSize READ baseFontSize WRITE setBaseFontSize NOTIFY baseFontSizeChanged) + /** + * Stores the background music volume set by the user. + */ + Q_PROPERTY(qreal backgroundMusicVolume READ backgroundMusicVolume WRITE setBackgroundMusicVolume NOTIFY backgroundMusicVolumeChanged) + + /** + * Stores the audio effects volume set by the user. + */ + Q_PROPERTY(qreal audioEffectsVolume READ audioEffectsVolume WRITE setAudioEffectsVolume NOTIFY audioEffectsVolumeChanged) + /** * Minimum allowed value for font-scaling. * * Constant value: -7 */ Q_PROPERTY(int baseFontSizeMin READ baseFontSizeMin CONSTANT) /** * Maximum allowed value for font-scaling. * * Constant value: +7 */ Q_PROPERTY(int baseFontSizeMax READ baseFontSizeMax CONSTANT) // admin group /** * Base-URL for resource downloads. * * @sa DownloadManager */ Q_PROPERTY(QString downloadServerUrl READ downloadServerUrl WRITE setDownloadServerUrl NOTIFY downloadServerUrlChanged) /** * Path where resources are downloaded and stored. * * @sa DownloadManager */ Q_PROPERTY(QString cachePath READ cachePath WRITE setCachePath NOTIFY cachePathChanged) /** * Return the platform specific path for storing data shared between apps * * On Android: /storage/emulated/0/GCompris (>= Android 4.2), * /storage/sdcard0/GCompris (< Android 4.2) * On Linux: $HOME/local/share/GCompris */ Q_PROPERTY(QString userDataPath READ userDataPath WRITE setUserDataPath NOTIFY userDataPathChanged) /** * Define the renderer used. * Either openGL or software renderer (only for Qt >= 5.8) */ Q_PROPERTY(QString renderer READ renderer WRITE setRenderer NOTIFY rendererChanged) /** * Stores the filtered background music playlist by the user. */ Q_PROPERTY(QStringList filteredBackgroundMusic READ filteredBackgroundMusic WRITE setFilteredBackgroundMusic NOTIFY filteredBackgroundMusicChanged) // internal group Q_PROPERTY(quint32 exeCount READ exeCount WRITE setExeCount NOTIFY exeCountChanged) // keep last version ran. If different than ApplicationInfo.GCVersionCode(), it means a new version is running Q_PROPERTY(int lastGCVersionRan READ lastGCVersionRan WRITE setLastGCVersionRan NOTIFY lastGCVersionRanChanged) // no group Q_PROPERTY(bool isBarHidden READ isBarHidden WRITE setBarHidden NOTIFY barHiddenChanged) public: /// @cond INTERNAL_DOCS explicit ApplicationSettings(const QString &configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/gcompris/" + GCOMPRIS_APPLICATION_NAME + ".conf", QObject *parent = 0); virtual ~ApplicationSettings(); // It is not recommended to create a singleton of Qml Singleton registered // object but we could not found a better way to let us access ApplicationInfo // on the C++ side. All our test shows that it works. static ApplicationSettings *getInstance() { if(!m_instance) { m_instance = new ApplicationSettings(); } return m_instance; } static QObject *applicationSettingsProvider(QQmlEngine *engine, QJSEngine *scriptEngine); bool showLockedActivities() const { return m_showLockedActivities; } void setShowLockedActivities(const bool newMode) { m_showLockedActivities = newMode; emit showLockedActivitiesChanged(); } bool isAudioVoicesEnabled() const { return m_isAudioVoicesEnabled; } void setIsAudioVoicesEnabled(const bool newMode) { m_isAudioVoicesEnabled = newMode; emit audioVoicesEnabledChanged(); } bool isAudioEffectsEnabled() const { return m_isAudioEffectsEnabled; } void setIsAudioEffectsEnabled(const bool newMode) { m_isAudioEffectsEnabled = newMode; emit audioEffectsEnabledChanged(); } bool isBackgroundMusicEnabled() const { return m_isBackgroundMusicEnabled; } void setIsBackgroundMusicEnabled(const bool newMode) { m_isBackgroundMusicEnabled = newMode; emit backgroundMusicEnabledChanged(); } bool isFullscreen() const { return m_isFullscreen; } void setFullscreen(const bool newMode) { if(m_isFullscreen != newMode) { m_isFullscreen = newMode; emit fullscreenChanged(); } } quint32 previousHeight() const { return m_previousHeight; } void setPreviousHeight(quint32 height) { if(m_previousHeight != height) { m_previousHeight = height; emit previousHeightChanged(); } } quint32 previousWidth() const { return m_previousWidth; } void setPreviousWidth(quint32 width) { if(m_previousWidth != width) { m_previousWidth = width; emit previousWidthChanged(); } } bool isVirtualKeyboard() const { return m_isVirtualKeyboard; } void setVirtualKeyboard(const bool newMode) { m_isVirtualKeyboard = newMode; emit virtualKeyboardChanged(); } QString locale() const { return m_locale; } void setLocale(const QString &newLocale) { m_locale = newLocale; emit localeChanged(); } QString font() const { return m_font; } void setFont(const QString &newFont) { m_font = newFont; emit fontChanged(); } bool isEmbeddedFont() const { return m_isEmbeddedFont; } void setIsEmbeddedFont(const bool newIsEmbeddedFont) { m_isEmbeddedFont = newIsEmbeddedFont; emit embeddedFontChanged(); } quint32 fontCapitalization() const { return m_fontCapitalization; } void setFontCapitalization(quint32 newFontCapitalization) { m_fontCapitalization = newFontCapitalization; emit fontCapitalizationChanged(); } qreal fontLetterSpacing() const { return m_fontLetterSpacing; } void setFontLetterSpacing(qreal newFontLetterSpacing) { m_fontLetterSpacing = newFontLetterSpacing; emit fontLetterSpacingChanged(); } qreal fontLetterSpacingMin() const { return m_fontLetterSpacingMin; } qreal fontLetterSpacingMax() const { return m_fontLetterSpacingMax; } bool isAutomaticDownloadsEnabled() const; void setIsAutomaticDownloadsEnabled(const bool newIsAutomaticDownloadsEnabled); quint32 filterLevelMin() const { return m_filterLevelMin; } void setFilterLevelMin(const quint32 newFilterLevelMin) { m_filterLevelMin = newFilterLevelMin; emit filterLevelMinChanged(); } quint32 filterLevelMax() const { return m_filterLevelMax; } void setFilterLevelMax(const quint32 newFilterLevelMax) { m_filterLevelMax = newFilterLevelMax; emit filterLevelMaxChanged(); } bool isDemoMode() const { return m_isDemoMode; } void setDemoMode(const bool newMode); QString codeKey() const { return m_codeKey; } void setCodeKey(const QString &newCodeKey) { m_codeKey = newCodeKey; emit codeKeyChanged(); } /** * @brief activationMode * @return 0: no, 1: inapp, 2: internal */ quint32 activationMode() const { return m_activationMode; } bool isKioskMode() const { return m_isKioskMode; } void setKioskMode(const bool newMode) { m_isKioskMode = newMode; emit kioskModeChanged(); } /** * Check validity of the activation code * @param code An activation code to check * @returns 0 if the code is not valid or we don't know yet * 1 if the code is valid but out of date * 2 if the code is valid and under 2 years */ Q_INVOKABLE uint checkActivationCode(const QString &code); /** * Check Payment API * Call a payment system to sync our demoMode state with it */ void checkPayment(); // Called by the payment system void bought(const bool isBought) { if(m_isDemoMode != !isBought) { m_isDemoMode = !isBought; emit demoModeChanged(); } } bool sectionVisible() const { return m_sectionVisible; } void setSectionVisible(const bool newMode) { qDebug() << "c++ setSectionVisible=" << newMode; m_sectionVisible = newMode; emit sectionVisibleChanged(); } QString wordset() const { return m_wordset; } void setWordset(const QString &newWordset) { m_wordset = newWordset; emit wordsetChanged(); } QString downloadServerUrl() const { return m_downloadServerUrl; } void setDownloadServerUrl(const QString &newDownloadServerUrl) { m_downloadServerUrl = newDownloadServerUrl; emit downloadServerUrlChanged(); } QString cachePath() const { return m_cachePath; } void setCachePath(const QString &newCachePath) { m_cachePath = newCachePath; emit cachePathChanged(); } QString userDataPath() const { return m_userDataPath; } void setUserDataPath(const QString &newUserDataPath) { m_userDataPath = newUserDataPath; emit userDataPathChanged(); } quint32 exeCount() const { return m_exeCount; } void setExeCount(const quint32 newExeCount) { m_exeCount = newExeCount; emit exeCountChanged(); } bool isBarHidden() const { return m_isBarHidden; } void setBarHidden(const bool newBarHidden) { m_isBarHidden = newBarHidden; emit barHiddenChanged(); } int baseFontSize() const { return m_baseFontSize; } void setBaseFontSize(const int newBaseFontSize) { m_baseFontSize = qMax(qMin(newBaseFontSize, baseFontSizeMax()), baseFontSizeMin()); emit baseFontSizeChanged(); } int baseFontSizeMin() const { return m_baseFontSizeMin; } int baseFontSizeMax() const { return m_baseFontSizeMax; } + qreal backgroundMusicVolume() const { return m_backgroundMusicVolume; } + void setBackgroundMusicVolume(const qreal newBackgroundMusicVolume) { + m_backgroundMusicVolume = newBackgroundMusicVolume; + emit backgroundMusicVolumeChanged(); + } + + qreal audioEffectsVolume() const { return m_audioEffectsVolume; } + void setAudioEffectsVolume(const qreal newAudioEffectsVolume) { + m_audioEffectsVolume = newAudioEffectsVolume; + emit audioEffectsVolumeChanged(); + } + int lastGCVersionRan() const { return m_lastGCVersionRan; } void setLastGCVersionRan(const int newLastGCVersionRan) { m_lastGCVersionRan = newLastGCVersionRan; emit lastGCVersionRanChanged(); } QString renderer() const { return m_renderer; } void setRenderer(const QString &newRenderer) { m_renderer = newRenderer; emit rendererChanged(); } QStringList filteredBackgroundMusic() const { return m_filteredBackgroundMusic; } void setFilteredBackgroundMusic(const QStringList &newFilteredBackgroundMusic) { m_filteredBackgroundMusic = newFilteredBackgroundMusic; emit filteredBackgroundMusicChanged(); } /** * Check if we use the external wordset for activity based on lang_api * @returns true if wordset is loaded * false if wordset is not loaded */ Q_INVOKABLE bool useExternalWordset(); protected slots: Q_INVOKABLE void notifyShowLockedActivitiesChanged(); Q_INVOKABLE void notifyAudioVoicesEnabledChanged(); Q_INVOKABLE void notifyAudioEffectsEnabledChanged(); Q_INVOKABLE void notifyBackgroundMusicEnabledChanged(); Q_INVOKABLE void notifyFullscreenChanged(); Q_INVOKABLE void notifyPreviousHeightChanged(); Q_INVOKABLE void notifyPreviousWidthChanged(); Q_INVOKABLE void notifyVirtualKeyboardChanged(); Q_INVOKABLE void notifyLocaleChanged(); Q_INVOKABLE void notifyFontChanged(); Q_INVOKABLE void notifyFontCapitalizationChanged(); Q_INVOKABLE void notifyFontLetterSpacingChanged(); Q_INVOKABLE void notifyEmbeddedFontChanged(); Q_INVOKABLE void notifyAutomaticDownloadsEnabledChanged(); Q_INVOKABLE void notifyFilterLevelMinChanged(); Q_INVOKABLE void notifyFilterLevelMaxChanged(); Q_INVOKABLE void notifyDemoModeChanged(); Q_INVOKABLE void notifyCodeKeyChanged(); Q_INVOKABLE void notifyKioskModeChanged(); Q_INVOKABLE void notifySectionVisibleChanged(); Q_INVOKABLE void notifyWordsetChanged(); Q_INVOKABLE void notifyFilteredBackgroundMusicChanged(); + Q_INVOKABLE void notifyBackgroundMusicVolumeChanged(); + Q_INVOKABLE void notifyAudioEffectsVolumeChanged(); Q_INVOKABLE void notifyDownloadServerUrlChanged(); Q_INVOKABLE void notifyCachePathChanged(); Q_INVOKABLE void notifyUserDataPathChanged(); Q_INVOKABLE void notifyExeCountChanged(); Q_INVOKABLE void notifyLastGCVersionRanChanged(); Q_INVOKABLE void notifyRendererChanged(); Q_INVOKABLE void notifyBarHiddenChanged(); public slots: Q_INVOKABLE bool isFavorite(const QString &activity); Q_INVOKABLE void setFavorite(const QString &activity, bool favorite); Q_INVOKABLE void saveBaseFontSize(); /// @endcond /** * Stores per-activity configuration @p data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @param data Map of configuration data so save. */ Q_INVOKABLE void saveActivityConfiguration(const QString &activity, const QVariantMap &data); /** * Loads per-activity configuration data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @returns Map of configuration items. */ Q_INVOKABLE QVariantMap loadActivityConfiguration(const QString &activity); /** * Loads per-activity progress using the default "progress" key. * * @param activity Name of the activity to load progress for. * @returns Last started level of the activity, 0 if none saved. */ Q_INVOKABLE int loadActivityProgress(const QString &activity); /** * Saves per-activity progress using the default "progress" key. * * @param activity Name of the activity that wants to persist settings. * @param progress Last started level to save as progress value. */ Q_INVOKABLE void saveActivityProgress(const QString &activity, int progress); signals: void showLockedActivitiesChanged(); void audioVoicesEnabledChanged(); void audioEffectsEnabledChanged(); void backgroundMusicEnabledChanged(); void fullscreenChanged(); void previousHeightChanged(); void previousWidthChanged(); void virtualKeyboardChanged(); void localeChanged(); void fontChanged(); void fontCapitalizationChanged(); void fontLetterSpacingChanged(); void embeddedFontChanged(); void automaticDownloadsEnabledChanged(); void filterLevelMinChanged(); void filterLevelMaxChanged(); void demoModeChanged(); void codeKeyChanged(); void kioskModeChanged(); void sectionVisibleChanged(); void wordsetChanged(); void baseFontSizeChanged(); void filteredBackgroundMusicChanged(); + void backgroundMusicVolumeChanged(); + void audioEffectsVolumeChanged(); void downloadServerUrlChanged(); void cachePathChanged(); void userDataPathChanged(); void exeCountChanged(); void lastGCVersionRanChanged(); void rendererChanged(); void barHiddenChanged(); protected: static ApplicationSettings *m_instance; private: // Update in configuration the couple {key, value} in the group. template void updateValueInConfig(const QString& group, const QString& key, const T& value); bool m_showLockedActivities; bool m_isAudioVoicesEnabled; bool m_isAudioEffectsEnabled; bool m_isBackgroundMusicEnabled; bool m_isFullscreen; quint32 m_previousHeight; quint32 m_previousWidth; bool m_isVirtualKeyboard; bool m_isAutomaticDownloadsEnabled; bool m_isEmbeddedFont; quint32 m_fontCapitalization; qreal m_fontLetterSpacing; quint32 m_filterLevelMin; quint32 m_filterLevelMax; bool m_defaultCursor; bool m_noCursor; QString m_locale; QString m_font; bool m_isDemoMode; QString m_codeKey; quint32 m_activationMode; bool m_isKioskMode; bool m_sectionVisible; QString m_wordset; QStringList m_filteredBackgroundMusic; + qreal m_backgroundMusicVolume; + qreal m_audioEffectsVolume; int m_baseFontSize; const int m_baseFontSizeMin; const int m_baseFontSizeMax; const qreal m_fontLetterSpacingMin; const qreal m_fontLetterSpacingMax; QString m_downloadServerUrl; QString m_cachePath; QString m_userDataPath; quint32 m_exeCount; int m_lastGCVersionRan; QString m_renderer; bool m_isBarHidden; QSettings m_config; }; #endif // APPLICATIONSETTINGS_H diff --git a/src/core/GCSfx.qml b/src/core/GCSfx.qml index 9f2aa2c33..29ba3da39 100644 --- a/src/core/GCSfx.qml +++ b/src/core/GCSfx.qml @@ -1,95 +1,95 @@ /* GCompris - GCSfx.qml * * Copyright (C) 2018 Timothée Giet * * Authors: * Johnny Jazeix (GCAudio base, 2014) * Timothée Giet * * 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 sfx playback. * @ingroup components * * Wrapper component around QtQuick's SoundEffect element, handling all sfx * playback in GCompris uniformly. * * * @inherit QtQuick.Item */ Item { id: gcsfx /** * type:bool * Whether sfx should be muted. */ - property bool muted + property alias muted: sfx.muted + + /** + * type:real + * Volume of the fx player. + */ + property alias volume: sfx.volume /** * type:url * URL to the sfx source to be played back. */ property alias source: sfx.source /** * type:string * Status of the fx player. */ property alias status: sfx.status - - /** - * When mute is changed we set the volume to 0 to mute a potential playing - * sound. - */ - onMutedChanged: muted ? sfx.volume = 0 : sfx.volume = 1 /** * Plays back the sfx resource @p file. * * @param type:string file [optional] URL to an sfx source. * @returns @c true if playback has been started, @c false if file does not * exist or sfx is muted */ function play(file) { if(file) { source = file } if(!muted) { sfx.play() } } /** * Stops sfx playback. */ function stop() { if(sfx.playing) sfx.stop() } /// @cond INTERNAL_DOCS SoundEffect { id: sfx } /// @endcond } diff --git a/src/core/main.qml b/src/core/main.qml index faa910f4d..96ef4f6a5 100644 --- a/src/core/main.qml +++ b/src/core/main.qml @@ -1,464 +1,463 @@ /* 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 a musical activity is running. * * 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 isMusicalActivityRunning: false /** * When a musical activity is started, the backgroundMusic pauses. * * When returning back from the musical activity to menu, backgroundMusic resumes. */ 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(ApplicationSettings.isAudioEffectsEnabled) audioVoices.append(ApplicationInfo.getAudioFilePath("qrc:/gcompris/src/core/resource/intro.$CA")) 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 muted: !ApplicationSettings.isBackgroundMusicEnabled - readonly property real backgroundMusicVolume: 0.5 - - volume: backgroundMusicVolume + volume: ApplicationSettings.backgroundMusicVolume onMutedChanged: { if(!hasAudio && !delayedbackgroundMusic.running && !files.length) { delayedbackgroundMusic.playBackgroundMusic() } } onDone: delayedbackgroundMusic.playBackgroundMusic() Timer { id: delayedbackgroundMusic interval: (ApplicationSettings.isAudioVoicesEnabled && !ApplicationSettings.isAudioEffectsEnabled) ? 2000 : 20000 repeat: false onTriggered: { delayedbackgroundMusic.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 && DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) { if(!ApplicationSettings.isAudioEffectsEnabled && !ApplicationSettings.isAudioVoicesEnabled) { delayedbackgroundMusic.playBackgroundMusic() } else { delayedbackgroundMusic.start() } } else { DownloadManager.backgroundMusicRegistered.connect(delayedbackgroundMusic.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 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() } } 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 } ); } } 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 }