diff --git a/src/activities/menu/ConfigurationItem.qml b/src/activities/menu/ConfigurationItem.qml index f8937a0543..f3beb76c01 100644 --- a/src/activities/menu/ConfigurationItem.qml +++ b/src/activities/menu/ConfigurationItem.qml @@ -213,6 +213,15 @@ Item { } } + GCDialogCheckBox { + id: enableExitDialogBox + text: qsTr("Enable activity Exit confirmation dialog") + checked: isExitDialogEnabled + onCheckedChanged: { + isExitDialogEnabled = checked; + } + } + GCDialogCheckBox { id: enableFullscreenBox text: qsTr("Fullscreen") @@ -547,6 +556,7 @@ Item { property bool showLockedActivities: ApplicationSettings.showLockedActivities property bool isAudioVoicesEnabled: ApplicationSettings.isAudioVoicesEnabled property bool isAudioEffectsEnabled: ApplicationSettings.isAudioEffectsEnabled + property bool isExitDialogEnabled: ApplicationSettings.isExitDialogEnabled property bool isFullscreen: ApplicationSettings.isFullscreen property bool isVirtualKeyboard: ApplicationSettings.isVirtualKeyboard property bool isAutomaticDownloadsEnabled: ApplicationSettings.isAutomaticDownloadsEnabled @@ -565,6 +575,9 @@ Item { isAudioEffectsEnabled = ApplicationSettings.isAudioEffectsEnabled enableAudioEffectsBox.checked = isAudioEffectsEnabled + isExitDialogEnabled = ApplicationSettings.isExitDialogEnabled + enableExitDialogBox.checked = isExitDialogEnabled + isFullscreen = ApplicationSettings.isFullscreen enableFullscreenBox.checked = isFullscreen @@ -612,6 +625,7 @@ Item { ApplicationSettings.showLockedActivities = showLockedActivities ApplicationSettings.isAudioVoicesEnabled = isAudioVoicesEnabled ApplicationSettings.isAudioEffectsEnabled = isAudioEffectsEnabled + ApplicationSettings.isExitDialogEnabled = isExitDialogEnabled ApplicationSettings.isFullscreen = isFullscreen ApplicationSettings.isVirtualKeyboard = isVirtualKeyboard ApplicationSettings.isAutomaticDownloadsEnabled = isAutomaticDownloadsEnabled diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index 36fdc4309a..114561ea59 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -47,6 +47,9 @@ ActivityBase { id: activity focus: true activityInfo: ActivityInfoTree.rootMenu + // set to true when returned to home from a dialog. + property bool returnFromDialog: false + onBack: { pageView.pop(to); // Restore focus that has been taken by the loaded activity @@ -58,17 +61,33 @@ ActivityBase { if(pageView.depth === 1 && !ApplicationSettings.isKioskMode) { Core.quit(main); } - else { + // When a dialog box is closed. + else if(returnFromDialog || !ApplicationSettings.isExitDialogEnabled) { + returnFromDialog = false; pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } + // A confirmation dialog to quit the current activity is popped. + else { + Core.showMessageDialog(parent, + qsTr("Do you want to leave current activity?"), + qsTr("Yes"), function() { pageView.pop(); + // Restore focus that has been taken by the loaded activity + if(pageView.currentItem == activity) + focus = true; }, + qsTr("No"), null, + null ); + } } - onDisplayDialog: pageView.push(dialog) - + onDisplayDialog: { + returnFromDialog = true; + pageView.push(dialog) + } onDisplayDialogs: { + returnFromDialog = true; var toPush = new Array(); for (var i = 0; i < dialogs.length; i++) { toPush.push({item: dialogs[i]}); @@ -215,12 +234,12 @@ ActivityBase { model: sections width: horizontal ? main.width : sectionCellWidth height: { - if(horizontal) - return sectionCellHeight - else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) - return sectionCellHeight * (sections.length+1) - else - return main.height - bar.height + if(horizontal) + return sectionCellHeight + else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) + return sectionCellHeight * (sections.length+1) + else + return main.height - bar.height } x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellHeight @@ -491,9 +510,9 @@ ActivityBase { visible: false anchors.fill: activitiesGrid gradient: Gradient { - GradientStop { position: 0.0; color: "#FFFFFFFF" } - GradientStop { position: 0.92; color: "#FFFFFFFF" } - GradientStop { position: 0.96; color: "#00FFFFFF"} + GradientStop { position: 0.0; color: "#FFFFFFFF" } + GradientStop { position: 0.92; color: "#FFFFFFFF" } + GradientStop { position: 0.96; color: "#00FFFFFF"} } } layer.enabled: ApplicationInfo.useOpenGL @@ -622,37 +641,37 @@ ActivityBase { } } function populate() { - var tmplayout = []; - var row = 0; - var offset = 0; - var cols; - while(offset < letter.length-1) { - if(letter.length <= 100) { - cols = Math.ceil((letter.length-offset) / (3 - row)); - } - else { - cols = background.horizontal ? (Math.ceil((letter.length-offset) / (15 - row))) - :(Math.ceil((letter.length-offset) / (22 - row))) - if(row == 0) { - tmplayout[row] = new Array(); - tmplayout[row].push({ label: keyboard.backspace }); - tmplayout[row].push({ label: keyboard.space }); - row ++; - } - } - - tmplayout[row] = new Array(); - for (var j = 0; j < cols; j++) - tmplayout[row][j] = { label: letter[j+offset] }; - offset += j; - row ++; - } - if(letter.length <= 100) { - tmplayout[0].push({ label: keyboard.space }); - tmplayout[row-1].push({ label: keyboard.backspace }); - } - keyboard.layout = tmplayout - } + var tmplayout = []; + var row = 0; + var offset = 0; + var cols; + while(offset < letter.length-1) { + if(letter.length <= 100) { + cols = Math.ceil((letter.length-offset) / (3 - row)); + } + else { + cols = background.horizontal ? (Math.ceil((letter.length-offset) / (15 - row))) + :(Math.ceil((letter.length-offset) / (22 - row))) + if(row == 0) { + tmplayout[row] = new Array(); + tmplayout[row].push({ label: keyboard.backspace }); + tmplayout[row].push({ label: keyboard.space }); + row ++; + } + } + + tmplayout[row] = new Array(); + for (var j = 0; j < cols; j++) + tmplayout[row][j] = { label: letter[j+offset] }; + offset += j; + row ++; + } + if(letter.length <= 100) { + tmplayout[0].push({ label: keyboard.space }); + tmplayout[row-1].push({ label: keyboard.backspace }); + } + keyboard.layout = tmplayout + } } Bar { @@ -678,6 +697,7 @@ ActivityBase { dialogActivityConfig.loader.item.loadFromConfig() displayDialog(dialogActivityConfig) } + onExitClicked: home() } DialogAbout { diff --git a/src/core/ActivityBase.qml b/src/core/ActivityBase.qml index 4e38ad42ed..e47047534c 100644 --- a/src/core/ActivityBase.qml +++ b/src/core/ActivityBase.qml @@ -178,14 +178,14 @@ Item { Keys.onPressed: { if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Q) { - // Ctrl+Q exit the application - Core.quit(main); + // Ctrl+Q exit the application without displaying confirmation dialog. + Core.quit(main, true); } else if (event.modifiers === Qt.ControlModifier && - event.key === Qt.Key_B) { + event.key === Qt.Key_B) { // Ctrl+B toggle the bar ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden; } else if (event.modifiers === Qt.ControlModifier && - event.key === Qt.Key_F) { + event.key === Qt.Key_F) { // Ctrl+F toggle fullscreen ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen } else if (event.modifiers === Qt.ControlModifier && diff --git a/src/core/ApplicationSettings.cpp b/src/core/ApplicationSettings.cpp index 781044645a..63d5bd1787 100644 --- a/src/core/ApplicationSettings.cpp +++ b/src/core/ApplicationSettings.cpp @@ -55,6 +55,7 @@ static const QString PREVIOUS_WIDTH_KEY = "previousWidth"; static const QString SHOW_LOCKED_ACTIVITIES_KEY = "showLockedActivities"; static const QString ENABLE_AUDIO_VOICES_KEY = "enableAudioVoices"; static const QString ENABLE_AUDIO_EFFECTS_KEY = "enableAudioEffects"; +static const QString ENABLE_EXIT_DIALOG_KEY = "enableExitDialog"; static const QString VIRTUALKEYBOARD_KEY = "virtualKeyboard"; static const QString LOCALE_KEY = "locale"; static const QString FONT_KEY = "font"; @@ -99,6 +100,7 @@ ApplicationSettings::ApplicationSettings(QObject *parent): QObject(parent), // general group m_config.beginGroup(GENERAL_GROUP_KEY); m_isAudioEffectsEnabled = m_config.value(ENABLE_AUDIO_EFFECTS_KEY, true).toBool(); + m_isExitDialogEnabled = m_config.value(ENABLE_EXIT_DIALOG_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(); @@ -170,7 +172,8 @@ ApplicationSettings::ApplicationSettings(QObject *parent): QObject(parent), connect(this, &ApplicationSettings::showLockedActivitiesChanged, this, &ApplicationSettings::notifyShowLockedActivitiesChanged); connect(this, &ApplicationSettings::audioVoicesEnabledChanged, this, &ApplicationSettings::notifyAudioVoicesEnabledChanged); connect(this, &ApplicationSettings::audioEffectsEnabledChanged, this, &ApplicationSettings::notifyAudioEffectsEnabledChanged); - connect(this, &ApplicationSettings::fullscreenChanged, this, &ApplicationSettings::notifyFullscreenChanged); + connect(this, &ApplicationSettings::exitDialogEnabledChanged, this, &ApplicationSettings::notifyExitDialogEnabledChanged); + 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); @@ -255,6 +258,12 @@ void ApplicationSettings::notifyAudioEffectsEnabledChanged() qDebug() << "notifyAudioEffects: " << m_isAudioEffectsEnabled; } +void ApplicationSettings::notifyExitDialogEnabledChanged() +{ + updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_EXIT_DIALOG_KEY, m_isExitDialogEnabled); + qDebug() << "exit dialog Visibilty set to : " << m_isExitDialogEnabled; +} + void ApplicationSettings::notifyLocaleChanged() { updateValueInConfig(GENERAL_GROUP_KEY, LOCALE_KEY, m_locale); diff --git a/src/core/ApplicationSettings.h b/src/core/ApplicationSettings.h index e681f56bb2..276721aad0 100644 --- a/src/core/ApplicationSettings.h +++ b/src/core/ApplicationSettings.h @@ -81,6 +81,11 @@ class ApplicationSettings : public QObject */ Q_PROPERTY(bool isAudioEffectsEnabled READ isAudioEffectsEnabled WRITE setIsAudioEffectsEnabled NOTIFY audioEffectsEnabledChanged) + /** + * Whether activity exit confirmation dialog should be displayed. + */ + Q_PROPERTY(bool isExitDialogEnabled READ isExitDialogEnabled WRITE setIsExitDialogEnabled NOTIFY exitDialogEnabledChanged) + /** * Whether GCompris should run in fullscreen mode. */ @@ -285,6 +290,12 @@ class ApplicationSettings : public QObject emit audioEffectsEnabledChanged(); } + bool isExitDialogEnabled() const { return m_isExitDialogEnabled; } + void setIsExitDialogEnabled(const bool newMode) { + m_isExitDialogEnabled = newMode; + emit exitDialogEnabledChanged(); + } + bool isFullscreen() const { return m_isFullscreen; } void setFullscreen(const bool newMode) { if(m_isFullscreen != newMode) { @@ -472,6 +483,7 @@ protected slots: Q_INVOKABLE void notifyShowLockedActivitiesChanged(); Q_INVOKABLE void notifyAudioVoicesEnabledChanged(); Q_INVOKABLE void notifyAudioEffectsEnabledChanged(); + Q_INVOKABLE void notifyExitDialogEnabledChanged(); Q_INVOKABLE void notifyFullscreenChanged(); Q_INVOKABLE void notifyPreviousHeightChanged(); Q_INVOKABLE void notifyPreviousWidthChanged(); @@ -541,6 +553,7 @@ public slots: void showLockedActivitiesChanged(); void audioVoicesEnabledChanged(); void audioEffectsEnabledChanged(); + void exitDialogEnabledChanged(); void fullscreenChanged(); void previousHeightChanged(); void previousWidthChanged(); @@ -579,6 +592,7 @@ public slots: bool m_showLockedActivities; bool m_isAudioVoicesEnabled; bool m_isAudioEffectsEnabled; + bool m_isExitDialogEnabled; bool m_isFullscreen; quint32 m_previousHeight; quint32 m_previousWidth; diff --git a/src/core/Bar.qml b/src/core/Bar.qml index e695b6818f..d66e33b58d 100644 --- a/src/core/Bar.qml +++ b/src/core/Bar.qml @@ -177,6 +177,14 @@ Item { */ signal homeClicked + /** + * Emitted when the exit button was clicked. + * + * Should always be connected to the ActivityBase.home signal and thus + * exits the GCompris after showing exit dialog. + */ + signal exitClicked + /// @cond INTERNAL_DOCS /* @@ -384,7 +392,7 @@ Item { BarButton { source: "qrc:/gcompris/src/core/resource/bar_exit.svg"; sourceSize.width: fullButton * barZoom - onClicked: Core.quit(bar.parent.parent); + onClicked: bar.exitClicked() } } Component { diff --git a/src/core/GCDialog.qml b/src/core/GCDialog.qml index 5d458ac540..a678bd0006 100644 --- a/src/core/GCDialog.qml +++ b/src/core/GCDialog.qml @@ -98,8 +98,14 @@ Item { fill: parent } - onStart: opacity = 1 - onStop: opacity = 0 + onStart: { + opacity = 1 + forceActiveFocus(); + } + onStop: { + opacity = 0 + parent.currentItem.forceActiveFocus(); + } onClose: destroy() Behavior on opacity { NumberAnimation { duration: 200 } } @@ -158,7 +164,7 @@ Item { fontSize: regularSize color: "black" // @FIXME This property breaks the wrapping -// horizontalAlignment: Text.AlignHCenter + // horizontalAlignment: Text.AlignHCenter // need to remove the anchors (left and right) else sometimes text is hidden on the side width: instruction.width - 2*flick.anchors.margins wrapMode: TextEdit.WordWrap diff --git a/src/core/core.js b/src/core/core.js index 8485504856..c3827d1cb0 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -95,12 +95,12 @@ function showMessageDialog(parent, informativeText, closeCallback) { //console.debug("Core.showMessageDialog: parent=" + parent + " backtrace="); console.trace(); var qmlStr = - 'import QtQuick 2.6\n' - + 'GCDialog {\n' - + ' message: "' + informativeText + '"\n' - + ' button1Text: "' + button1Text + '"\n' - + ' button2Text: "' + button2Text + '"\n' - + ' }\n'; + 'import QtQuick 2.6\n' + + 'GCDialog {\n' + + ' message: "' + informativeText + '"\n' + + ' button1Text: "' + button1Text + '"\n' + + ' button2Text: "' + button2Text + '"\n' + + ' }\n'; var dialog = null; try { @@ -156,7 +156,7 @@ function showDownloadDialog(parent, properties) { downloadDialogComponent = Qt.createComponent("qrc:/gcompris/src/core/DownloadDialog.qml"); if (downloadDialogComponent.status != Qml.Component.Ready) { throw new Error("Error creating DownloadDialog component: " - + downloadDialogComponent.errorString()); + + downloadDialogComponent.errorString()); downloadDialogComponent = null; } } @@ -187,13 +187,13 @@ function checkForVoices(parent) { if (!GCompris.DownloadManager.areVoicesRegistered()) { showMessageDialog(parent, - qsTr("Missing sound files!") + '\n' - + qsTr("This activity uses language sound files, that are not yet installed on your system.") - + '\n' - + qsTr("For downloading the needed sound files go to the preferences dialog."), - "", null, - "", null, - null); + qsTr("Missing sound files!") + '\n' + + qsTr("This activity uses language sound files, that are not yet installed on your system.") + + '\n' + + qsTr("For downloading the needed sound files go to the preferences dialog."), + "", null, + "", null, + null); } } @@ -202,12 +202,12 @@ var aboutToQuit = false; * Central function for quitting GCompris. * * Should be used everywhere instead of Qt.quit(), warning in case of running - * downloadloads and showing a confirmation dialog on mobile devices. + * downloadloads and showing a confirmation dialog on closing GCompris. * Call Qt.quit() itself upon confirmation. * * @param parent QML parent object used for the dynamic dialog. */ -function quit(parent) +function quit(parent, forcedQuit) { if (aboutToQuit) // don't execute concurrently return; @@ -218,26 +218,27 @@ function quit(parent) if (GCompris.DownloadManager.downloadIsRunning()) { var dialog = showDownloadDialog(parent, { - text: qsTr("Download in progress") - + '\n' - + qsTr("Download in progress.
'Abort' it to quit immediately."), - autohide: true, - reportError: false, - reportSuccess: false, - backgroundButtonVisible: false - }); + text: qsTr("Download in progress") + + '\n' + + qsTr("Download in progress.
'Abort' it to quit immediately."), + autohide: true, + reportError: false, + reportSuccess: false, + backgroundButtonVisible: false + }); dialog.finished.connect(function() {Qt.quit();}); - } else if (GCompris.ApplicationInfo.isMobile) { + } + else if(forcedQuit) { + Qt.quit(); + } + else { // prevent the user from quitting accidentially by clicking back too often: showMessageDialog(parent, - qsTr("Quit?") + - '\n' + - qsTr("Do you really want to quit GCompris?"), - qsTr("Yes"), function() { Qt.quit(); }, - qsTr("No"), function() { aboutToQuit = false; }, - function() { aboutToQuit = false; } ); - } else - Qt.quit(); + qsTr("Do you really want to quit GCompris?"), + qsTr("Yes"), function() { Qt.quit(); }, + qsTr("No"), function() { aboutToQuit = false; }, + function() { aboutToQuit = false; } ); + } } function isLeftToRightLocale(locale) {