diff --git a/src/activities/menu/BackgroundMusicList.qml b/src/activities/menu/BackgroundMusicList.qml index 953e97e91..db41a36fe 100644 --- a/src/activities/menu/BackgroundMusicList.qml +++ b/src/activities/menu/BackgroundMusicList.qml @@ -1,231 +1,236 @@ /* GCompris - BackgroundMusicList.qml * * Copyright (C) 2019 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta (Qt Quick) * * 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 "qrc:/gcompris/src/core/core.js" as Core Rectangle { id: dialogBackground color: "#696da3" border.color: "black" border.width: 1 z: 10000 anchors.fill: parent visible: false Keys.onEscapePressed: close() signal close property bool horizontalLayout: dialogBackground.width >= dialogBackground.height property int margin30: Math.round(30 * ApplicationInfo.ratio) Row { spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { id: titleRectangle color: "#e6e6e6" radius: 6.0 width: dialogBackground.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 GCText { id: title text: qsTr("Background music") width: dialogBackground.width - 30 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogBackground.width - 30 height: dialogBackground.height - 100 border.color: "black" border.width: 2 anchors.margins: 100 Flickable { id: flickableList anchors.fill: parent anchors.margins: 10 * ApplicationInfo.ratio contentHeight: musicGrid.height + musicInfo.height + margin30 flickableDirection: Flickable.VerticalFlick clip: true Flow { id: musicGrid width: parent.width spacing: 10 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter Repeater { model: dialogActivityConfig.configItem ? dialogActivityConfig.configItem.allBackgroundMusic : 0 Item { width: (musicGrid.width - margin30) * 0.33 height: title.height * 2 Button { text: modelData.slice(0, modelData.lastIndexOf('.')) onClicked: { if(dialogActivityConfig.configItem.filteredBackgroundMusic.indexOf(modelData) == -1) { // Keep the filtered playlist sorted w.r.t to their positions in "allBackgroundMusic" to maintain their playing order var musicOriginalPosition = dialogActivityConfig.configItem.allBackgroundMusic.indexOf(modelData) var i = 0 while(i < dialogActivityConfig.configItem.filteredBackgroundMusic.length) { var filteredMusicName = dialogActivityConfig.configItem.filteredBackgroundMusic[i] if(dialogActivityConfig.configItem.allBackgroundMusic.indexOf(filteredMusicName) > musicOriginalPosition) break i++ } dialogActivityConfig.configItem.filteredBackgroundMusic.splice(i, 0, modelData) } else { dialogActivityConfig.configItem.filteredBackgroundMusic.splice(dialogActivityConfig.configItem.filteredBackgroundMusic.indexOf(modelData), 1) if(dialogActivityConfig.configItem.filteredBackgroundMusic == 0) { dialogActivityConfig.configItem.filteredBackgroundMusic.push(modelData) selectedIcon.visible = false Core.showMessageDialog(dialogBackground, qsTr("Disable the background music if you don't want to play them."), "", null, "", null, null ); } } selectedIcon.visible = !selectedIcon.visible } width: parent.width height: parent.height * 0.8 style: GCButtonStyle { theme: "dark" } Image { id: selectedIcon source: "qrc:/gcompris/src/core/resource/apply.svg" sourceSize.width: height sourceSize.height: height width: height height: parent.height / 4 anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: 2 visible: dialogActivityConfig.configItem.filteredBackgroundMusic ? dialogActivityConfig.configItem.filteredBackgroundMusic.indexOf(modelData) != -1 : false } } } } } Column { id: musicInfo spacing: 10 * ApplicationInfo.ratio width: parent.width anchors.top: musicGrid.bottom anchors.leftMargin: 20 GCText { - text: qsTr("Now Playing" + " :") + //: Current background music playing + text: qsTr("Now Playing:") width: dialogBackground.width - 30 horizontalAlignment: Text.AlignHCenter color: "black" fontSize: mediumSize wrapMode: Text.WordWrap } GCText { - text: qsTr("Title" + " : " + backgroundMusic.metaDataMusic[0]) + //: Title of the current background music playing + text: qsTr("Title: %1").arg(backgroundMusic.metaDataMusic[0]) width: dialogBackground.width - 30 horizontalAlignment: Text.AlignLeft color: "black" fontSize: smallSize wrapMode: Text.WordWrap } GCText { - text: qsTr("Artist" + " : " + backgroundMusic.metaDataMusic[1]) + //: Artist of the current background music playing + text: qsTr("Artist: %1").arg(backgroundMusic.metaDataMusic[1]) width: dialogBackground.width - 30 horizontalAlignment: Text.AlignLeft color: "black" fontSize: smallSize wrapMode: Text.WordWrap } GCText { - text: qsTr("Date" + " : " + backgroundMusic.metaDataMusic[2]) + //: Date of the current background music playing + text: qsTr("Date: %1").arg(backgroundMusic.metaDataMusic[2]) width: dialogBackground.width - 30 horizontalAlignment: Text.AlignLeft color: "black" fontSize: smallSize wrapMode: Text.WordWrap } GCText { - text: qsTr("Copyright" + " : " + backgroundMusic.metaDataMusic[3]) + //: Copyright of the current background music playing + text: qsTr("Copyright: %1").arg(backgroundMusic.metaDataMusic[3]) width: dialogBackground.width - 30 horizontalAlignment: Text.AlignLeft color: "black" fontSize: smallSize wrapMode: Text.WordWrap } } } // The scroll buttons GCButtonScroll { anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: flickableList.bottom anchors.bottomMargin: 30 * ApplicationInfo.ratio width: parent.width / 20 height: width * heightRatio onUp: flickableList.flick(0, 1400) onDown: flickableList.flick(0, -1400) upVisible: (flickableList.visibleArea.yPosition <= 0) ? false : true downVisible: ((flickableList.visibleArea.yPosition + flickableList.visibleArea.heightRatio) >= 1) ? false : true } } Item { width: 1; height: 10 } } } GCButtonCancel { onClose: { parent.close() } } } diff --git a/src/activities/menu/ConfigurationItem.qml b/src/activities/menu/ConfigurationItem.qml index af6e330e8..01f9c45dc 100644 --- a/src/activities/menu/ConfigurationItem.qml +++ b/src/activities/menu/ConfigurationItem.qml @@ -1,884 +1,884 @@ /* 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 * ApplicationInfo.ratio width: parent.width move: Transition { NumberAnimation { properties: "x,y"; duration: 120 } } // Put configuration here Row { id: demoModeBox width: parent.width spacing: 10 * ApplicationInfo.ratio 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 * ApplicationInfo.ratio width: parent.width GCText { id: audioEffectsVolumeText text: qsTr("Audio effects volume") fontSize: mediumSize wrapMode: Text.WordWrap } } Flow { spacing: 5 * ApplicationInfo.ratio width: parent.width GCSlider { id: audioEffectsVolumeSlider width: 250 * ApplicationInfo.ratio maximumValue: 10 minimumValue: 0 value: audioEffectsVolume * 10 onValueChanged: ApplicationSettings.audioEffectsVolume = value / 10; scrollEnabled: false } } GCDialogCheckBox { id: enableBackgroundMusicBox text: qsTr("Enable background music") checked: isBackgroundMusicEnabled onCheckedChanged: { isBackgroundMusicEnabled = checked; } } Flow { spacing: 5 * ApplicationInfo.ratio width: parent.width GCText { text: qsTr("Background Music") fontSize: mediumSize height: 50 * ApplicationInfo.ratio } Image { source: "qrc:/gcompris/src/core/resource/bar_next.svg" height: Math.min(50 * ApplicationInfo.ratio, parent.width / 8) sourceSize.width: height MouseArea { anchors.fill: parent enabled: (backgroundMusic.playbackState == Audio.PlayingState && !backgroundMusic.muted) onClicked: backgroundMusic.nextAudio() } } } Flow { width: parent.width spacing: 5 * ApplicationInfo.ratio Button { id: backgroundMusicName height: 30 * ApplicationInfo.ratio width: background.width * 0.8 text: { if(backgroundMusic.playbackState != Audio.PlayingState) return qsTr("Not playing") else if (backgroundMusic.metaDataMusic[0] != undefined) - return (qsTr("Title") + ": " + backgroundMusic.metaDataMusic[0] + " " + qsTr("Artist") + ": " + backgroundMusic.metaDataMusic[1]) + return (qsTr("Title: %1 Artist: %2").arg(backgroundMusic.metaDataMusic[0]).arg(backgroundMusic.metaDataMusic[1])) else if (String(backgroundMusic.source).slice(0, 37) === "qrc:/gcompris/src/core/resource/intro") - return (qsTr("Introduction music")) - return ("") + return qsTr("Introduction music") + return "" } style: GCButtonStyle {} onClicked: { dialogConfig.visible = false backgroundMusicList.visible = true } } } Flow { spacing: 5 width: parent.width GCText { id: backgroundMusicVolumeText text: qsTr("Background music volume") fontSize: mediumSize wrapMode: Text.WordWrap } } GCSlider { id: backgroundMusicVolumeSlider width: 250 * ApplicationInfo.ratio maximumValue: 10 minimumValue: 0 value: backgroundMusicVolume * 10 onValueChanged: ApplicationSettings.backgroundMusicVolume = value / 10; scrollEnabled: false } 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") } } GCText { id: baseFontSizeText text: qsTr("Font size") fontSize: mediumSize wrapMode: Text.WordWrap } 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 } 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") } } GCText { id: fontLetterSpacingText text: qsTr("Font letter spacing") fontSize: mediumSize wrapMode: Text.WordWrap } 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 } 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 } } Flow { width: parent.width spacing: 5 * 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) } } } // 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) } } } } } 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/GCAudio.qml b/src/core/GCAudio.qml index 8ae34c881..5a960af9d 100644 --- a/src/core/GCAudio.qml +++ b/src/core/GCAudio.qml @@ -1,269 +1,269 @@ /* 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 - * backgrounMusic metadata + * 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) _playNextFile() } 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) return var nextFile = files.shift() if(nextFile === '') { audio.source = "" gcaudio.done() } else { audio.source = "" audio.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 }