diff --git a/CMakeLists.txt b/CMakeLists.txt index dd1013320..7de1259dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,416 +1,416 @@ cmake_minimum_required(VERSION 2.8.12) cmake_policy(SET CMP0043 NEW) project(gcompris-qt C CXX) # get all the redist dll needed for windows when compiling with vc set(CMAKE_INSTALL_UCRT_LIBRARIES 1) include(InstallRequiredSystemLibraries) # Set c++11 support include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) if(COMPILER_SUPPORTS_CXX11) set(my_cxx_flags "-std=c++11") elseif(COMPILER_SUPPORTS_CXX0X) set(my_cxx_flags "-std=c++0x") else() message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}") # enable qml debugging for DEBUG builds: set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_QML_DEBUG") set(GCOMPRIS_MAJOR_VERSION 0) set(GCOMPRIS_MINOR_VERSION 98) set(GCOMPRIS_PATCH_VERSION 0) # Set the BUILD_DATE string(TIMESTAMP BUILD_DATE %Y%m) # cmake modules setup find_package(ECM 1.4.0 QUIET NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake/) set(CMAKE_PREFIX_PATH "${Qt5_DIR}/lib/cmake/Qt5") # KDE po to qm tools if(ECM_FOUND) include(kdeFetchTranslation) include(ECMAddTests) include(ECMPoQmTools) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() option(BUILD_TESTING "Build and enable unit tests" OFF) include(ECMCoverageOption) endif(ECM_FOUND) # add tools (cppcheck, clang-tidy...) if build on testing mode only # (slower compilation) if(BUILD_TESTING) include(CodeQualityUtils) endif() -set(QT_REQUIRED_VERSION 5.6.0) +set(QT_REQUIRED_VERSION 5.7.0) if(CMAKE_SYSTEM_NAME STREQUAL Android) find_package(ECM) set(ANDROID 1) # TODO: possibly should be setup by toolchain one day set(QT_QMAKE_EXECUTABLE "${_qt5Core_install_prefix}/bin/qmake") if(ECM_VERSION VERSION_GREATER "5.55.0") set(QT_REQUIRED_VERSION 5.12.0) endif() # workaround until this fix is in released ECM if(ECM_VERSION VERSION_LESS "5.15.0") add_definitions(-DANDROID) endif() endif() # Set executable filename if(ANDROID) set(GCOMPRIS_EXECUTABLE_NAME GCompris) elseif(SAILFISHOS) set(GCOMPRIS_EXECUTABLE_NAME harbour-gcompris-qt) elseif(WIN32) set(GCOMPRIS_EXECUTABLE_NAME GCompris) else() set(GCOMPRIS_EXECUTABLE_NAME gcompris-qt) endif() set(GCOMPRIS_VERSION ${GCOMPRIS_MAJOR_VERSION}.${GCOMPRIS_MINOR_VERSION}) # An integer value that represents the version of the application # Increase it at each release math(EXPR GCOMPRIS_VERSION_CODE "${GCOMPRIS_MAJOR_VERSION}*10000 + ${GCOMPRIS_MINOR_VERSION}*100 + ${GCOMPRIS_PATCH_VERSION}") # prevent build in source directory if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") message(SEND_ERROR "Building in the source directory is not supported.") message(FATAL_ERROR "Please remove the created \"CMakeCache.txt\" file, the \"CMakeFiles\" directory and create a build directory and call \"${CMAKE_COMMAND} \".") endif("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED Qml Quick Gui Multimedia Core Svg Xml XmlPatterns LinguistTools Sensors) if(ANDROID) find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED AndroidExtras) endif(ANDROID) if(SAILFISHOS) find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED Widgets) endif(SAILFISHOS) ## For now we workaround CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION to clang with a command variable, so not needed # if(ANDROID AND ECM_VERSION VERSION_LESS "5.56.0" AND Qt5Core_VERSION VERSION_GREATER "5.11.99") # message(FATAL_ERROR "ECM ${ECM_VERSION} not compatible with Qt ${Qt5Core_VERSION} version for android.") # endif() if((UNIX AND NOT APPLE AND NOT SAILFISHOS AND NOT ANDROID) OR WIN32) find_package(OpenSSL REQUIRED) endif() find_package (KF5 QUIET COMPONENTS DocTools ) if(ECM_FOUND) include(KDEInstallDirs) if(ECM_VERSION VERSION_GREATER "1.6.0") add_subdirectory(images) install(FILES org.kde.gcompris.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES org.kde.gcompris.desktop DESTINATION ${KDE_INSTALL_APPDIR}) else() message(STATUS "ECM_VERSION is ${ECM_VERSION}, icons and desktop files won't be installed.") endif() endif() # Tell CMake to run moc when necessary: set(CMAKE_AUTOMOC ON) # As moc files are generated in the binary dir, tell CMake # to always look for includes there: set(CMAKE_INCLUDE_CURRENT_DIR ON) #get_cmake_property(_variableNames VARIABLES) #foreach (_variableName ${_variableNames}) # message("${_variableName}=${${_variableName}}") #endforeach() set(ACTIVATION_MODE "no" CACHE STRING "Policy for activation [no|inapp|internal]") option(WITH_DEMO_ONLY "Include only demo activities" OFF) option(WITH_DOWNLOAD "Internal download" ON) # @FIXME These permissions should be removed if download is disable # but it makes the application crash on exit (tested on Android 6) set(ANDROID_INTERNET_PERMISSION "") set(ANDROID_ACCESS_NETWORK_STATE_PERMISSION "") set(GRAPHICAL_RENDERER "auto" CACHE STRING "Policy for choosing the renderer backend [opengl|software|auto]") # Set output directory if(CMAKE_HOST_APPLE) set(_bundle_bin gcompris-qt.app/Contents/MacOS) set(_data_dest_dir bin/${_bundle_bin}/../Resources) else() set(_data_dest_dir share/${GCOMPRIS_EXECUTABLE_NAME}) endif() if(ANDROID) # Android .so output set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/android/libs/${ANDROID_ABI}/) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir} CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/android/assets/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) if(ACTIVATION_MODE STREQUAL "inapp") set(ANDROID_BILLING_PERMISSION "") set(ANDROID_PACKAGE "net.gcompris") else(ACTIVATION_MODE) set(ANDROID_PACKAGE "net.gcompris.full") endif() add_subdirectory(android) elseif(CMAKE_HOST_APPLE) # MacOSX build set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/translations CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) else() # Desktop build set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/translations CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) endif(ANDROID) # Always create these folders add_custom_command( OUTPUT shareFolders COMMAND cmake -E make_directory ${GCOMPRIS_TRANSLATIONS_DIR} COMMAND cmake -E make_directory ${GCOMPRIS_RCC_DIR} ) add_custom_target( createShareFolders ALL DEPENDS shareFolders ) include(cmake/rcc.cmake) # Translations handling # Simple command calling the python script add_custom_command( OUTPUT retrievePoFilesFromSvn COMMAND python2 tools/l10n-fetch-po-files.py WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Install translations add_custom_target(getSvnTranslations DEPENDS retrievePoFilesFromSvn COMMENT "Re-run cmake after this to be able to run BuildTranslations with the latest files" ) # Get all po files in po/. You can get them doing: python2 tools/l10n-fetch-po-files.py file(GLOB TRANSLATIONS_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "po/*.po") # Set the output dir for the translation files to /bin foreach(PoSource ${TRANSLATIONS_FILES}) # Changes the .po extension to .ts string(REPLACE ".po" ".ts" TsSource ${PoSource}) # Removes the po/ folder string(REPLACE "po/" "" TsSource ${TsSource}) # qm filename string(REPLACE ".ts" ".qm" QmOutput ${TsSource}) set(OutTsFile ${CMAKE_BINARY_DIR}/tmp/${TsSource}) add_custom_command( OUTPUT ${QmOutput} COMMAND cmake -E make_directory ${GCOMPRIS_TRANSLATIONS_DIR} COMMAND cmake -E make_directory ${CMAKE_BINARY_DIR}/tmp # Remove the obsolete translations and set po in the ts output file COMMAND msgattrib --no-obsolete ${CMAKE_CURRENT_SOURCE_DIR}/${PoSource} -o ${OutTsFile} # Convert the po into ts COMMAND Qt5::lconvert -if po -of ts -i ${OutTsFile} -o ${OutTsFile} # Convert the ts in qm removing non finished translations COMMAND Qt5::lrelease -compress -nounfinished ${OutTsFile} -qm ${GCOMPRIS_TRANSLATIONS_DIR}/${QmOutput} ) list(APPEND QM_FILES ${QmOutput}) endforeach() # Install translations if (WIN32) add_custom_target(BuildTranslations DEPENDS ${QM_FILES} COMMENT "If you don't have the .po, you need to run make getSvnTranslations first then re-run cmake" ) else() add_custom_target(BuildTranslations ALL DEPENDS ${QM_FILES} COMMENT "If you don't have the .po, you need to run make getSvnTranslations first then re-run cmake" ) endif() add_custom_command( OUTPUT doBundleTranslations COMMAND 7z a -w${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME} ${CMAKE_BINARY_DIR}/translations-${GCOMPRIS_VERSION}.7z ${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME}/translations WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Bundle translations add_custom_target(BundleTranslations DEPENDS doBundleTranslations COMMENT "If you want to provide a zip of the translations on a server (run make BuildTranslations first)" ) add_custom_command( OUTPUT doDlAndInstallBundledTranslations COMMAND curl -fsS -o translations-${GCOMPRIS_VERSION}.7z http://gcompris.net/download/translations-${GCOMPRIS_VERSION}.7z COMMAND 7z x -y -o${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME} translations-${GCOMPRIS_VERSION}.7z WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) # Download and install bundled translations add_custom_target(DlAndInstallBundledTranslations DEPENDS doDlAndInstallBundledTranslations COMMENT "Download the bundled translation and install them in the build dir" ) if(CMAKE_HOST_APPLE) install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION ${_bundle_bin}) elseif(ANDROID) install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION "share") else() install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION ${_data_dest_dir}) endif() # Build standalone package option -> if ON, we will copy the required Qt files in the build package. # If OFF, "make install" will not copy Qt files so only GCompris files will be packaged. # By default, it is true on Windows (as we deliver NSIS package), macOS (bundled), android (apk) and false on linux (to do make install) # If you want to create a STGZ package for linux (auto-extractible), override this variable by typing : cmake -DBUILD_STANDALONE=ON if(UNIX AND NOT ANDROID AND NOT APPLE) option(BUILD_STANDALONE "Build a standalone package when typing 'make package'" OFF) else() option(BUILD_STANDALONE "Build a standalone package when typing 'make package'" ON) endif() option(WITH_KIOSK_MODE "Set the kiosk mode by default" OFF) if(WIN32) set(COMPRESSED_AUDIO "mp3" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") elseif(APPLE) set(COMPRESSED_AUDIO "aac" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") else() set(COMPRESSED_AUDIO "ogg" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") endif() file(GLOB_RECURSE OGG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/ "*.ogg") foreach(OGG_FILE ${OGG_FILES}) # This should only replace the extension string(REGEX REPLACE "ogg$" "aac" AAC_FILE ${OGG_FILE}) add_custom_command( OUTPUT ${AAC_FILE} # Put the good line depending on your installation COMMAND avconv -v warning -i ${OGG_FILE} -acodec libvo_aacenc ${AAC_FILE} #COMMAND ffmpeg -v warning -i ${OGG_FILE} -acodec aac -strict -2 ${AAC_FILE} ) list(APPEND AAC_FILES ${AAC_FILE}) # This should only replace the extension string(REGEX REPLACE "ogg$" "mp3" MP3_FILE ${OGG_FILE}) add_custom_command( OUTPUT ${MP3_FILE} # Put the good line depending on your installation #COMMAND avconv -v warning -i ${OGG_FILE} -acodec mp3 ${MP3_FILE} COMMAND ffmpeg -v warning -i ${OGG_FILE} -acodec mp3 -strict -2 ${MP3_FILE} ) list(APPEND MP3_FILES ${MP3_FILE}) endforeach() add_custom_target( createAacFromOgg DEPENDS ${AAC_FILES} ) add_custom_target( createMp3FromOgg DEPENDS ${MP3_FILES} ) # predownload assets (voices and images) and install them in the rcc folder set(DOWNLOAD_ASSETS "" CACHE STRING "Download and packages images and voices. use a list like: words,en,fr,pt_BR to retrieve multiple files") add_custom_command( OUTPUT predownloadAssets COMMAND python tools/download-assets.py ${DOWNLOAD_ASSETS} ${COMPRESSED_AUDIO} ${GCOMPRIS_RCC_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_command( OUTPUT assetsFolders COMMAND cmake -E make_directory "${GCOMPRIS_RCC_DIR}/data2" COMMAND cmake -E make_directory "${GCOMPRIS_RCC_DIR}/data2/voices-${COMPRESSED_AUDIO}" COMMAND cmake -E make_directory "${GCOMPRIS_RCC_DIR}/data2/words" ) # Install assets add_custom_target(getAssets DEPENDS assetsFolders predownloadAssets ) add_custom_command( OUTPUT doBundleConvertedOggs COMMAND 7z a converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z '-ir!src/*${COMPRESSED_AUDIO}' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Bundle oggs ready to be uploaded on a server. This ease build on system without the appropriate audio # convertion tools. add_custom_target(BundleConvertedOggs DEPENDS doBundleConvertedOggs COMMENT "Bundle the converted oggs to upload them on a server. First set COMPRESSED_AUDIO appropriately." ) add_custom_command( OUTPUT doDlAndInstallBundledConvertedOggs COMMAND curl -fsS -o converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z http://gcompris.net/download/converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z COMMAND 7z x -y converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Download and install bundled converted oggs add_custom_target(DlAndInstallBundledConvertedOggs DEPENDS doDlAndInstallBundledConvertedOggs COMMENT "Download the bundled converted oggs and install them in the source dir" ) set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${GCOMPRIS_VERSION}) add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ HEAD | xz > ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) if(KF5_FOUND) add_subdirectory(docs/docbook) endif(KF5_FOUND) # qml-box2d include(cmake/box2d.cmake) add_subdirectory(src) if(SAILFISHOS) # Need to be done at the end, after src add_subdirectory(platforms/sailfishOS) endif() # only enable unit tests for linux if(BUILD_TESTING) enable_testing() add_subdirectory(tests) endif(BUILD_TESTING) add_custom_target(binaries) add_dependencies(binaries ${GCOMPRIS_EXECUTABLE_NAME} rcc_core rcc_menu rcc_activities all_activities) diff --git a/src/activities/baby_wordprocessor/BabyWordprocessor.qml b/src/activities/baby_wordprocessor/BabyWordprocessor.qml index 92d6a19b1..495978339 100644 --- a/src/activities/baby_wordprocessor/BabyWordprocessor.qml +++ b/src/activities/baby_wordprocessor/BabyWordprocessor.qml @@ -1,317 +1,316 @@ /* GCompris - baby_wordprocessor.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 -import QtQuick.Controls 1.5 import "../../core" ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: 'white' signal start signal stop Component.onCompleted: { activity.start.connect(start) } onStart: { keyboard.populate(); edit.forceActiveFocus(); } property bool horizontalMode: height <= width GCCreationHandler { id: creationHandler onFileLoaded: { edit.clear() edit.text = data["text"] edit.cursorPosition = edit.length } onClose: edit.forceActiveFocus() } Column { id: controls width: 120 * ApplicationInfo.ratio anchors { right: parent.right top: parent.top margins: 10 } spacing: 10 - Button { - style: GCButtonStyle { textSize: "title"} + GCButton { + textSize: "title" text: qsTr("Title") width: parent.width onClicked: edit.formatLineWith('h2') } - Button { - style: GCButtonStyle { textSize: "subtitle"} + GCButton { + textSize: "subtitle" text: qsTr("Subtitle") width: parent.width onClicked: edit.formatLineWith('h3') } - Button { - style: GCButtonStyle { textSize: "regular"} + GCButton { + textSize: "regular" width: parent.width text: qsTr("Paragraph") onClicked: edit.formatLineWith('p') } } Column { id: saveAndLoadButtons width: controls.width property bool isEnoughSpace: { if(ApplicationInfo.isMobile && parent.horizontalMode) return false return (parent.height - keyboard.height - controls.height) > 2.5 * loadButton.height } anchors { right: !isEnoughSpace ? controls.left : parent.right top: !isEnoughSpace ? parent.top : controls.bottom margins: 10 } spacing: 10 - Button { + GCButton { id: loadButton - style: GCButtonStyle { textSize: "regular"} + textSize: "regular" width: parent.width text: qsTr("Load") onClicked: { creationHandler.loadWindow() } } - Button { + GCButton { id: saveButton - style: GCButtonStyle { textSize: "regular"} + textSize: "regular" width: parent.width text: qsTr("Save") onClicked: { var textToSave = {} textToSave["text"] = edit.getFormattedText(0, edit.length) creationHandler.saveWindow(textToSave) } } } Flickable { id: flick anchors { left: parent.left right: saveAndLoadButtons.left top: parent.top bottom: bar.top margins: 10 } contentWidth: edit.paintedWidth contentHeight: edit.paintedHeight clip: true flickableDirection: Flickable.VerticalFlick function ensureVisible(r) { if (contentX >= r.x) contentX = r.x; else if (contentX+width <= r.x+r.width) contentX = r.x+r.width-width; if (contentY >= r.y) contentY = r.y; else if (contentY+height <= r.y+r.height) contentY = r.y+r.height-height; } TextEdit { id: edit width: flick.width height: flick.height focus: true wrapMode: TextEdit.Wrap onCursorRectangleChanged: flick.ensureVisible(cursorRectangle) textFormat: TextEdit.RichText color: "#373737" font { pointSize: (18 + ApplicationSettings.baseFontSize) * ApplicationInfo.fontRatio capitalization: ApplicationSettings.fontCapitalization weight: Font.DemiBold family: GCSingletonFontLoader.fontLoader.name letterSpacing: ApplicationSettings.fontLetterSpacing wordSpacing: 10 } cursorDelegate: Rectangle { id: cursor width: 10 // height should be set automatically as mention in cursorRectangle property // documentation but it does not work height: parent.cursorRectangle.height color: '#DF543D' SequentialAnimation on opacity { running: true loops: Animation.Infinite PropertyAnimation { to: 0.2 duration: 1000 } PropertyAnimation { to: 1 duration: 1000 } } } function insertText(text) { edit.insert(cursorPosition, text) } function backspace() { if(cursorPosition > 0) { moveCursorSelection(cursorPosition - 1, TextEdit.SelectCharacters) cut() } } function newline() { insert(cursorPosition, "

") } function formatLineWith(tag) { var text = getText(0, length) var initialPosition = cursorPosition var first = cursorPosition - 1 for(; first >= 0; first--) { if(text.charCodeAt(first) === 8233) break } first++ var last = cursorPosition for(; last < text.length; last++) { if(text.charCodeAt(last) === 8233) break } var line = getText(first, last) remove(first, last) insert(first, '<' + tag + '>' + line + '') cursorPosition = initialPosition } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | reload } onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: activity.home() onReloadClicked: edit.text = '' } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter width: parent.width visible: ApplicationSettings.isVirtualKeyboard && !ApplicationInfo.isMobile onKeypress: { if(text == backspace) edit.backspace() else if(text == newline) edit.newline() else edit.insertText(text) } shiftKey: true onError: console.log("VirtualKeyboard error: " + msg); readonly property string newline: "\u21B2" function populate() { layout = [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" } ], [ { label: "A" }, { label: "B" }, { label: "C" }, { label: "D" }, { label: "E" }, { label: "F" }, { label: "G" }, { label: "H" }, { label: "I" } ], [ { label: "J" }, { label: "K" }, { label: "L" }, { label: "M" }, { label: "N" }, { label: "O" }, { label: "P" }, { label: "Q" }, { label: "R" } ], [ { label: "S" }, { label: "T" }, { label: "U" }, { label: "V" }, { label: "W" }, { label: "X" }, { label: "Y" }, { label: "Z" }, { label: " " }, { label: backspace }, { label: newline } ] ] } } } } diff --git a/src/activities/balancebox/Balancebox.qml b/src/activities/balancebox/Balancebox.qml index 6cf717bb8..5337a334f 100644 --- a/src/activities/balancebox/Balancebox.qml +++ b/src/activities/balancebox/Balancebox.qml @@ -1,609 +1,605 @@ /* GCompris - balancebox.qml * * Copyright (C) 2014-2016 Holger Kaelberer * * Authors: * Holger Kaelberer * * 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.Window 2.2 import QtSensors 5.0 import QtGraphicalEffects 1.0 import GCompris 1.0 import Box2D 2.0 -import QtQuick.Controls 1.5 - import "../../core" import "editor/" import "balancebox.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity property string mode: "play" // "play" or "test" property string levelSet: "builtin" // "builtin" or "user" // When the user launches the activity in "user" mode by default(due to previously save config mode) for the first time after updating GCompris, default user file must be loaded as they must be having created levels in it. // From next time onwards, the saved file path is loaded. Refer to line 567. property string loadedFilePath: (levelSet == "builtin") ? Activity.builtinFile : Activity.userFile property var testLevel property bool inForeground: false // to avoid unneeded reconfigurations property bool alwaysStart: true // enforce start signal for editor-to-testing- and returning from config-transition property bool needRestart: true onWidthChanged: if (inForeground && pageView.currentItem === activity) Activity.reconfigureScene(); onHeightChanged: if (inForeground && pageView.currentItem === activity) Activity.reconfigureScene(); onStart: { inForeground = true; focus = true; } onStop: inForeground = false; Keys.onPressed: Activity.processKeyPress(event.key) Keys.onReleased: Activity.processKeyRelease(event.key) pageComponent: Image { - id: background + id: backgroundActivity source: Activity.baseUrl + "/maze_bg.svg" sourceSize.width: parent.width anchors.fill: parent signal start signal stop function startEditor() { editorLoader.active = true; if (activity.mode == "test") displayDialogs([dialogActivityConfig, editorLoader.item]); else displayDialog(editorLoader.item); } function handleBackEvent() { if (activity.mode == "test") { startEditor(); return true; } else return false; } Keys.onEscapePressed: event.accepted = handleBackEvent(); Keys.onReleased: { if (event.key === Qt.Key_Back) event.accepted = handleBackEvent(); } Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) items.dpi = Math.round(Screen.pixelDensity*25.4); } onStart: if (activity.needRestart) { Activity.start(items); activity.needRestart = false; } else Activity.initLevel(); onStop: { Activity.stop(); activity.needRestart = true; } QtObject { id: items property string mode: activity.mode property string levelSet: activity.levelSet property string filePath: activity.loadedFilePath property var testLevel: activity.testLevel property Item main: activity.main - property alias background: background + property alias background: backgroundActivity property alias bar: bar property alias bonus: bonus property alias tilt: tilt property alias timer: timer property alias ball: ball property alias file: file property int ballSize: cellSize - 2*wallSize property alias mapWrapper: mapWrapper property int cellSize: mapWrapper.length / Math.min(mapWrapper.rows, mapWrapper.columns) property int wallSize: cellSize / 5 property var world: physicsWorld property alias keyboardTimer: keyboardTimer property var ballType: Fixture.Category1 property var wallType: Fixture.Category2 property var holeType: Fixture.Category3 property var goalType: Fixture.Category4 property var buttonType: Fixture.Category5 property alias parser: parser property double dpi property GCSfx audioEffects: activity.audioEffects property Loading loading: activity.loading } Loader { id: editorLoader active: false sourceComponent: BalanceboxEditor { id: editor visible: true testBox: activity onClose: activity.home() } } JsonParser { id: parser onError: console.error("Balancebox: Error parsing JSON: " + msg); } Rectangle { id: mapWrapper property double margin: 20 property int columns: 0 property int rows: 0 - property double length: Math.min(background.height - - 2*mapWrapper.margin, background.width - 2*mapWrapper.margin); + property double length: Math.min(backgroundActivity.height - + 2*mapWrapper.margin, backgroundActivity.width - 2*mapWrapper.margin); color: "#E3DEDB" width: length height: length anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter transform: [ Rotation { origin.x: mapWrapper.width / 2 origin.y: mapWrapper.height / 2 axis { x: 1; y: 0; z: 0 } angle: ApplicationInfo.isMobile ? 0 : -items.tilt.xRotation }, Rotation { origin.x: mapWrapper.width / 2 origin.y: mapWrapper.height / 2 axis { x: 0; y: 1; z: 0 } angle: ApplicationInfo.isMobile ? 0 : items.tilt.yRotation } ] // right: Wall { id: rightWall width: items.wallSize height: parent.height + items.wallSize anchors.left: mapWrapper.right anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // bottom: Wall { id: bottomWall width: parent.width + items.wallSize height: items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.bottom anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // top: Wall { id: topWall width: parent.width + items.wallSize height: items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } // left: Wall { id: leftWall width: items.wallSize height: parent.height + items.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - items.wallSize/2 anchors.top: parent.top anchors.topMargin: -items.wallSize/2 shadow: false shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize) shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize) } BalanceItem { id: ball world: physicsWorld imageSource: Activity.baseUrl + "/ball.svg" visible: false scale: 1.0 width: items.ballSize height: items.ballSize z: 3 // above other BalanceItems categories: items.ballType collidesWith: items.wallType | items.holeType | items.goalType | items.buttonType density: 1 friction: Activity.friction linearDamping: Activity.friction restitution: Activity.restitution bodyType: Body.Dynamic shadow: true shadowHorizontalOffset: (items.tilt.yRotation > 0) ? Math.min(items.tilt.yRotation, items.wallSize) : Math.max(items.tilt.yRotation, -items.wallSize) shadowVerticalOffset: (items.tilt.xRotation > 0) ? Math.min(items.tilt.xRotation, items.wallSize) : Math.max(items.tilt.xRotation, -items.wallSize) Behavior on scale { NumberAnimation { id: fallAnimation duration: 1000 } } onBeginContact: { if (other.categories !== items.wallType) Activity.addBallContact(other); else { // sound-effect on each contact with a wall might be too annoying: //items.audioEffects.stop(); //items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav"); } } onEndContact: { if (other.categories !== items.wallType) Activity.removeBallContact(other); } } World { id: physicsWorld gravity: Qt.point(0, 0) // we calculate acceleration ourselves pixelsPerMeter: Activity.box2dPpm // default: 32 timeStep: Activity.step/1000 // default: 1/60 } DebugDraw { id: debugDraw world: physicsWorld visible: Activity.debugDraw z: 100 } } Timer { id: timer interval: Activity.step; running: false; repeat: true onTriggered: Activity.moveBall() } Item { id: tilt property double xRotation: 0 property double yRotation: 0 property bool swapAxes: false property bool invertX: false property bool invertY: false onXRotationChanged: { if (xRotation > 90) xRotation = 90; else if (xRotation < -90) xRotation = -90; } onYRotationChanged: { if (yRotation > 90) yRotation = 90; else if (yRotation < -90) yRotation = -90; } TiltSensor { id: tiltSensor active: ApplicationInfo.isMobile ? true : false onReadingChanged: { if (!tilt.swapAxes) { tilt.xRotation = tilt.invertX ? -reading.xRotation : reading.xRotation; tilt.yRotation = tilt.invertY ? -reading.yRotation : reading.yRotation; } else { tilt.xRotation = tilt.invertX ? -reading.yRotation : reading.yRotation; tilt.yRotation = tilt.invertY ? -reading.xRotation : reading.xRotation; } tiltText.text = "X/Y Rotation: " + tiltSensor.reading.xRotation + "/" + tiltSensor.reading.yRotation } } } Item { id: textWrapper anchors.left: parent.left anchors.top: parent.top width: parent.width height: parent.height / 3 visible: Activity.debugDraw Text { id: tiltText anchors.left: parent.left anchors.top: parent.top text: "X/Y Rotation: " + tilt.xRotation + "/" + tilt.yRotation font.pointSize: 12 } Text { id: posText anchors.left: parent.left anchors.top: tiltText.bottom text: "X/Y = " + ball.x + "/" + ball.y font.pointSize: 12 } } MultiPointTouchArea { anchors.fill: parent touchPoints: [ TouchPoint { id: point1 } ] property real startX property real startY property int offset: 30 function reset() { startX = point1.x startY = point1.y } onPressed: { reset() } onUpdated: { var moveX = point1.x - startX var moveY = point1.y - startY // Find the direction with the most move if(Math.abs(moveX) * ApplicationInfo.ratio > offset && Math.abs(moveX) > Math.abs(moveY)) { if(moveX > offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Right) reset() } else if(moveX < -offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Left) reset() } } else if(Math.abs(moveY) * ApplicationInfo.ratio > offset && Math.abs(moveX) < Math.abs(moveY)) { if(moveY > offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Down) reset() } else if(moveY < -offset * ApplicationInfo.ratio) { Activity.processKeyPress(Qt.Key_Up) reset() } } } onReleased: { Activity.keyboardIsTilting = false } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: activity.mode == "play" ? (help | home | level | config ) : ( help | home ) } onHelpClicked: { // stop everything or the ball keeps moving while we're away: items.timer.stop(); displayDialog(dialogHelp); } onPreviousLevelClicked: if (!Activity.finishRunning) Activity.previousLevel() onNextLevelClicked: if (!Activity.finishRunning) Activity.nextLevel() onHomeClicked: { if (activity.mode == "test") - background.startEditor(); + backgroundActivity.startEditor(); else activity.home() } onConfigClicked: { items.timer.stop(); dialogActivityConfig.active = true // Set default values dialogActivityConfig.setDefaultValues(); displayDialog(dialogActivityConfig) } } Bonus { id: bonus looseSound: "qrc:/gcompris/src/core/resource/sounds/crash.wav" Component.onCompleted: { win.connect(Activity.nextLevel); loose.connect(Activity.initLevel); } } Timer { id: keyboardTimer interval: Activity.keyboardTimeStep; running: false repeat: false onTriggered: Activity.keyboardHandler() } GCCreationHandler { id: creationHandler readonly property bool isEditorActive: editorLoader.active && editorLoader.item.visible onFileLoaded: { if(!isEditorActive) activity.loadedFilePath = filePath else editorLoader.item.filename = filePath close() } parent: isEditorActive ? editorLoader.item : dialogActivityConfig } File { id: file } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias levelsBox: levelsBox property var availableLevels: [ { "text": qsTr("Built-in"), "value": "builtin" }, { "text": qsTr("User"), "value": "user" }, ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: levelsBox model: availableLevels background: dialogActivityConfig label: qsTr("Select your level set") } Column { spacing: 5 - Button { + GCButton { id: editorButton - style: GCButtonStyle {} height: levelsBox.height text: qsTr("Start Editor") visible: levelsBox.currentIndex == 1 - onClicked: background.startEditor() + onClicked: backgroundActivity.startEditor() } - Button { + GCButton { id: loadButton - style: GCButtonStyle {} height: levelsBox.height text: qsTr("Load saved levels") visible: levelsBox.currentIndex == 1 onClicked: creationHandler.loadWindow() } } } } } onClose: home(); onLoadData: { if(dataToSave && dataToSave["levels"]) { activity.levelSet = dataToSave["levels"]; if(dataToSave['filePath']) activity.loadedFilePath = dataToSave["filePath"] } } onSaveData: { var newLevels = dialogActivityConfig.configItem .availableLevels[dialogActivityConfig.configItem.levelsBox.currentIndex].value; var initialFilePath = dataToSave['filePath'] ? dataToSave['filePath'] : "" if(newLevels === "builtin") activity.loadedFilePath = Activity.builtinFile if (newLevels !== activity.levelSet || initialFilePath != activity.loadedFilePath) { activity.levelSet = newLevels; dataToSave = {"levels": activity.levelSet, "filePath": activity.loadedFilePath}; activity.needRestart = true; } } dataValidationFunc: function() { var newLevels = dialogActivityConfig.configItem .availableLevels[dialogActivityConfig.configItem.levelsBox.currentIndex].value if (newLevels === "user" && activity.loadedFilePath === Activity.builtinFile) { Core.showMessageDialog(dialogActivityConfig, qsTr("You selected the user-defined level set, but you have not yet defined any user levels!
" + "Either create your user levels by starting the level editor or choose the 'built-in' level set."), qsTr("Ok"), null, "", null, null); return false; } return true; } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableLevels.length; i ++) { if(dialogActivityConfig.configItem.availableLevels[i].value === activity.levelSet) { dialogActivityConfig.configItem.levelsBox.currentIndex = i; break; } } } } } } diff --git a/src/activities/balancebox/editor/BalanceboxEditor.qml b/src/activities/balancebox/editor/BalanceboxEditor.qml index a0d42899c..11e5670cd 100644 --- a/src/activities/balancebox/editor/BalanceboxEditor.qml +++ b/src/activities/balancebox/editor/BalanceboxEditor.qml @@ -1,679 +1,674 @@ /* GCompris - BalanceboxEditor.qml * * Copyright (C) 2015 Holger Kaelberer * * Authors: * Holger Kaelberer * * 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 QtGraphicalEffects 1.0 import GCompris 1.0 -import QtQuick.Controls 1.5 +import QtQuick.Controls 2.0 import "../../../core" import ".." import "balanceboxeditor.js" as Activity Item { id: editor property string filename: "" onFilenameChanged: Activity.initEditor(props) property ActivityBase currentActivity property var testBox property bool isTesting: false // props needed for stackView integration: signal close signal start signal stop property bool isDialog: true property bool alwaysStart: true // enforce start signal for configDialog-to-editor-transition function handleBackEvent() { if (!isTesting) { if (Activity.levelChanged) Activity.warnUnsavedChanges(function() {stop(); home();}, function() {}); else { stop(); home(); } return true; } else return false; } Keys.onEscapePressed: event.accepted = handleBackEvent(); Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = handleBackEvent(); } else event.accepted = false; } onStart: { focus = true; if (!isTesting) Activity.initEditor(props); else stopTesting(); } onStop: testBox.focus = true; QtObject { id: props property int columns: 10 property int rows: 10 property int currentTool property alias editor: editor property alias mapModel: mapModel property alias mapWrapper: mapWrapper property int cellSize: mapWrapper.length / Math.min(mapWrapper.rows, mapWrapper.columns) property int wallSize: cellSize / 5 property int ballSize: cellSize - 2*wallSize property alias toolBox: toolBox property string contactValue: "1" property int lastOrderNum: 0 property alias file: file property alias parser: parser property alias bar: bar property int lastGoalIndex: -1 property int lastBallIndex: -1 property alias editorWorker: editorWorker } function startTesting() { editor.isTesting = true; testBox.mode = "test"; testBox.testLevel = Activity.modelToLevel(); testBox.needRestart = true; back(testBox); } function stopTesting() { editor.isTesting = false; testBox.mode = "play"; testBox.testLevel = null; testBox.needRestart = true; } Rectangle { - id: background + id: backgroundEditor anchors.fill: parent File { id: file onError: console.error("File error: " + msg); } JsonParser { id: parser onError: console.error("Balanceboxeditor: Error parsing JSON: " + msg); } Column { id: toolBox2 anchors.top: mapWrapper.top anchors.left: mapWrapper.right anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.topMargin: 20 * ApplicationInfo.ratio spacing: 5 * ApplicationInfo.ratio - width: (background.width - mapWrapper.width - props.wallSize - 20 * ApplicationInfo.ratio) / 2 + width: (backgroundEditor.width - mapWrapper.width - props.wallSize - 20 * ApplicationInfo.ratio) / 2 height: parent.height // anchors.topMargin: 20 - Button { + GCButton { id: loadButton width: parent.width height: props.cellSize - style: GCButtonStyle {} text: qsTr("Load") onClicked: creationHandler.loadWindow() } - Button { + GCButton { id: saveButton width: parent.width height: props.cellSize - style: GCButtonStyle {} text: qsTr("Save") onClicked: creationHandler.saveWindow(Activity.saveModel()) } - Button { + GCButton { id: testButton width: parent.width height: props.cellSize - style: GCButtonStyle {} text: qsTr("Test") onClicked: editor.startTesting(); } } Column { id: toolBox anchors.top: mapWrapper.top anchors.topMargin: 20 * ApplicationInfo.ratio anchors.left: parent.left anchors.leftMargin: 10 width: (mapWrapper.x - 20) spacing: 5 * ApplicationInfo.ratio Component.onCompleted: clearTool.selected = true; function setCurrentTool(item) { props.currentTool = item.type; if (clearTool !== item) clearTool.selected = false; if (hWallTool !== item) hWallTool.selected = false; if (vWallTool !== item) vWallTool.selected = false; if (holeTool !== item) holeTool.selected = false; if (ballTool !== item) ballTool.selected = false; if (contactTool !== item) contactTool.selected = false; if (goalTool !== item) goalTool.selected = false; } EditorTool { id: clearTool type: Activity.TOOL_CLEAR anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize - 2 onSelectedChanged: { if (selected) { toolBox.setCurrentTool(clearTool); } } Image { id: clear source: "qrc:/gcompris/src/core/resource/cancel.svg" width: props.cellSize - 4 height: props.cellSize - 4 anchors.centerIn: parent anchors.margins: 3 } } EditorTool { id: hWallTool type: Activity.TOOL_H_WALL anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize onSelectedChanged: { if (selected) { toolBox.setCurrentTool(hWallTool); } } Wall { id: hWall width: props.cellSize height: props.wallSize anchors.centerIn: parent anchors.margins: 3 } } EditorTool { id: vWallTool anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize type: Activity.TOOL_V_WALL onSelectedChanged: { if (selected) { toolBox.setCurrentTool(vWallTool); } } Wall { id: vWall width: props.wallSize height: props.cellSize - 4 anchors.centerIn: parent anchors.margins: 3 } } EditorTool { id: holeTool anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize type: Activity.TOOL_HOLE onSelectedChanged: { if (selected) { toolBox.setCurrentTool(holeTool); } } BalanceItem { id: hole width: props.cellSize - props.wallSize / 2 height: props.cellSize - props.wallSize / 2 anchors.centerIn: parent anchors.margins: props.wallSize / 2 visible: true imageSource: Activity.baseUrl + "/hole.svg" } } EditorTool { id: ballTool anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize type: Activity.TOOL_BALL onSelectedChanged: { if (selected) { toolBox.setCurrentTool(ballTool); } } BalanceItem { id: ball width: props.cellSize - props.wallSize / 2 height: parent.height - props.wallSize / 2 anchors.centerIn: parent anchors.margins: props.wallSize / 2 visible: true imageSource: Activity.baseUrl + "/ball.svg" } } EditorTool { id: goalTool anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize type: Activity.TOOL_GOAL onSelectedChanged: { if (selected) { toolBox.setCurrentTool(goalTool); } } BalanceItem { id: goal width: props.cellSize - props.wallSize height: props.cellSize - props.wallSize anchors.centerIn: parent anchors.margins: props.wallSize / 2 z: 1 imageSource: Activity.baseUrl + "/door.svg" } } EditorTool { id: contactTool anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: props.cellSize type: Activity.TOOL_CONTACT onSelectedChanged: { if (selected) { toolBox.setCurrentTool(contactTool); } } Row { id: contactToolRow spacing: 5 width: contact.width + contactTextInput.width + spacing anchors.centerIn: parent BalanceContact { id: contact width: props.cellSize - props.wallSize height: props.cellSize - props.wallSize anchors.margins: props.wallSize / 2 pressed: false orderNum: 99 text: props.contactValue z: 1 } SpinBox { id: contactTextInput width: contact.width * 2 height: contact.height value: props.contactValue - maximumValue: 99 - minimumValue: 1 - decimals: 0 - horizontalAlignment: Qt.AlignHCenter + from: 1 + to: 99 font.family: GCSingletonFontLoader.fontLoader.name font.pixelSize: height / 2 onValueChanged: if (value != props.contactValue) props.contactValue = value; } } } } WorkerScript { id: editorWorker source: "editor_worker.js" onMessage: { // worker finished, update all changed values (except the model): props.contactValue = messageObject.maxContactValue; props.lastBallIndex = messageObject.lastBallIndex; props.lastGoalIndex = messageObject.lastGoalIndex; props.lastOrderNum = messageObject.lastOrderNum; Activity.targetList = messageObject.targetList; testBox.loading.stop(); } } ListModel { id: mapModel } Rectangle { id: mapWrapper property double margin: 20 property int columns: props.columns property int rows: props.rows - property double length: Math.min(background.height - - 2*mapWrapper.margin, background.width - 2*mapWrapper.margin); + property double length: Math.min(backgroundEditor.height - + 2*mapWrapper.margin, backgroundEditor.width - 2*mapWrapper.margin); color: "#E3DEDB" width: length height: length anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter Grid { id: mapGrid columns: mapWrapper.columns rows: mapWrapper.rows anchors.fill: parent width: parent.width height: parent.height spacing: 0 Repeater { id: mapGridRepeater model: mapModel//mapGrid.columns * mapGrid.rows delegate: Item { // cell wrapper id: cell width: props.cellSize height: props.cellSize property bool highlighted: false Loader { id: northWallLoader active: value & Activity.NORTH width: props.cellSize + props.wallSize height: props.wallSize anchors.top: parent.top anchors.left: parent.left anchors.topMargin: -props.wallSize / 2 anchors.leftMargin: -props.wallSize / 2 sourceComponent: Wall { id: northWall shadow: false anchors.centerIn: parent z: 1 } } Loader { id: eastWallLoader active: value & Activity.EAST || (cell.highlighted && props.currentTool === Activity.TOOL_V_WALL) width: props.wallSize height: props.cellSize + props.wallSize anchors.bottom: parent.bottom anchors.right: parent.right anchors.bottomMargin: -props.wallSize / 2 anchors.rightMargin: -props.wallSize / 2 sourceComponent: Wall { id: eastWall anchors.centerIn: parent shadow: false z: 1 } } Loader { id: southWallLoader active: value & Activity.SOUTH || (cell.highlighted && props.currentTool === Activity.SOUTH) width: props.cellSize + props.wallSize height: props.wallSize anchors.bottom: parent.bottom anchors.left: parent.left anchors.bottomMargin: -props.wallSize / 2 anchors.leftMargin: -props.wallSize / 2 sourceComponent: Wall { id: southWall anchors.centerIn: parent shadow: false z: 1 } } Loader { id: westWallLoader active: value & Activity.WEST width: props.wallSize height: props.cellSize + props.wallSize anchors.bottom: parent.bottom anchors.left: parent.left anchors.bottomMargin: -props.wallSize / 2 anchors.leftMargin: -props.wallSize / 2 sourceComponent: Wall { id: westWall anchors.centerIn: parent shadow: false z: 1 } } Loader { id: doorLoader active: value & Activity.GOAL || (cell.highlighted && props.currentTool === Activity.TOOL_GOAL) anchors.centerIn: parent width: props.cellSize - props.wallSize height: props.cellSize - props.wallSize sourceComponent: BalanceItem { id: goal anchors.centerIn: parent z: 1 imageSource: Activity.baseUrl + "/door.svg" } } Loader { id: holeLoader active: value & Activity.HOLE || (cell.highlighted && props.currentTool === Activity.TOOL_HOLE) anchors.centerIn: parent sourceComponent: BalanceItem { id: hole width: props.ballSize height:props.ballSize anchors.centerIn: parent z: 1 imageSource: Activity.baseUrl + "/hole.svg" } } Loader { id: ballLoader active: value & Activity.START || (cell.highlighted && props.currentTool === Activity.TOOL_BALL) anchors.centerIn: parent sourceComponent: BalanceItem { id: ball width: props.ballSize height:props.ballSize anchors.centerIn: parent visible: true imageSource: Activity.baseUrl + "/ball.svg" z: 1 } } Loader { id: contactLoader active: (value & Activity.CONTACT) || (cell.highlighted && props.currentTool === Activity.TOOL_CONTACT) width: props.cellSize - props.wallSize height: props.cellSize - props.wallSize anchors.centerIn: parent sourceComponent: BalanceContact { id: contact anchors.centerIn: parent visible: true pressed: false text: contactValue z: 1 } } Rectangle { // bounding rect id: cellRect width: props.cellSize height: props.cellSize color: "transparent" border.width: 1 border.color: cell.highlighted ? "yellow": "lightgray" z: 10 MouseArea { id: cellMouse anchors.fill: parent hoverEnabled: ApplicationInfo.isMobile ? false : true onEntered: cell.highlighted = true onExited: cell.highlighted = false onClicked: { editor.focus = true; Activity.modifyMap(props, row, col); } } } } } } // right: Wall { id: rightWall width: props.wallSize height: parent.height + props.wallSize anchors.left: mapWrapper.right anchors.leftMargin: - props.wallSize/2 anchors.top: parent.top anchors.topMargin: -props.wallSize/2 shadow: false } // bottom: Wall { id: bottomWall width: parent.width + props.wallSize height: props.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - props.wallSize/2 anchors.top: parent.bottom anchors.topMargin: -props.wallSize/2 shadow: false } // top: Wall { id: topWall width: parent.width + props.wallSize height: props.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - props.wallSize/2 anchors.top: parent.top anchors.topMargin: -props.wallSize/2 shadow: false } // left: Wall { id: leftWall width: props.wallSize height: parent.height + props.wallSize anchors.left: mapWrapper.left anchors.leftMargin: - props.wallSize/2 anchors.top: parent.top anchors.topMargin: -props.wallSize/2 shadow: false } } } Bar { id: bar content: BarEnumContent { value: home | level } // FIXME: add dedicated editor help? onPreviousLevelClicked: { if (Activity.currentLevel > 0) { if (Activity.levelChanged) Activity.warnUnsavedChanges(Activity.previousLevel, function() {}); else Activity.previousLevel(); } } onNextLevelClicked: { if (Activity.levelChanged) Activity.warnUnsavedChanges(function() { Activity.levelChanged = false; // mark unchanged early to make check in nextLevel() work Activity.nextLevel(); }, function() {}); else Activity.nextLevel(); } onHomeClicked: { if (Activity.levelChanged) Activity.warnUnsavedChanges(activity.home, function() {}); else { activity.home() } } } } diff --git a/src/activities/categorization/Categorization.qml b/src/activities/categorization/Categorization.qml index ceff9d62b..352bf6cbd 100644 --- a/src/activities/categorization/Categorization.qml +++ b/src/activities/categorization/Categorization.qml @@ -1,239 +1,239 @@ /* GCompris - categorization.qml * * Copyright (C) 2016 Divyam Madaan * * Authors: * Divyam Madaan * * 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 2.0 import GCompris 1.0 import "../../core" import "categorization.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core import "." ActivityBase { id: activity onStart: focus = true onStop: {} property string boardsUrl: ":/gcompris/src/activities/categorization/resource/board/" property bool vert: background.width <= background.height property var barAtStart pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" anchors.fill: parent sourceSize.width: parent.width signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias categoryReview: categoryReview property alias menuScreen: menuScreen property alias menuModel: menuScreen.menuModel property alias dialogActivityConfig: dialogActivityConfig property string mode: "easy" property bool instructionsVisible: true property bool categoryImageChecked: (mode === "easy" || mode === "medium") property bool scoreChecked: (mode === "easy" || mode === "expert") property bool iAmReadyChecked: (mode === "expert") property bool displayUpdateDialogAtStart: true property var details property bool categoriesFallback property alias file: file property var categories: directory.getFiles(boardsUrl) } function hideBar() { barAtStart = ApplicationSettings.isBarHidden; if(categoryReview.width >= categoryReview.height) ApplicationSettings.isBarHidden = false; else ApplicationSettings.isBarHidden = true; } onStart: { Activity.init(items, boardsUrl) dialogActivityConfig.getInitialConfiguration() Activity.start() hideBar() } onStop: { dialogActivityConfig.saveDatainConfiguration() ApplicationSettings.isBarHidden = barAtStart; } MenuScreen { id: menuScreen File { id: file onError: console.error("File error: " + msg); } } Directory { id: directory } CategoryReview { id: categoryReview } - ExclusiveGroup { - id: configOptions + ButtonGroup { + id: childGroup } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias easyModeBox: easyModeBox property alias mediumModeBox: mediumModeBox property alias expertModeBox: expertModeBox GCDialogCheckBox { id: easyModeBox width: column.width - 50 text: qsTr("Put together all the elements from a category (with score)") checked: (items.mode == "easy") ? true : false - exclusiveGroup: configOptions + ButtonGroup.group: childGroup onCheckedChanged: { if(easyModeBox.checked) { items.mode = "easy" menuScreen.iAmReady.visible = false } } } GCDialogCheckBox { id: mediumModeBox width: easyModeBox.width text: qsTr("Put together all the elements from a category (without score)") checked: (items.mode == "medium") ? true : false - exclusiveGroup: configOptions + ButtonGroup.group: childGroup onCheckedChanged: { if(mediumModeBox.checked) { items.mode = "medium" menuScreen.iAmReady.visible = false } } } GCDialogCheckBox { id: expertModeBox width: easyModeBox.width text: qsTr("Discover a category, grouping elements together") checked: (items.mode == "expert") ? true : false - exclusiveGroup: configOptions + ButtonGroup.group: childGroup onCheckedChanged: { if(expertModeBox.checked) { items.mode = "expert" menuScreen.iAmReady.visible = true } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] if(dataToSave && dataToSave["displayUpdateDialogAtStart"]) items.displayUpdateDialogAtStart = (dataToSave["displayUpdateDialogAtStart"] == "true") ? true : false } onSaveData: { dataToSave["data"] = Activity.categoriesToSavedProperties(dataToSave) dataToSave["mode"] = items.mode dataToSave["displayUpdateDialogAtStart"] = items.displayUpdateDialogAtStart ? "true" : "false" } onClose: home() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: menuScreen.started ? withConfig : withoutConfig property BarEnumContent withConfig: BarEnumContent { value: help | home | config } property BarEnumContent withoutConfig: BarEnumContent { value: home | level } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: { if(items.menuScreen.started) activity.home() else if(items.categoryReview.started) Activity.launchMenuScreen() } onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Loader { id: categoriesFallbackDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("You don't have all the images for this activity. " + "Press Update to get the complete dataset. " + "Press the Cross to play with demo version or 'Never show this dialog later' if you want to never see again this dialog.") button1Text: qsTr("Update the image set") button2Text: qsTr("Never show this dialog later") onClose: items.categoriesFallback = false onButton1Hit: DownloadManager.downloadResource('data2/words/words.rcc') onButton2Hit: { items.displayUpdateDialogAtStart = false; dialogActivityConfig.saveDatainConfiguration() } } anchors.fill: parent focus: true active: items.categoriesFallback && items.displayUpdateDialogAtStart onStatusChanged: if (status == Loader.Ready) item.start() } } } diff --git a/src/activities/categorization/MenuScreen.qml b/src/activities/categorization/MenuScreen.qml index c5bc004bd..956d3745c 100644 --- a/src/activities/categorization/MenuScreen.qml +++ b/src/activities/categorization/MenuScreen.qml @@ -1,248 +1,247 @@ /* GCompris - MenuScreen.qml * * Copyright (C) Divyam Madaan (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.5 import "../../core" import "categorization.js" as Activity Image { id: menuScreen anchors.fill: parent fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" sourceSize.width: Math.max(parent.width, parent.height) opacity: 0 property alias menuModel: menuModel property alias iAmReady: iAmReady property bool keyboardMode: false property bool started: opacity == 1 Behavior on opacity { PropertyAnimation { duration: 200 } } visible: opacity != 0 function start() { focus = true forceActiveFocus() menuGrid.currentIndex = 0 opacity = 1 } function stop() { focus = false opacity = 0 } Keys.onEscapePressed: { home() } Keys.enabled: (items.mode == "expert") ? false : true Keys.onPressed: { if(event.key === Qt.Key_Space) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Enter) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Return) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Left) { menuGrid.moveCurrentIndexLeft() event.accepted = true } if(event.key === Qt.Key_Right) { menuGrid.moveCurrentIndexRight() event.accepted = true } if(event.key === Qt.Key_Up) { menuGrid.moveCurrentIndexUp() event.accepted = true } if(event.key === Qt.Key_Down) { menuGrid.moveCurrentIndexDown() event.accepted = true } } Keys.onReleased: { keyboardMode = true event.accepted = false } // sections property int iconWidth: 180 * ApplicationInfo.ratio property int iconHeight: 180 * ApplicationInfo.ratio property int levelCellWidth: background.width / Math.floor(background.width / iconWidth ) property int levelCellHeight: iconHeight * 1.2 ListModel { id: menuModel } GridView { id: menuGrid anchors { fill: parent bottomMargin: bar.height } cellWidth: levelCellWidth cellHeight: levelCellHeight clip: true model: menuModel keyNavigationWraps: true property int spacing: 10 ReadyButton { id: iAmReady focus: true visible: items.iAmReadyChecked onClicked: { Activity.startCategory() } } delegate: Item { id: delegateItem width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing property string sectionName: name opacity: (items.mode == "expert") ? 0.25 : 1 Rectangle { id: activityBackground width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { id: containerImage source: image anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter height: activityBackground.height*0.8 - 6 width: height anchors.margins: 5 sourceSize.height: height fillMode: Image.PreserveAspectCrop clip: true } GCText { id: categoryName anchors.top: containerImage.bottom horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width: activityBackground.width height: activityBackground.height*0.2 - 6 fontSizeMode: Text.Fit elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: name opacity: (items.mode == "expert") ? 0 : 1 } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground enabled: menuScreen.started && items.mode !== "expert" onClicked: selectCurrentItem() } function selectCurrentItem() { particles.burst(50) Activity.storeCategoriesLevels(index) } Image { source: "qrc:/gcompris/src/activities/menu/resource/" + ( favorite ? "all.svg" : "all_disabled.svg" ); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: { for(var i = 0; i < items.menuModel.count; i++) { var category = items.menuModel.get(i) var categoryIndex = category.index if(index == categoryIndex) menuModel.get(i)['favorite'] = !menuModel.get(i)['favorite'] } } } } } //delegate close highlight: Rectangle { width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing color: "#AA41AAC4" border.width: 3 border.color: "black" visible: (items.mode == "expert") ? false : true Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle { id: menusMask visible: false anchors.fill: menuGrid gradient: Gradient { GradientStop { position: 0.0; color: "#FFFFFFFF" } GradientStop { position: 0.92; color: "#FFFFFFFF" } GradientStop { position: 0.96; color: "#00FFFFFF" } } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: menuGrid maskSource: menusMask anchors.fill: menuGrid } } // grid view close } diff --git a/src/activities/checkers/Checkers.qml b/src/activities/checkers/Checkers.qml index 574d796fd..3ec50936e 100644 --- a/src/activities/checkers/Checkers.qml +++ b/src/activities/checkers/Checkers.qml @@ -1,490 +1,488 @@ /* GCompris - checkers.qml * * Copyright (C) 2017 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 "../../core" import "." import "checkers.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min(background.width / (items.numberOfCases + 2), (background.height - controls.height) / (items.numberOfCases + barHeightAddon)) : Math.min(background.width / (items.numberOfCases + 2), background.height / (items.numberOfCases + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property var movesToDo: [] property string message property alias trigComputerMove: trigComputerMove property int numberOfCases: 10 Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize / 2 leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true) ? 1 : 3 rows: (items.isPortrait==true) ? 2 : 1 width: (items.isPortrait==true) ? undefined : background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 60 * ApplicationInfo.ratio columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter - Button { + GCButton { id: undo height: 30 * ApplicationInfo.ratio width: height - text: ""; - style: GCButtonStyle { theme: "light" } + text: "" + theme: "light" onClicked: Activity.undo() enabled: (items.history && items.history.length > 0) ? true : false opacity: enabled ? 1 : 0 Image { source: 'qrc:/gcompris/src/activities/chess/resource/undo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } - Button { + GCButton { id: redo height: undo.height width: undo.height - text: ""; - style: GCButtonStyle { theme: "light" } + text: "" + theme: "light" onClicked: { Activity.redo() } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Image { source: 'qrc:/gcompris/src/activities/chess/resource/redo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } - Button { + GCButton { height: undo.height width: undo.height - text: ""; - style: GCButtonStyle { theme: "light" } + text: "" + theme: "light" enabled: items.twoPlayer opacity: enabled Image { source: 'qrc:/gcompris/src/activities/chess/resource/turn.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } onClicked: chessboard.swap() } } } Rectangle { id: boardBg width: items.cellSize * (items.numberOfCases + 0.2) height: items.cellSize * (items.numberOfCases + 0.2) z: 09 color: "#2E1B0C" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * items.numberOfCases height: items.cellSize * items.numberOfCases interactive: false keyNavigationWraps: true model: items.numberOfCases*items.numberOfCases layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / items.numberOfCases) % 2) == 1 ? Activity.url + 'checkers-white.svg' : Activity.url + 'checkers-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (9 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 width: items.cellSize - spacing height: items.cellSize - spacing z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove: false property bool jumpable: false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove || parent.jumpable ? 5 : 0 border.color: parent.acceptMove ? '#FF808080' : '#C0808080' radius: parent.acceptMove ? width*0.5 : 0 z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return undefined } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: items.cellSize - spacing source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (items.numberOfCases - 1 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 if(pieces.getPieceAt(pos)) pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to, moves) { items.movesToDo.push({"from": from, "to": to, "move": moves}); if(items.movesToDo.length == 1) { moveInternal(); } } function moveInternal() { var moveToDo = items.movesToDo[0] var from = moveToDo.from; var to = moveToDo.to; var moves = moveToDo.move; var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) if(moves.jumps.length != 0) items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) movingPiece = fromPiece if(moves.jumps.length !== 0) { listJumps = moves.jumps } else { // create the move if needed listJumps = [Activity.viewPosToEngine(from), Activity.viewPosToEngine(to)] } } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { if(pieces.itemAt(i).newPos === pos) return pieces.itemAt(i) } return undefined } } property var movingPiece: undefined property var listJumps property bool restartNeeded: false onListJumpsChanged: { if(listJumps.length >= 2) { if(!animationTimer.isRunning) animationTimer.restart(); else restartNeeded = true; } } SequentialAnimation { id: animationTimer onRunningChanged: { if(!running && restartNeeded) start() } ScriptAction { script: { restartNeeded = false var to = Activity.engineToViewPos(listJumps[1]) movingPiece.move(to) } } PauseAnimation { duration: 200 } ScriptAction { script: { listJumps.shift() // only shifting does not trigger the onChanged var tmp = listJumps listJumps = tmp // only change player once all the jumps have been done if(listJumps.length === 1) { items.movesToDo.shift() if(items.movesToDo.length > 0) { pieces.moveInternal() } else { Activity.refresh() movingPiece = undefined } } } } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/chess/Chess.qml b/src/activities/chess/Chess.qml index 1840d25fc..0849a8c47 100644 --- a/src/activities/chess/Chess.qml +++ b/src/activities/chess/Chess.qml @@ -1,486 +1,484 @@ /* GCompris - chess.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (Qt Quick port) * * 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 "../../core" import "." import "chess.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false property int coordsOpacity: 1 // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true property var fen: [ ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"] ] onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - controls.height - letters.childrenRect.height) / (8 + barHeightAddon)) : Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - letters.childrenRect.height) / (8.5 + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property string message property bool isWarningMessage property alias trigComputerMove: trigComputerMove Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true)?1:3 rows: (items.isPortrait==true)?2:1 width: (items.isPortrait==true)?undefined:background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } z: 20 width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: items.isWarningMessage ? "red" : "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 60 * ApplicationInfo.ratio columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter - Button { + GCButton { id: undo height: 30 * ApplicationInfo.ratio width: height text: ""; - style: GCButtonStyle { theme: "light" } + theme: "light" onClicked: Activity.undo() enabled: items.history.length > 0 ? 1 : 0 opacity: enabled Image { source: Activity.url + 'undo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } - Button { + GCButton { id: redo height: undo.height width: undo.height - text: ""; - style: GCButtonStyle { theme: "light" } + text: "" + theme: "light" onClicked: { if (!twoPlayers) { acceptClick = false; Activity.redo() } else { Activity.redo() } } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Image { source: Activity.url + 'redo.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } - Button { + GCButton { height: undo.height width: undo.height - text: ""; - style: GCButtonStyle { theme: "light" } + text: "" + theme: "light" enabled: items.twoPlayer opacity: enabled Image { source: Activity.url + 'turn.svg' height: parent.height width: height sourceSize.height: height fillMode: Image.PreserveAspectFit } onClicked: chessboard.swap() } } } Rectangle { id: boardBg width: items.cellSize * 8.2 height: boardBg.width z: 08 color: "#452501" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * 8 height: chessboard.width interactive: false keyNavigationWraps: true model: 64 layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / 8) % 2) == 1 ? Activity.url + 'chess-white.svg' : Activity.url + 'chess-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') coordsOpacity = 0 timerSwap.start() if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } Timer { id: timerSwap interval: 1500 running: false repeat: false onTriggered: coordsOpacity = 1 } Grid { id: letters anchors.left: chessboard.left anchors.top: chessboard.bottom opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { id: lettersA model: chessboard.rotation == 0 ? ["F", "G", "F", "E", "D", "C", "B", "A"] : ["A", "B", "C", "D", "E", "F", "G", "H"] GCText { x: items.cellSize * (index % 8) + (items.cellSize/2-width/2) y: items.cellSize * Math.floor(index / 8) text: modelData color: "#CBAE7B" } } } Grid { id: numbers anchors.left: chessboard.right anchors.top: chessboard.top opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { model: chessboard.rotation == 0 ? ["1", "2", "3", "4", "5", "6", "7", "8"] : ["8", "7", "6", "5", "4", "3", "2", "1"] GCText { x: items.cellSize * Math.floor(index / 8) + width y: items.cellSize * (index % 8) + (items.cellSize/2-height/2) text: modelData color: "#CBAE7B" } } } Rectangle { id: boardBorder width: items.cellSize * 10 height: boardBorder.width anchors.centerIn: boardBg z: -1 color: "#542D0F" border.color: "#3A1F0A" border.width: items.cellSize * 0.1 } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 width: items.cellSize - spacing height: square.width z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove : false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove ? 5 : 0 border.color: '#FF808080' z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return(undefined) } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: piece.width source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to) { var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) if(toPiece.img !== '') items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) fromPiece.move(to) } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { if(pieces.itemAt(i).newPos === pos) return pieces.itemAt(i) } return(undefined) } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/explore_farm_animals/AnimalLevels.qml b/src/activities/explore_farm_animals/AnimalLevels.qml index aa8554168..638eeeec7 100644 --- a/src/activities/explore_farm_animals/AnimalLevels.qml +++ b/src/activities/explore_farm_animals/AnimalLevels.qml @@ -1,117 +1,117 @@ /* GCompris - AnimalLevels.qml * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * Johnny Jazeix (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import "../../core" import "explore-level.js" as Activity Image { id: animalImg width: animalWidth height: animalHeight sourceSize.width: width sourceSize.height: height fillMode: Image.PreserveAspectFit property string name: name property alias starVisible: star.visible property int questionId property string title property string description property string imageSource property string question property string audio signal displayDescription(var animal) SequentialAnimation { id: anim running: true loops: 1 NumberAnimation { target: animalImg property: "rotation" from: 0; to: 360 duration: 400 + Math.floor(Math.random() * 400) easing.type: Easing.InOutQuad } } Image { id: star x: animalImg.width / 2.5 y: animalImg.height * 0.8 visible: false source:"qrc:/gcompris/src/core/resource/star.png" } MultiPointTouchArea { id: touchArea anchors.centerIn: parent // Make the item big enough to be clicked easily width: Math.max(parent.width, 55 * ApplicationInfo.ratio) height: Math.max(parent.height, 55 * ApplicationInfo.ratio) touchPoints: [ TouchPoint { id: point1 } ] - mouseEnabled: progressbar.value != progressbar.maximumValue && !items.bonus.isPlaying + mouseEnabled: progressbar.value != progressbar.to && !items.bonus.isPlaying onPressed: { - if(items.progressbar.value >= progressbar.maximumValue) { + if(items.progressbar.value >= progressbar.to) { return } var questionTargetId = items.questionOrder[Activity.items.progressbar.value] Activity.items.instruction.visible = false if (Activity.items.score.currentSubLevel === 1) { if(animalImg.audio) { audioVoices.play(animalImg.audio); } displayDescription(animalImg) star.visible = true; } else { if (questionId === questionTargetId) { animWin.start(); items.progressbar.value ++; items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav"); Activity.nextSubSubLevel(); } else { items.bonus.bad("smiley") } } } } SequentialAnimation { id: animWin running: false loops: 1 NumberAnimation { target: animalImg property: "rotation" from: 0; to: 360 duration: 600 easing.type: Easing.InOutQuad } } } diff --git a/src/activities/explore_farm_animals/ExploreLevels.qml b/src/activities/explore_farm_animals/ExploreLevels.qml index 4ac1991e5..b7053aa3e 100644 --- a/src/activities/explore_farm_animals/ExploreLevels.qml +++ b/src/activities/explore_farm_animals/ExploreLevels.qml @@ -1,305 +1,292 @@ /* GCompris - ExploreLevels.qml * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * Johnny Jazeix (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 -import QtQuick.Controls 1.5 import "../../core" import "explore-level.js" as Activity ActivityBase { id: activity property int numberOfLevels property string url property bool hasAudioQuestions onStart: focus = true onStop: {} pageComponent: Item { id: background /* In order to accept any screen ratio the play area is always a 1000x1000 * square and is centered in a big background image that is 2000x2000 */ Image { id: bg source: dataset.item.backgroundImage sourceSize.width: 2000 * ApplicationInfo.ratio sourceSize.height: 2000 * ApplicationInfo.ratio width: 2000 * background.playRatio height: width anchors.centerIn: parent } property bool horizontalLayout: background.width >= background.height property int playX: (activity.width - playWidth) / 2 property int playY: (activity.height - playHeight) / 2 property int playWidth: horizontalLayout ? activity.height : activity.width property int playHeight: playWidth property double playRatio: playWidth / 1000 focus: true signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property GCAudio audioVoices: activity.audioVoices property GCSfx audioEffects: activity.audioEffects property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias progressbar: progressbar property alias ok: ok property alias dataModel: dataModel property alias dataset: dataset property alias instruction: instruction property alias instructionText: instructionText property alias descriptionPanel: descriptionPanel property alias nextQuestion: nextQuestion property bool hasAudioQuestions: activity.hasAudioQuestions property string currentAudio property var questionOrder property var currentQuestion: items.dataset ? items.dataset.item.tab[items.questionOrder[progressbar.value]] : "" } Timer { id: nextQuestion repeat: false interval: 2000 onTriggered: { Activity.repeat(); } } Loader{ id: dataset asynchronous: false onStatusChanged: { if (status == Loader.Ready) { // create table of size N filled with numbers from 0 to N items.questionOrder = Array.apply(null, {length: items.dataModel.count}).map(Number.call, Number) } } } onStart: { Activity.start(items, url, numberOfLevels) } onStop: { Activity.stop() } Keys.onEscapePressed: { descriptionPanel.visible ? descriptionPanel.closeDescriptionPanel() : home() } Repeater { id: dataModel model: dataset && dataset.item && dataset.item.tab ? dataset.item.tab.length : 0 AnimalLevels { questionId: index source: dataset.item.tab[index].image x: background.playX + background.playWidth * dataset.item.tab[index].x - width / 2 y: background.playY + background.playHeight * dataset.item.tab[index].y - height / 2 width: background.playWidth * dataset.item.tab[index].width height: background.playHeight * dataset.item.tab[index].height title: dataset.item.tab[index].title description: dataset.item.tab[index].text imageSource: dataset.item.tab[index].image2 question: dataset.item.tab[index].text2 audio: dataset.item.tab[index].audio !== undefined ? dataset.item.tab[index].audio : "" Component.onCompleted: { displayDescription.connect(displayDescriptionItem) } } } function displayDescriptionItem(animal) { descriptionPanel.title = animal.title descriptionPanel.description = animal.description descriptionPanel.imageSource = animal.imageSource descriptionPanel.visible = true descriptionPanel.showDescriptionPanel() } AnimalDescriptionLevels { id: descriptionPanel width: parent.width height: parent.height z: instruction.z + 1 } Column { id: progress visible: items.score.currentSubLevel != 1 anchors.bottom: bar.top anchors.right: parent.right anchors.margins: 10 * ApplicationInfo.ratio - ProgressBar { + GCProgressBar { id: progressbar - height: progressbarText.height width: bar.width - property string message - onValueChanged: message = value + "/" + maximumValue - onMaximumValueChanged: message = value + "/" + maximumValue - - GCText { - id: progressbarText - anchors.centerIn: parent - fontSize: mediumSize - font.bold: true - color: "black" - text: progressbar.message - } + message: value + "/" + to } } Image { id: ok - visible: progressbar.value === progressbar.maximumValue + visible: progressbar.value === progressbar.to source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: questionText.height * 2 fillMode: Image.PreserveAspectFit anchors.right: progress.left anchors.bottom: bar.top anchors.margins: 10 * ApplicationInfo.ratio MouseArea { anchors.fill: parent onClicked: Activity.nextLevel() } } Row { id: row spacing: 10 * ApplicationInfo.ratio anchors.fill: parent anchors.margins: 10 * ApplicationInfo.ratio layoutDirection: leftCol.width === 0 ? Qt.RightToLeft : Qt.LeftToRight Column { id: leftCol spacing: 10 * ApplicationInfo.ratio Rectangle { id: instruction width: row.width - rightCol.width - 10 * ApplicationInfo.ratio height: instructionText.height color: "#CCCCCCCC" radius: 10 border.width: 3 border.color: "black" GCText { id: instructionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.centerIn: parent.Center color: "black" width: parent.width wrapMode: Text.Wrap text: (dataset.item && items.score.currentSubLevel - 1 != items.score.numberOfSubLevels && items.score.currentSubLevel != 0) ? dataset.item.instructions[items.score.currentSubLevel - 1].text : "" } MouseArea { anchors.fill: parent onClicked: instruction.visible = false enabled: instruction.visible } } Rectangle { id: question width: row.width - rightCol.width - 10 * ApplicationInfo.ratio height: questionText.height color: '#CCCCCCCC' radius: 10 border.width: 3 border.color: "black" visible: items.score.currentSubLevel == 3 || (items.score.currentSubLevel == 2 && !items.hasAudioQuestions) GCText { id: questionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.centerIn: parent.Center color: "black" width: parent.width wrapMode: Text.Wrap text: items.currentQuestion ? items.currentQuestion.text2 : "" } } } Column { id: rightCol spacing: 10 * ApplicationInfo.ratio Score { id: score anchors { bottom: undefined right: undefined } } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 60 * ApplicationInfo.ratio anchors.right: parent.right visible: items.score.currentSubLevel == 2 && activity.hasAudioQuestions //&& ApplicationSettings.isAudioVoicesEnabled onClicked: Activity.repeat(); } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.start(items, url, numberOfLevels) } Bonus { id: bonus } } } diff --git a/src/activities/explore_farm_animals/explore-level.js b/src/activities/explore_farm_animals/explore-level.js index 02b23c64f..472c56763 100644 --- a/src/activities/explore_farm_animals/explore-level.js +++ b/src/activities/explore_farm_animals/explore-level.js @@ -1,144 +1,144 @@ /* GCompris - explore-level.js * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * * 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 . */ .pragma library .import GCompris 1.0 as GCompris .import "qrc:/gcompris/src/core/core.js" as Core var numberOfLevels var items var url var currentLevel function start(items_,url_,levelCount_) { items = items_ url = url_ numberOfLevels = levelCount_ currentLevel = 1 items.score.currentSubLevel = 1 initLevel() } function stop() { items.audioVoices.stop() } function initLevel() { items.bar.level = currentLevel var filename = url + "board" + "/" + "board" + currentLevel + ".qml" items.dataset.source = filename items.progressbar.value = 0 - items.progressbar.maximumValue = items.dataModel.count + items.progressbar.to = items.dataModel.count items.score.numberOfSubLevels = items.hasAudioQuestions ? 3 : 2; // randomize the questions for level 2 and 3 Core.shuffle(items.questionOrder); // Change the currentSubLevel value to 1 to be sure to update the question value // else if you are sublevel 0 and go to last level, the question is not the good one items.progressbar.value = 1 items.progressbar.value = 0 items.descriptionPanel.visible = false items.instruction.visible = true reload(); } function nextLevel() { ++items.score.currentSubLevel if(numberOfLevels <= currentLevel && items.score.numberOfSubLevels < items.score.currentSubLevel) { currentLevel = 0 } if (items.score.numberOfSubLevels < items.score.currentSubLevel) { currentLevel++ items.score.currentSubLevel = 1 } initLevel(); // Stop audio if necessary (switch from level 2 at beginning to a new level for example) items.audioVoices.stop() if (items.score.currentSubLevel == 2) { items.progressbar.value = 0; initSubSubLevel(); } } function previousLevel() { --items.score.currentSubLevel if(currentLevel <= 1 && items.score.currentSubLevel < 1) { currentLevel = numberOfLevels items.score.currentSubLevel = items.score.numberOfSubLevels } else if(items.score.currentSubLevel < 1) { currentLevel-- items.score.currentSubLevel = items.score.numberOfSubLevels } initLevel(); // Stop audio if necessary (switch from level 2 at beginning to a new level for example) items.audioVoices.stop() if(items.score.currentSubLevel == 2 && items.hasAudioQuestions) { repeat(); } } function isComplete() { for(var i = 0 ; i < items.dataModel.count ; ++ i) { if(!items.dataModel.itemAt(i).starVisible) return false; } return true; } function initSubSubLevel(IsNext) { if(items.progressbar.value == items.dataModel.count) { items.bonus.good("smiley"); } if(items.score.currentSubLevel == 2 && items.hasAudioQuestions && getCurrentQuestion()) { repeat(); } } function nextSubSubLevel() { items.audioVoices.silence(2000) initSubSubLevel(true) } function reload() { for(var i = 0 ; i < items.dataModel.count ; ++ i) { items.dataModel.itemAt(i).starVisible = false; } } function repeat() { items.audioVoices.stop() items.audioVoices.clearQueue() items.audioVoices.append(getCurrentQuestion().audio); } function getCurrentQuestion() { return items.dataset.item.tab[items.questionOrder[items.progressbar.value]]; } diff --git a/src/activities/lang/MenuScreen.qml b/src/activities/lang/MenuScreen.qml index e41259726..08bdeb4a3 100644 --- a/src/activities/lang/MenuScreen.qml +++ b/src/activities/lang/MenuScreen.qml @@ -1,243 +1,242 @@ /* GCompris - MenuScreen.qml * * Copyright (C) Siddhesh suthar (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.5 import "../../core" import "lang.js" as Activity Image { id: menuScreen anchors.fill: parent fillMode: Image.PreserveAspectCrop source: Activity.baseUrl + "imageid-bg.svg" sourceSize.width: Math.max(parent.width, parent.height) opacity: 0 property alias menuModel: menuModel property bool keyboardMode: false property bool started: opacity == 1 Behavior on opacity { PropertyAnimation { duration: 200 } } function start() { focus = true forceActiveFocus() menuGrid.currentIndex = 0 opacity = 1 } function stop() { focus = false opacity = 0 } Keys.onEscapePressed: { home() } Keys.onPressed: { if(event.key === Qt.Key_Space) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Enter) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Return) { menuGrid.currentItem.selectCurrentItem() event.accepted = true } if(event.key === Qt.Key_Left) { menuGrid.moveCurrentIndexLeft() event.accepted = true } if(event.key === Qt.Key_Right) { menuGrid.moveCurrentIndexRight() event.accepted = true } if(event.key === Qt.Key_Up) { menuGrid.moveCurrentIndexUp() event.accepted = true } if(event.key === Qt.Key_Down) { menuGrid.moveCurrentIndexDown() event.accepted = true } } Keys.onReleased: { keyboardMode = true event.accepted = false } // Activities property int iconWidth: 180 * ApplicationInfo.ratio property int iconHeight: 180 * ApplicationInfo.ratio property int levelCellWidth: background.width / Math.floor(background.width / iconWidth ) property int levelCellHeight: iconHeight * 1.4 ListModel { id: menuModel } GridView { id: menuGrid anchors { fill: parent bottomMargin: bar.height } cellWidth: levelCellWidth cellHeight: levelCellHeight clip: true model: menuModel keyNavigationWraps: true property int spacing: 10 delegate: Item { id: delegateItem width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing property string sectionName: name Rectangle { id: activityBackground width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { id: containerImage source: image; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter width: iconWidth height: iconHeight anchors.margins: 5 GCText { id: title anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: Activity.items.categoriesTranslations[name] } - ProgressBar { + GCProgressBar { id: progressLang anchors.top: title.bottom anchors.topMargin: ApplicationInfo.ratio * 4 anchors.horizontalCenter: parent.horizontalCenter width: activityBackground.width height: 14 * ApplicationInfo.ratio - maximumValue: wordCount - minimumValue: 0 + from: 0 + to: wordCount value: progress - orientation: Qt.Horizontal + displayText: false } } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground enabled: menuScreen.started onClicked: selectCurrentItem() } function selectCurrentItem() { particles.burst(50) Activity.initLevel(index) } Image { source: "qrc:/gcompris/src/activities/menu/resource/" + ( favorite ? "all.svg" : "all_disabled.svg" ); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: { menuModel.get(index)['favorite'] = !menuModel.get(index)['favorite'] } } } } //delegate close highlight: Rectangle { width: levelCellWidth - menuGrid.spacing height: levelCellHeight - menuGrid.spacing color: "#AA41AAC4" border.width: 3 border.color: "black" visible: menuScreen.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle{ id: menusMask visible: false anchors.fill: menuGrid gradient: Gradient { GradientStop { position: 0.0; color: "#FFFFFFFF" } GradientStop { position: 0.92; color: "#FFFFFFFF" } GradientStop { position: 0.96; color: "#00FFFFFF"} } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: menuGrid maskSource: menusMask anchors.fill: menuGrid } } // grid view close } diff --git a/src/activities/letter-in-word/LetterInWord.qml b/src/activities/letter-in-word/LetterInWord.qml index c404f3c53..1a5f7781d 100644 --- a/src/activities/letter-in-word/LetterInWord.qml +++ b/src/activities/letter-in-word/LetterInWord.qml @@ -1,434 +1,434 @@ /* GCompris - LetterInWord.qml * * Copyright (C) 2014 Holger Kaelberer * 2016 Akshat Tandon * * Authors: * Holger Kaelberer (Click on Letter - Qt Quick port) * Akshat Tandon (Modifications to Click on Letter code * to make Letter in which word activity) * * 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 QtGraphicalEffects 1.0 -import QtQuick.Controls 1.5 +import QtQuick.Controls 2.0 import GCompris 1.0 import "../../core" import "letter-in-word.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity focus: true onStart: focus = true pageComponent: Image { id: background source: Activity.resUrl + "hillside.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop focus: true // system locale by default property string locale: "system" property bool englishFallback: false property bool keyboardMode: false signal start signal stop signal voiceError Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property alias bar: bar property alias background: background property alias wordsModel: wordsModel property int currentLetterCase: ApplicationSettings.fontCapitalization property int currentMode: normalModeWordCount readonly property int easyModeWordCount: 5 readonly property int normalModeWordCount: 11 property GCAudio audioVoices: activity.audioVoices property alias parser: parser property alias animateX: animateX property alias repeatItem: repeatItem property alias score: score property alias bonus: bonus property alias locale: background.locale property alias questionItem: questionItem property alias englishFallbackDialog: englishFallbackDialog property string question } onStart: { activity.audioVoices.error.connect(voiceError) Activity.start(items); } onStop: Activity.stop() onWidthChanged: { animateX.restart(); } onHeightChanged: { animateX.restart(); } - ExclusiveGroup { - id: configOptions + ButtonGroup { + id: childGroup } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox property alias easyModeConfig: easyModeConfig property alias normalModeConfig: normalModeConfig property alias letterCaseBox: letterCaseBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: dialogActivityConfig.width height: dialogActivityConfig.height GCDialogCheckBox { id: normalModeConfig width: column.width - 50 text: qsTr("All words") checked: (items.currentMode === items.normalModeWordCount) ? true : false - exclusiveGroup: configOptions + ButtonGroup.group: childGroup } GCDialogCheckBox { id: easyModeConfig width: column.width - 50 text: qsTr("Only 5 words") checked: (items.currentMode === items.easyModeWordCount) ? true : false - exclusiveGroup: configOptions + ButtonGroup.group: childGroup } Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: letterCaseBox label: qsTr("Select case for letter to be searched") background: dialogActivityConfig model: [ {"text": qsTr("Mixed Case"), "value": Font.MixedCase}, {"text": qsTr("Upper Case"), "value": Font.AllUppercase}, {"text": qsTr("Lower Case"), "value": Font.AllLowercase} ] currentText: model[items.currentLetterCase].text currentIndex: items.currentLetterCase } } Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig label: qsTr("Select your locale") } } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["savedMode"]) { items.currentMode = dataToSave["savedMode"] === "5" ? items.easyModeWordCount : items.normalModeWordCount } if(dataToSave && dataToSave["savedLetterCase"]) { items.currentLetterCase = dataToSave["savedLetterCase"] } if(dataToSave && dataToSave["locale"]) { background.locale = dataToSave["locale"]; } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; // Remove .UTF-8 if(newLocale.indexOf('.') != -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } var oldMode = items.currentMode items.currentMode = dialogActivityConfig.loader.item.easyModeConfig.checked ? items.easyModeWordCount : items.normalModeWordCount var oldLetterCase = items.currentLetterCase items.currentLetterCase = dialogActivityConfig.loader.item.letterCaseBox.model[dialogActivityConfig.loader.item.letterCaseBox.currentIndex].value dataToSave = {"locale": newLocale, "savedMode": items.currentMode, "savedLetterCase": items.currentLetterCase} background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale || oldMode !== items.currentMode || oldLetterCase !== items.currentLetterCase) { background.stop(); background.start(); } } function setDefaultValues() { var localeUtf8 = background.locale; if(background.locale != "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { dialogActivityConfig.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelpLeftRight onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Score { id: score anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.left: parent.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined anchors.right: undefined } Bonus { id: bonus interval: 100 Component.onCompleted: { win.connect(Activity.nextSubLevel); } } Item { id: planeText width: plane.width height: plane.height x: -width anchors.top: parent.top anchors.topMargin: 5 * ApplicationInfo.ratio Image { id: plane anchors.centerIn: planeText anchors.top: parent.top source: Activity.resUrl + "plane.svg" sourceSize.height: itemHeight } GCText { id: questionItem anchors { right: planeText.right rightMargin: 2 * plane.width / 3 verticalCenter: planeText.verticalCenter bottomMargin: 10 * ApplicationInfo.ratio } fontSize: hugeSize font.weight: Font.DemiBold color: "#2a2a2a" text: items.question } PropertyAnimation { id: animateX target: planeText properties: "x" from: -planeText.width //to:background.width/2 - planeText.width/2 to: bar.level <= 2 ? background.width/3.7 : background.width duration: bar.level <= 2 ? 5500: 11000 //easing.type: Easing.OutQuad easing.type: bar.level <= 2 ? Easing.OutQuad: Easing.OutInCirc } } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg" sourceSize.width: 80 * ApplicationInfo.ratio anchors { top: parent.top right: parent.right margins: 10 } onClicked:{ Activity.playLetter(Activity.currentLetter); animateX.restart(); } } Keys.onPressed: { if(event.key === Qt.Key_Space) { wordsView.currentItem.select() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onEnterPressed: wordsView.currentItem.select(); Keys.onReturnPressed: wordsView.currentItem.select(); Keys.onRightPressed: wordsView.moveCurrentIndexRight(); Keys.onLeftPressed: wordsView.moveCurrentIndexLeft(); Keys.onDownPressed: wordsView.moveCurrentIndexDown(); Keys.onUpPressed: wordsView.moveCurrentIndexUp(); ListModel { id: wordsModel } property int itemWidth: Math.min(parent.width / 7.5, parent.height / 6.5) property int itemHeight: itemWidth * 1.11 GridView { id: wordsView anchors.bottom: bar.top anchors.left: parent.left anchors.right: parent.right anchors.top: planeText.bottom anchors.topMargin: 10 * ApplicationInfo.ratio anchors.leftMargin: 15 * ApplicationInfo.ratio anchors.rightMargin: 15 * ApplicationInfo.ratio anchors.bottomMargin: 10 * ApplicationInfo.ratio cellWidth: itemWidth + 25 * ApplicationInfo.ratio cellHeight: itemHeight + 15 * ApplicationInfo.ratio clip: false interactive: false //verticalLayoutDirection: GridView.BottomToTop layoutDirection: Qt.LeftToRight keyNavigationWraps: true model: wordsModel delegate: Card { width: background.itemWidth Connections { target: bonus onStart: { mouseActive = false; } onStop: { mouseActive = true; } } } highlight: Rectangle { width: wordsView.cellWidth - wordsView.spacing height: wordsView.cellHeight - wordsView.spacing color: "#AAFFFFFF" border.width: 3 border.color: "black" visible: background.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } BarButton { id: ok source: "qrc:/gcompris/src/core/resource/bar_ok.svg" width: wordsView.cellWidth*0.8 height: width sourceSize.width: wordsView.cellWidth anchors { right: parent.right rightMargin: 3 * ApplicationInfo.ratio bottom: wordsView.bottom } MouseArea { anchors.fill: parent onClicked: { Activity.checkAnswer(); } } } JsonParser { id: parser onError: console.error("Click_on_letter: Error parsing JSON: " + msg); } Loader { id: englishFallbackDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("https://l10n.kde.org/") + "

" + qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") onClose: background.englishFallback = false } anchors.fill: parent focus: true active: background.englishFallback onStatusChanged: if (status == Loader.Ready) item.start() } } } diff --git a/src/activities/menu/ConfigurationItem.qml b/src/activities/menu/ConfigurationItem.qml index 758c3a4be..988a571e6 100644 --- a/src/activities/menu/ConfigurationItem.qml +++ b/src/activities/menu/ConfigurationItem.qml @@ -1,731 +1,726 @@ /* 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 QtQuick.Controls 2.0 import GCompris 1.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 { + GCButton { + id: control 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 + background: Rectangle { + implicitWidth: 100 + implicitHeight: 25 + border.width: 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"} } } + contentItem: 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; } } 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 + from: ApplicationSettings.baseFontSizeMin + to: ApplicationSettings.baseFontSizeMax value: baseFontSize onValueChanged: ApplicationSettings.baseFontSize = value; - scrollEnabled: false + wheelEnabled: false } GCText { id: baseFontSizeText text: qsTr("Font size") fontSize: mediumSize wrapMode: Text.WordWrap } - Button { + GCButton { 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 + from: ApplicationSettings.fontLetterSpacingMin + to: ApplicationSettings.fontLetterSpacingMax value: fontLetterSpacing onValueChanged: ApplicationSettings.fontLetterSpacing = value; - scrollEnabled: false + wheelEnabled: false } GCText { id: fontLetterSpacingText text: qsTr("Font letter spacing") fontSize: mediumSize wrapMode: Text.WordWrap } - Button { + GCButton { 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 { + GCButton { 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 isFullscreen: ApplicationSettings.isFullscreen property bool isVirtualKeyboard: ApplicationSettings.isVirtualKeyboard property bool isAutomaticDownloadsEnabled: ApplicationSettings.isAutomaticDownloadsEnabled property bool sectionVisible: ApplicationSettings.sectionVisible property string wordset: ApplicationSettings.wordset 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 function loadFromConfig() { // Synchronize settings with data showLockedActivities = ApplicationSettings.showLockedActivities isAudioVoicesEnabled = ApplicationSettings.isAudioVoicesEnabled enableAudioVoicesBox.checked = isAudioVoicesEnabled isAudioEffectsEnabled = ApplicationSettings.isAudioEffectsEnabled enableAudioEffectsBox.checked = isAudioEffectsEnabled 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; // 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.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 ); } } } } 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 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.isFullscreen != isFullscreen) || (ApplicationSettings.isVirtualKeyboard != isVirtualKeyboard) || (ApplicationSettings.isAutomaticDownloadsEnabled != isAutomaticDownloadsEnabled) || (ApplicationSettings.baseFontSize != baseFontSize) || (ApplicationSettings.showLockedActivities != showLockedActivities) ); } } diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index 83d6ef919..529d25c58 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -1,733 +1,728 @@ /* GCompris - Menu.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (Qt Quick port) * * 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 "../../core" import GCompris 1.0 import QtGraphicalEffects 1.0 import "qrc:/gcompris/src/core/core.js" as Core -import QtQuick.Controls 1.5 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.0 /** * GCompris' top level menu screen. * * Displays a grid of available activities divided subdivided in activity * categories/sections. * * The visibility of the section row is toggled by the setting * ApplicationSettings.sectionVisible. * * The list of available activities depends on the following settings: * * * ApplicationSettings.showLockedActivities * * ApplicationSettings.filterLevelMin * * ApplicationSettings.filterLevelMax * * @inherit QtQuick.Item */ ActivityBase { id: activity focus: true activityInfo: ActivityInfoTree.rootMenu onBack: { pageView.pop(to); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } onHome: { if(pageView.depth === 1 && !ApplicationSettings.isKioskMode) { Core.quit(main); } else { pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } } onDisplayDialog: pageView.push(dialog) onDisplayDialogs: { var toPush = new Array(); for (var i = 0; i < dialogs.length; i++) { toPush.push({item: dialogs[i]}); } pageView.push(toPush); } Connections { // At the launch of the application, box2d check is performed after we // first initialize the menu. This connection is to refresh // automatically the menu at start. target: ApplicationInfo onIsBox2DInstalledChanged: { ActivityInfoTree.filterByTag(activity.currentTag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } // @cond INTERNAL_DOCS property string url: "qrc:/gcompris/src/activities/menu/resource/" property var sections: [ { icon: activity.url + "all.svg", tag: "favorite" }, { icon: activity.url + "computer.svg", tag: "computer" }, { icon: activity.url + "discovery.svg", tag: "discovery" }, { icon: activity.url + "experience.svg", tag: "experiment" }, { icon: activity.url + "fun.svg", tag: "fun" }, { icon: activity.url + "math.svg", tag: "math" }, { icon: activity.url + "puzzle.svg", tag: "puzzle" }, { icon: activity.url + "reading.svg", tag: "reading" }, { icon: activity.url + "strategy.svg", tag: "strategy" }, { icon: activity.url + "search-icon.svg", tag: "search" } ] property string currentTag: sections[0].tag /// @endcond pageComponent: Image { id: background source: activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) height: main.height fillMode: Image.PreserveAspectCrop Timer { // triggered once at startup to populate the keyboard id: keyboardFiller interval: 1000; running: true; onTriggered: { keyboard.populate(); } } function loadActivity() { // @TODO init of item would be better in setsource but it crashes on Qt5.6 // https://bugreports.qt.io/browse/QTBUG-49793 activityLoader.item.audioVoices = audioVoices activityLoader.item.audioEffects = audioEffects activityLoader.item.loading = loading //take the focus away from textField before starting an activity searchTextField.focus = false pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: { if (status == Loader.Loading) { loading.start(); } else if (status == Loader.Ready) { loading.stop(); loadActivity(); } else if (status == Loader.Error) loading.stop(); } } // Filters property bool horizontal: main.width >= main.height property int sectionIconWidth: { if(horizontal) return Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1)) else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) return Math.min(100 * ApplicationInfo.ratio, (background.height - (bar.height+keyboard.height)) / (sections.length + 1)) else return Math.min(100 * ApplicationInfo.ratio, (background.height - bar.height) / (sections.length + 1)) } property int sectionIconHeight: sectionIconWidth property int sectionCellWidth: sectionIconWidth * 1.1 property int sectionCellHeight: sectionIconHeight * 1.1 property var currentActiveGrid: activitiesGrid property bool keyboardMode: false Keys.onPressed: { // Ctrl-modifiers should never be handled by the search-field if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_S) { // Ctrl+S toggle show / hide section ApplicationSettings.sectionVisible = !ApplicationSettings.sectionVisible } } else if(currentTag === "search") { // forward to the virtual keyboard the pressed keys if(event.key == Qt.Key_Backspace) keyboard.keypress(keyboard.backspace); else keyboard.keypress(event.text); } else if(event.key === Qt.Key_Space && currentActiveGrid.currentItem) { currentActiveGrid.currentItem.selectCurrentItem() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onTabPressed: currentActiveGrid = ((currentActiveGrid == activitiesGrid) ? section : activitiesGrid); Keys.onEnterPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onReturnPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onRightPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexRight(); Keys.onLeftPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexLeft(); Keys.onDownPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYEnd) currentActiveGrid.moveCurrentIndexDown(); Keys.onUpPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYBeginning) currentActiveGrid.moveCurrentIndexUp(); GridView { id: section 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 } x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellHeight visible: ApplicationSettings.sectionVisible cellWidth: sectionCellWidth cellHeight: sectionCellHeight interactive: false keyNavigationWraps: true property int initialX: 4 property int initialY: 4 Component { id: sectionDelegate Item { id: backgroundSection width: sectionCellWidth height: sectionCellHeight Image { source: modelData.icon sourceSize.height: sectionIconHeight anchors.margins: 5 anchors.horizontalCenter: parent.horizontalCenter } ParticleSystemStarLoader { id: particles anchors.fill: backgroundSection clip: false } MouseArea { anchors.fill: backgroundSection onClicked: { selectCurrentItem() } } function selectCurrentItem() { section.currentIndex = index activity.currentTag = modelData.tag particles.burst(10) if(modelData.tag === "search") { ActivityInfoTree.filterBySearch(searchTextField.text); } else { ActivityInfoTree.filterByTag(modelData.tag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } } } delegate: sectionDelegate highlight: Item { width: sectionCellWidth height: sectionCellHeight Rectangle { anchors.fill: parent color: "#5AFFFFFF" } Image { source: "qrc:/gcompris/src/core/resource/button.svg" anchors.fill: parent } Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } // Activities property int iconWidth: 120 * ApplicationInfo.ratio property int activityCellWidth: horizontal ? background.width / Math.floor(background.width / iconWidth) : (background.width - section.width) / Math.floor((background.width - section.width) / iconWidth) property int activityCellHeight: iconWidth * 1.7 Loader { id: warningOverlay anchors { top: horizontal ? section.bottom : parent.top bottom: parent.bottom left: horizontal ? parent.left : section.right right: parent.right margins: 4 } active: (ActivityInfoTree.menuTree.length === 0) && (currentTag === "favorite") sourceComponent: Item { anchors.fill: parent GCText { id: instructionTxt fontSize: smallSize y: height * 0.2 x: (parent.width - width) / 2 z: 2 width: parent.width * 0.6 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.weight: Font.DemiBold color: 'white' text: qsTr("Put your favorite activities here by selecting the " + "sun at the top right of that activity.") } Rectangle { anchors.fill: instructionTxt anchors.margins: -6 z: 1 opacity: 0.5 radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } } } } GridView { id: activitiesGrid anchors { top: { if(activity.currentTag === "search") return searchBar.bottom else return horizontal ? section.bottom : parent.top } bottom: bar.top left: horizontal ? parent.left : section.right margins: 4 } width: background.width cellWidth: activityCellWidth cellHeight: activityCellHeight clip: true model: ActivityInfoTree.menuTree keyNavigationWraps: true property int spacing: 10 delegate: Item { id: delegateItem width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing Rectangle { id: activityBackground width: parent.width height: parent.height anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { source: "qrc:/gcompris/src/activities/" + icon; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter width: iconWidth - activitiesGrid.spacing height: width sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.margins: 5 Image { source: "qrc:/gcompris/src/core/resource/difficulty" + ActivityInfoTree.menuTree[index].difficulty + ".svg"; anchors.top: parent.top sourceSize.width: iconWidth * 0.15 x: 5 } Image { anchors { horizontalCenter: parent.horizontalCenter top: parent.top rightMargin: 4 } source: demo || !ApplicationSettings.isDemoMode ? "" : activity.url + "lock.svg" sourceSize.width: 30 * ApplicationInfo.ratio } Image { anchors { left: parent.left bottom: parent.bottom } source: ActivityInfoTree.menuTree[index].createdInVersion == ApplicationInfo.GCVersionCode ? activity.url + "new.svg" : "" sourceSize.width: 30 * ApplicationInfo.ratio } GCText { id: title anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].title } // If we have enough room at the bottom display the description GCText { id: description visible: delegateItem.height - (title.y + title.height) > description.height ? 1 : 0 anchors.top: title.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 3 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].description } } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground onClicked: selectCurrentItem() } Image { source: activity.url + (favorite ? "all.svg" : "all_disabled.svg"); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: favorite = !favorite } } function selectCurrentItem() { if(pageView.busy) return particles.burst(50) ActivityInfoTree.currentActivity = ActivityInfoTree.menuTree[index] activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.menuTree[index].name, { 'menu': activity, 'activityInfo': ActivityInfoTree.currentActivity }) if (activityLoader.status == Loader.Ready) loadActivity() } } highlight: Rectangle { width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing color: "#AAFFFFFF" border.width: 3 border.color: "black" visible: background.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle { id: activitiesMask 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"} } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: activitiesGrid maskSource: activitiesMask anchors.fill: activitiesGrid } } // The scroll buttons GCButtonScroll { visible: !ApplicationInfo.useOpenGL anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: activitiesGrid.bottom anchors.bottomMargin: 30 * ApplicationInfo.ratio onUp: activitiesGrid.flick(0, 1127) onDown: activitiesGrid.flick(0, -1127) upVisible: activitiesGrid.visibleArea.yPosition <= 0 ? false : true downVisible: activitiesGrid.visibleArea.yPosition >= 1 ? false : true } Rectangle { id: searchBar width: horizontal ? parent.width/2 : parent.width - (section.width+10) height: searchTextField.height visible: activity.currentTag === "search" anchors { top: horizontal ? section.bottom : parent.top left: horizontal ? undefined : section.right } anchors.topMargin: horizontal ? 0 : 4 anchors.bottomMargin: horizontal ? 0 : 4 anchors.horizontalCenter: horizontal ? parent.horizontalCenter : undefined opacity: 0.5 radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.3; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } Connections { // On mobile with GCompris' virtual keyboard activated: // Force invisibility of Androids virtual keyboard: target: (ApplicationInfo.isMobile && activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) ? Qt.inputMethod : null onVisibleChanged: { if (ApplicationSettings.isVirtualKeyboard && visible) Qt.inputMethod.hide(); } onAnimatingChanged: { // note: seems to be never fired! if (ApplicationSettings.isVirtualKeyboard && Qt.inputMethod.visible) Qt.inputMethod.hide(); } } Connections { target: activity onCurrentTagChanged: { if (activity.currentTag === 'search') { searchTextField.focus = true; } else activity.focus = true; } } TextField { id: searchTextField width: parent.width - textColor: "black" + color: "black" font.pointSize: 16 font.bold: true horizontalAlignment: TextInput.AlignHCenter verticalAlignment: TextInput.AlignVCenter font.family: GCSingletonFontLoader.fontLoader.name inputMethodHints: Qt.ImhNoPredictiveText // Note: we give focus to the textfield also in case // isMobile && !ApplicationSettings.isVirtualKeyboard // in conjunction with auto-hiding the inputMethod to always get // an input-cursor: activeFocusOnPress: true //ApplicationInfo.isMobile ? !ApplicationSettings.isVirtualKeyboard : true Keys.onReturnPressed: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } onEditingFinished: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } - style: TextFieldStyle { - placeholderTextColor: "black" - } - placeholderText: qsTr("Search specific activities") onTextChanged: ActivityInfoTree.filterBySearch(searchTextField.text); } } VirtualKeyboard { id: keyboard readonly property var letter: ActivityInfoTree.characters width: parent.width visible: activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter onKeypress: { if(text == keyboard.backspace) { searchTextField.text = searchTextField.text.slice(0, -1); } else if(text == keyboard.space) { searchTextField.text = searchTextField.text.concat(" "); } else { searchTextField.text = searchTextField.text.concat(text); } } 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 } } Bar { id: bar // No exit button on mobile, UI Guidelines prohibits it content: BarEnumContent { value: help | config | about | (ApplicationInfo.isMobile ? 0 : exit) } anchors.bottom: keyboard.visible ? keyboard.top : parent.bottom onAboutClicked: { searchTextField.focus = false displayDialog(dialogAbout) } onHelpClicked: { searchTextField.focus = false displayDialog(dialogHelp) } onConfigClicked: { searchTextField.focus = false dialogActivityConfig.active = true dialogActivityConfig.loader.item.loadFromConfig() displayDialog(dialogActivityConfig) } } DialogAbout { id: dialogAbout onClose: home() } DialogHelp { id: dialogHelp onClose: home() activityInfo: ActivityInfoTree.rootMenu } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { ConfigurationItem { id: configItem width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio } } onSaveData: { dialogActivityConfig.configItem.save(); } onClose: { if(activity.currentTag != "search") { ActivityInfoTree.filterByTag(activity.currentTag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } else ActivityInfoTree.filterBySearch(searchTextField.text); home() } } } } diff --git a/src/activities/note_names/AdvancedTimer.qml b/src/activities/note_names/AdvancedTimer.qml index 7a0879cd8..5824de4eb 100644 --- a/src/activities/note_names/AdvancedTimer.qml +++ b/src/activities/note_names/AdvancedTimer.qml @@ -1,73 +1,72 @@ /* GCompris - AdvancedTimer.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 -import QtQuick.Controls 1.5 import GCompris 1.0 import "note_names.js" as Activity Timer { id: timer property double startTime property double pauseTime property int timerNormalInterval: 2700 property int remainingInterval interval: timerNormalInterval signal pause signal resume signal restart onPause: { if(timer.running) { pauseTime = new Date().getTime() timer.stop() } } onResume: { if(!timer.running) { if(!triggeredOnStart) { remainingInterval = Math.abs(timer.interval - Math.abs(pauseTime - startTime)) timer.interval = remainingInterval } timer.start() } } onRestart: { timer.stop() timer.interval = 1 timer.start() } onTriggered:{ if(interval != timerNormalInterval) { interval = timerNormalInterval } } onRunningChanged: { if(running) startTime = new Date().getTime() } } diff --git a/src/activities/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index 9926f4a90..d2f36bf1a 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,425 +1,415 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 -import QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyNoteBindings = {} keyNoteBindings[Qt.Key_1] = 'C' keyNoteBindings[Qt.Key_2] = 'D' keyNoteBindings[Qt.Key_3] = 'E' keyNoteBindings[Qt.Key_4] = 'F' keyNoteBindings[Qt.Key_5] = 'G' keyNoteBindings[Qt.Key_6] = 'A' keyNoteBindings[Qt.Key_7] = 'B' if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { if(keyNoteBindings[event.key]) { // If the key pressed matches the note, pass the correct answer as parameter. isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false function getTranslatedNoteName(noteName) { for(var i = 0; i < doubleOctave.keyNames.length; i++) { if(doubleOctave.keyNames[i][0] == noteName) return doubleOctave.keyNames[i][1] } return "" } onVisibleChanged: { if(Activity.targetNotes[0] === undefined) text = "" else if(items.isTutorialMode) text = qsTr("New note: %1").arg(getTranslatedNoteName(Activity.targetNotes[0])) else text = getTranslatedNoteName(Activity.newNotesSequence[Activity.currentNoteIndex]) } property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } - ProgressBar { + GCProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage - maximumValue: 100 + to: 100 visible: !items.isTutorialMode + message: qsTr("%1%").arg(value) anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } - - GCText { - anchors.centerIn: parent - fontSize: mediumSize - font.bold: true - color: "black" - //: The following translation represents percentage. - text: qsTr("%1%").arg(parent.value) - z: 2 - } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" softColorOpacity: 0 isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] property var keyNames: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } Component.onCompleted: doubleOctave.keyNames = whiteKeyNoteLabelsArray } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow iconsWidth: 0 visible: false } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/photo_hunter/PhotoHunter.qml b/src/activities/photo_hunter/PhotoHunter.qml index 3524c622b..955e9f050 100644 --- a/src/activities/photo_hunter/PhotoHunter.qml +++ b/src/activities/photo_hunter/PhotoHunter.qml @@ -1,244 +1,219 @@ /* GCompris - PhotoHunter.qml * * Copyright (C) 2016 Stefan Toncu * * Authors: * (GTK+ version) * Stefan Toncu (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 -import QtQuick.Controls 1.5 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.0 import "../../core" import "photo_hunter.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "white" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property var model property bool notShowed: true property alias img1: img1 property alias img2: img2 property int total property int totalFound: img1.good + img2.good property alias problem: problem property alias frame: frame } onStart: { Activity.start(items) } onStop: { Activity.stop() } property bool vert: background.width <= background.height property double barHeight: ApplicationSettings.isBarHidden ? bar.height / 2 : bar.height property bool startedHelp: false function checkAnswer() { if (items.totalFound === items.model.length) { bonus.good("flower") // after completing a level, mark the problem as shown if (items.notShowed) { items.notShowed = false } //remove the problem from the board after first level if (problem.z > 0) Activity.hideProblem() } } Rectangle { id: problem width: parent.width height: problemText.height anchors.top: parent.top anchors.topMargin: 10 border.width: 2 border.color: "black" color: "red" z: 5 property alias problemText: problemText GCText { id: problemText anchors.centerIn: parent width: parent.width * 3 / 4 fontSize: mediumSize wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter text: background.startedHelp ? qsTr("Drag the slider to show the differences!") : qsTr("Click on the differences between the two images!") color: "white" onHeightChanged: { if (items.problem.z > 0) frame.problemTextHeight = problemText.height } } MouseArea { anchors.fill: parent onClicked: Activity.hideProblem() } } Rectangle { id: frame color: "transparent" width: background.vert ? img1.width : parent.width - 20 height: parent.height - background.barHeight - 30 anchors.top: problem.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 10 property real problemTextHeight: problemText.height //left/top image Observe { id: img1 show: true anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: background.startedHelp ? 0 : background.vert ? 0 : - img1.width / 2 - 5 verticalCenter: parent.verticalCenter verticalCenterOffset: background.startedHelp ? background.vert ? - frame.problemTextHeight * 0.8 : - frame.problemTextHeight * 1.01 : background.vert ? - frame.problemTextHeight * 0.5 - img1.height * 0.5 - 5 : - frame.problemTextHeight * 0.5 } } //right/bottom image Observe { id: img2 opacity: background.startedHelp ? 1 - slider.value : 1 show: false anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: background.startedHelp ? 0 : background.vert ? 0 : img1.width / 2 + 5 verticalCenter: parent.verticalCenter verticalCenterOffset: background.startedHelp ? background.vert ? - frame.problemTextHeight * 0.8 : - frame.problemTextHeight * 1.01 : background.vert ? - frame.problemTextHeight * 0.5 + img1.height * 0.5 + 5 : - frame.problemTextHeight * 0.5 } } - Slider { + GCSlider { id: slider - value: 0 height: 50 width: img1.width * 0.9 z: background.startedHelp ? 5 : -5 opacity: background.startedHelp ? 1 : 0 enabled: background.startedHelp - - style: SliderStyle { - handle: Rectangle { - height: background.vert ? 80 : 70 - width: height - radius: width / 2 - color: "lightblue" - } - - groove: Rectangle { - implicitHeight: slider.height - implicitWidth: background.vert ? slider.width * 0.85 : slider.width - radius: height / 2 - border.color: "#6699ff" - color: "#99bbff" - - Rectangle { - height: parent.height - width: styleData.handlePosition - implicitHeight: 6 - implicitWidth: 100 - radius: height/2 - color: "#4d88ff" - } - } - } + value: 0 + snapMode: Slider.NoSnap + stepSize: 0 anchors { top: img1.bottom topMargin: 20 horizontalCenter: img1.horizontalCenter } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | hint } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onHintClicked: { background.startedHelp = !background.startedHelp slider.value = 0 } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Score { anchors { bottom: parent.bottom bottomMargin: 10 * ApplicationInfo.ratio right: parent.right rightMargin: 10 * ApplicationInfo.ratio top: undefined left: undefined } numberOfSubLevels: items.total currentSubLevel: items.totalFound } } } diff --git a/src/activities/piano_composition/MelodyList.qml b/src/activities/piano_composition/MelodyList.qml index cc1ec6f92..4bab321c5 100644 --- a/src/activities/piano_composition/MelodyList.qml +++ b/src/activities/piano_composition/MelodyList.qml @@ -1,174 +1,171 @@ /* GCompris - MelodyList.qml * * Copyright (C) 2017 Divyam Madaan * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Divyam Madaan (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 -import QtQuick.Controls 1.5 import "../../core" import "piano_composition.js" as Activity Rectangle { id: dialogBackground color: "#696da3" border.color: "black" border.width: 1 z: 10000 anchors.fill: parent visible: false focus: true Keys.onEscapePressed: close() signal close property alias melodiesModel: melodiesModel property bool horizontalLayout: dialogBackground.width >= dialogBackground.height property int selectedMelodyIndex: -1 ListModel { id: melodiesModel } 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("Melodies") 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.topMargin: 10 anchors.leftMargin: 20 contentWidth: parent.width contentHeight: melodiesGrid.height flickableDirection: Flickable.VerticalFlick clip: true Flow { id: melodiesGrid width: parent.width spacing: 40 anchors.horizontalCenter: parent.horizontalCenter Repeater { id: melodiesRepeater model: melodiesModel Item { id: melodiesItem width: dialogBackground.horizontalLayout ? dialogBackground.width / 5 : dialogBackground.width / 4 height: dialogBackground.height / 5 - Button { + GCButton { text: title onClicked: { dialogBackground.selectedMelodyIndex = index items.multipleStaff.stopAudios() items.multipleStaff.nbStaves = 2 items.multipleStaff.bpmValue = defaultBPM ? defaultBPM : 60 items.multipleStaff.loadFromData(melody) lyricsArea.setLyrics(title, _origin, lyrics) } width: parent.width height: parent.height * 0.8 - style: GCButtonStyle { - theme: "dark" - } + theme: "dark" Image { 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: dialogBackground.selectedMelodyIndex === index } } } } } } // 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: { dialogBackground.selectedMelodyIndex = -1 parent.close() } } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml index cefa05337..8b34c7a59 100644 --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -1,563 +1,558 @@ /* GCompris - Piano_composition.qml * * Copyright (C) 2016 Johnny Jazeix * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * Timothée Giet (refactoring) * * 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 import "piano_composition.js" as Activity import "melodies.js" as Dataset ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true property bool horizontalLayout: background.width >= background.height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(event.key >= Qt.Key_1 && event.key <= Qt.Key_7 && keyboardBindings[event.key] < piano.whiteKeyNoteLabels.length) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { piano.currentOctaveNb-- } if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { piano.currentOctaveNb++ } if(event.key === Qt.Key_Delete) { optionsRow.clearButtonClicked() } if(event.key === Qt.Key_Backspace) { optionsRow.undoButtonClicked() } if(event.key === Qt.Key_Space) { multipleStaff.play() } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { if(piano.keyRepeater.itemAt(i) === undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias bonus: bonus property alias multipleStaff: multipleStaff property string staffLength: "short" property alias melodyList: melodyList property alias file: file property alias piano: piano property alias optionsRow: optionsRow property alias lyricsArea: lyricsArea } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string currentType: "Quarter" property string restType: "Whole" property string clefType: bar.level == 2 ? "Bass" : "Treble" property bool isLyricsMode: (optionsRow.lyricsOrPianoModeIndex === 1) && optionsRow.lyricsOrPianoModeOptionVisible property int layoutMargins: 5 * ApplicationInfo.ratio File { id: file onError: console.error("File error: " + msg) } Item { id: clickedOptionMessage signal show(string message) onShow: { messageText.text = message messageAnimation.stop() messageAnimation.start() } height: width * 0.4 visible: false anchors.top: optionsRow.bottom anchors.horizontalCenter: optionsRow.horizontalCenter z: 5 Rectangle { id: messageRectangle width: messageText.contentWidth + 5 height: messageText.height + 5 anchors.centerIn: messageText color: "black" opacity: 0.5 border.width: 3 border.color: "black" radius: 15 } GCText { id: messageText anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit color: "white" } SequentialAnimation { id: messageAnimation onStarted: clickedOptionMessage.visible = true PauseAnimation { duration: 1000 } NumberAnimation { targets: [messageRectangle, messageText] property: "opacity" to: 0 duration: 200 } onStopped: { clickedOptionMessage.visible = false messageRectangle.opacity = 0.5 messageText.opacity = 1 } } } MelodyList { id: melodyList onClose: { visible = false piano.enabled = true focus = false activity.focus = true } } Rectangle { id: instructionBox radius: 10 width: background.width * 0.75 height: background.height * 0.15 anchors.right: parent.right opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { id: instructionText color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: Activity.instructions[bar.level - 1].text } } MultipleStaff { id: multipleStaff nbStaves: 2 clef: clefType coloredNotes: ['C','D', 'E', 'F', 'G', 'A', 'B'] noteHoverEnabled: true anchors.margins: layoutMargins onNoteClicked: { if(selectedIndex === noteIndex) selectedIndex = -1 else { selectedIndex = noteIndex background.clefType = musicElementModel.get(selectedIndex).soundPitch_ playNoteAudio(musicElementModel.get(selectedIndex).noteName_, musicElementModel.get(selectedIndex).noteType_, background.clefType, musicElementRepeater.itemAt(selectedIndex).duration) } } } GCButtonScroll { id: multipleStaffFlickButton anchors.left: multipleStaff.right anchors.verticalCenter: multipleStaff.verticalCenter width: bar.height * 0.3 height: width * 2 onUp: multipleStaff.flickableStaves.flick(0, multipleStaff.height * 1.3) onDown: multipleStaff.flickableStaves.flick(0, -multipleStaff.height * 1.3) upVisible: multipleStaff.flickableStaves.visibleArea.yPosition > 0 downVisible: (multipleStaff.flickableStaves.visibleArea.yPosition + multipleStaff.flickableStaves.visibleArea.heightRatio) < 1 } Item { id: pianoLayout width: multipleStaff.width + multipleStaffFlickButton.width height: multipleStaff.height anchors.margins: layoutMargins PianoOctaveKeyboard { id: piano height: parent.height width: parent.width * 0.8 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter blackLabelsVisible: [3, 4, 5, 6, 7, 8].indexOf(items.bar.level) == -1 ? false : true useSharpNotation: bar.level != 4 blackKeysEnabled: bar.level > 2 visible: !background.isLyricsMode currentOctaveNb: (background.clefType === "Bass") ? 0 : 1 onNoteClicked: { parent.addMusicElementAndPushToStack(note, currentType) } } function addMusicElementAndPushToStack(noteName, noteType, elementType) { if(noteType === "Rest") elementType = "rest" else if(elementType === undefined) elementType = "note" var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement(elementType, noteName, noteType, false, true, background.clefType) } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: parent.width * 0.1 width: sourceSize.width height: parent.height fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb > 0) && piano.visible anchors.right: piano.left anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb-- } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: parent.width * 0.1 width: sourceSize.width height: parent.height fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb < piano.maxNbOctaves - 1) && piano.visible anchors.left: piano.right anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb++ } } LyricsArea { id: lyricsArea width: parent.width height: parent.height anchors.fill: pianoLayout } } GCCreationHandler { id: creationHandler onFileLoaded: { // We need to draw the notes twice since we first need to count the number of staffs needed for the melody (we get that from // the 1st redraw call), then we redraw the 2nd time to actually display the notes perfectly. This is done because for some reason, the // staves model is updated slower than the addition of notes, so the notes aggregates in their default position instead of // their required position, due to unavailability of the updated staff at that instant. So calculating the number of required staffs first seems the only solution for now. multipleStaff.redraw(data) multipleStaff.redraw(data) lyricsArea.resetLyricsArea() } onClose: { optionsRow.lyricsOrPianoModeIndex = 0 } } OptionsRow { id: optionsRow anchors.margins: layoutMargins anchors.left: background.left iconsWidth: 0 noteOptionsVisible: bar.level > 4 playButtonVisible: true keyOption.clefButtonVisible: bar.level > 2 clearButtonVisible: true undoButtonVisible: true openButtonVisible: bar.level > 6 saveButtonVisible: bar.level > 6 changeAccidentalStyleButtonVisible: bar.level >= 4 lyricsOrPianoModeOptionVisible: bar.level > 6 restOptionsVisible: bar.level > 5 bpmVisible: true onUndoButtonClicked: { Activity.undoChange() } onClearButtonClicked: { if((multipleStaff.musicElementModel.count > 1) && multipleStaff.selectedIndex === -1) { Core.showMessageDialog(main, qsTr("You have not selected any note. Do you want to erase all the notes?"), qsTr("Yes"), function() { Activity.undoStack = [] lyricsArea.resetLyricsArea() multipleStaff.eraseAllNotes() multipleStaff.nbStaves = 2 }, qsTr("No"), null, null ) } else if((multipleStaff.musicElementModel.count > 1) && !multipleStaff.musicElementModel.get(multipleStaff.selectedIndex).isDefaultClef_) { var noteIndex = multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.eraseNote(noteIndex) } } onOpenButtonClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onSaveButtonClicked: { var notesToSave = multipleStaff.createNotesBackup() // add bpm at start notesToSave.unshift({"bpm": multipleStaff.bpmValue}); creationHandler.saveWindow(notesToSave) } keyOption.onClefAdded: { var insertingIndex = multipleStaff.selectedIndex === -1 ? multipleStaff.musicElementModel.count : multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement("clef", "", "", false, false, background.clefType) if(background.clefType === "Bass") piano.currentOctaveNb = 0 else piano.currentOctaveNb = 1 } onBpmDecreased: { if(multipleStaff.bpmValue - 1 >= 1) multipleStaff.bpmValue-- } onBpmIncreased: { multipleStaff.bpmValue++ } onEmitOptionMessage: clickedOptionMessage.show(message) } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 10 width: dialogActivityConfig.width height: dialogActivityConfig.height GCText { text: qsTr("Select the type of melody to load.") fontSizeMode: mediumSize } - Button { + GCButton { text: qsTr("Pre-defined melodies") onClicked: { melodyList.melodiesModel.clear() var dataset = Dataset.get() for(var i = 0; i < dataset.length; i++) { melodyList.melodiesModel.append(dataset[i]) } melodyList.visible = true piano.enabled = false melodyList.forceActiveFocus() dialogActivityConfig.close() } width: 150 * ApplicationInfo.ratio height: 60 * ApplicationInfo.ratio - style: GCButtonStyle { - theme: "dark" - } + theme: "dark" } - Button { + GCButton { text: qsTr("Your saved melodies") onClicked: { creationHandler.loadWindow() dialogActivityConfig.close() } width: 150 * ApplicationInfo.ratio height: 60 * ApplicationInfo.ratio - style: GCButtonStyle { - theme: "dark" - } + theme: "dark" } } } onClose: home() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } states: [ State { name: "hScreen" when: horizontalLayout PropertyChanges { target: clickedOptionMessage width: background.width / 12 } AnchorChanges { target: optionsRow anchors.top: instructionBox.bottom } PropertyChanges { target: optionsRow columns: 11 iconsWidth: background.width / 15 } AnchorChanges { target: multipleStaff anchors.left: background.horizontalCenter anchors.top: optionsRow.bottom } PropertyChanges { target: multipleStaff width: background.width * 0.5 - multipleStaffFlickButton.width - layoutMargins * 3 height: background.height - instructionBox.height - optionsRow.height - bar.height - layoutMargins * 4 } AnchorChanges { target: pianoLayout anchors.left: background.left anchors.top: optionsRow.bottom } }, State { name: "vScreen" when: !horizontalLayout PropertyChanges { target: clickedOptionMessage width: background.width / 6 } AnchorChanges{ target: optionsRow anchors.top: background.top } PropertyChanges { target: optionsRow columns: 1 iconsWidth: (background.height - bar.height) / 12 } AnchorChanges { target: multipleStaff anchors.left: optionsRow.right anchors.top: instructionBox.bottom } PropertyChanges { target: multipleStaff width: background.width - multipleStaffFlickButton.width - optionsRow.width - layoutMargins * 3 height: (background.height - instructionBox.height - bar.height - layoutMargins * 4) * 0.5 } AnchorChanges { target: pianoLayout anchors.left: optionsRow.right anchors.top: multipleStaff.bottom } } ] } } diff --git a/src/activities/play_piano/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml index ffb41c7e8..b9455d0e0 100644 --- a/src/activities/play_piano/PlayPiano.qml +++ b/src/activities/play_piano/PlayPiano.qml @@ -1,331 +1,331 @@ /* GCompris - PlayPiano.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (Qt Quick port) * * 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 2.0 import GCompris 1.0 import "../../core" import "../piano_composition" import "play_piano.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true property bool horizontalLayout: width >= height * 1.2 pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_8] = 7 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(piano.whiteKeysEnabled && !iAmReady.visible) { if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } else if(event.key === Qt.Key_Space) { multipleStaff.play() } else if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { Activity.undoPreviousAnswer() } } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { if(piano.keyRepeater.itemAt(i) === undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias multipleStaff: multipleStaff property alias piano: piano property alias bar: bar property alias bonus: bonus property alias score: score property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias parser: parser property string mode: "coloredNotes" } onStart: { dialogActivityConfig.getInitialConfiguration() Activity.start(items) } onStop: { Activity.stop() } property string clefType: (items.bar.level <= 5) ? "Treble" : "Bass" Timer { id: introductoryAudioTimer interval: 4000 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 5 width: horizontalLayout ? parent.width / 10 : (parent.width - instruction.x - instruction.width - 1.5 * anchors.rightMargin) } Rectangle { id: instruction radius: 10 width: background.width * 0.6 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: qsTr("Click on the piano keys that match the given notes.") } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.8 height: horizontalLayout ? parent.height * 0.85 : parent.height * 0.58 nbStaves: 1 clef: clefType coloredNotes: (items.mode === "coloredNotes") ? ['C', 'D', 'E', 'F', 'G', 'A', 'B'] : [] isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: instruction.bottom anchors.topMargin: horizontalLayout ? parent.height * 0.02 : parent.height * 0.15 onNoteClicked: { playNoteAudio(musicElementModel.get(noteIndex).noteName_, musicElementModel.get(noteIndex).noteType_, musicElementModel.get(noteIndex).soundPitch_) } centerNotesPosition: true } PianoOctaveKeyboard { id: piano width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.7 height: parent.height * 0.3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 20 blackLabelsVisible: ([4, 5, 9, 10].indexOf(items.bar.level) != -1) blackKeysEnabled: blackLabelsVisible && !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeysEnabled: !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeyNoteLabelsTreble: [ whiteKeyNoteLabelsArray.slice(18, 26) ] whiteKeyNoteLabelsBass: [ whiteKeyNoteLabelsArray.slice(11, 19)] onNoteClicked: { multipleStaff.playNoteAudio(note, "Quarter", clefType, 500) Activity.checkAnswer(note) } useSharpNotation: true } Rectangle { id: optionDeck width: optionsRow.changeAccidentalStyleButtonVisible ? optionsRow.iconsWidth * 3.3 : optionsRow.iconsWidth * 2.2 height: optionsRow.iconsWidth * 1.1 color: "white" opacity: 0.5 radius: 10 y: horizontalLayout ? piano.y : multipleStaff.y / 2 + instruction.height - height / 2 x: horizontalLayout ? multipleStaff.x + multipleStaff.width + 25 : background.width / 2 - width / 2 } OptionsRow { id: optionsRow anchors.centerIn: optionDeck playButtonVisible: true undoButtonVisible: true onUndoButtonClicked: Activity.undoPreviousAnswer() } - ExclusiveGroup { - id: configOptions + ButtonGroup { + id: childGroup } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias coloredNotesModeBox: coloredNotesModeBox property alias colorlessNotesModeBox: colorlessNotesModeBox GCDialogCheckBox { id: coloredNotesModeBox width: column.width - 50 text: qsTr("Display colored notes.") + ButtonGroup.group: childGroup checked: items.mode === "coloredNotes" - exclusiveGroup: configOptions onCheckedChanged: { if(coloredNotesModeBox.checked) { items.mode = "coloredNotes" } } } GCDialogCheckBox { id: colorlessNotesModeBox width: coloredNotesModeBox.width + ButtonGroup.group: childGroup text: qsTr("Display colorless notes.") checked: items.mode === "colorlessNotes" - exclusiveGroup: configOptions onCheckedChanged: { if(colorlessNotesModeBox.checked) { items.mode = "colorlessNotes" } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] } onSaveData: dataToSave["mode"] = items.mode onClose: { home() } onVisibleChanged: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config | reload } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/solar_system/QuizScreen.qml b/src/activities/solar_system/QuizScreen.qml index 084460ae0..8b6ea9daa 100644 --- a/src/activities/solar_system/QuizScreen.qml +++ b/src/activities/solar_system/QuizScreen.qml @@ -1,307 +1,307 @@ /* GCompris - QuizScreen.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.5 +import QtQuick.Controls 2.0 import "../../core" import "solar_system.js" as Activity Item { id: mainQuizScreen width: parent.width height: parent.height focus: true property alias score: score property alias optionListModel: optionListModel property alias restartAssessmentMessage: restartAssessmentMessage property alias blockAnswerButtons: optionListView.blockAnswerButtons property alias closenessMeter: closenessMeter property string planetRealImage property string question property string closenessMeterValue property int numberOfCorrectAnswers: 0 Rectangle { id: questionArea anchors.right: score.left anchors.top: parent.top anchors.left: parent.left anchors.margins: 10 * ApplicationInfo.ratio height: questionText.height + 10 * ApplicationInfo.ratio color: 'white' radius: 10 border.width: 3 opacity: 0.8 border.color: "black" GCText { id: questionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.centerIn: parent.Center color: "black" width: parent.width wrapMode: Text.Wrap text: mainQuizScreen.question } } // Model of options for a question ListModel { id: optionListModel } // This grid has image of the planet in its first column/row (row in case of vertical screen) and the options on the 2nd column/row Grid { id: imageAndOptionGrid columns: (background.horizontalLayout && !items.assessmentMode && items.bar.level != 2) ? 2 : 1 spacing: 10 * ApplicationInfo.ratio anchors.top: (questionArea.y + questionArea.height) > (score.y + score.height) ? questionArea.bottom : score.bottom anchors.left: parent.left anchors.right: parent.right // An item to hold image of the planet Item { width: background.horizontalLayout ? background.width * 0.40 : background.width - imageAndOptionGrid.anchors.margins * 2 height: background.horizontalLayout ? background.height - bar.height - questionArea.height - 10 * ApplicationInfo.ratio : (background.height - bar.height - questionArea.height - 10 * ApplicationInfo.ratio) * 0.37 visible: !items.assessmentMode && (items.bar.level != 2) Image { id: planetImageMain sourceSize.width: Math.min(parent.width, parent.height) * 0.9 anchors.centerIn: parent source: mainQuizScreen.planetRealImage fillMode: Image.PreserveAspectCrop } } // An item to hold the list view of options Item { width: ( items.assessmentMode || items.bar.level == 2 ) ? mainQuizScreen.width : background.horizontalLayout ? background.width * 0.55 : background.width - imageAndOptionGrid.anchors.margins * 2 height: background.horizontalLayout ? itemHeightHorizontal : itemHeightVertical readonly property real itemHeightHorizontal: background.height - bar.height - closenessMeter.height - questionArea.height - 10 * ApplicationInfo.ratio readonly property real itemHeightVertical: (items.bar.level != 2 && !items.assessmentMode) ? itemHeightHorizontal * 0.39 : itemHeightHorizontal * 0.8 ListView { id: optionListView anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter width: background.horizontalLayout ? background.width * 0.40 : background.width - imageAndOptionGrid.anchors.margins * 2 height: background.horizontalLayout ? background.height - bar.height - closenessMeter.height * 1.5 - questionArea.height - 50 * ApplicationInfo.ratio : parent.itemHeightVertical spacing: background.horizontalLayout ? 10 * ApplicationInfo.ratio : 7.5 * ApplicationInfo.ratio orientation: Qt.Vertical verticalLayoutDirection: ListView.TopToBottom interactive: false model: optionListModel readonly property real buttonHeight: (height - 3 * spacing) / 4 add: Transition { NumberAnimation { properties: "y"; from: parent.y; duration: 500 } onRunningChanged: { optionListView.blockAnswerButtons = running } } property bool blockAnswerButtons: false delegate: AnswerButton { id: optionButton width: parent.width height: optionListView.buttonHeight textLabel: optionValue anchors.right: parent.right anchors.left: parent.left blockAllButtonClicks: optionListView.blockAnswerButtons isCorrectAnswer: closeness === 100 onPressed: optionListView.blockAnswerButtons = true onIncorrectlyPressed: { if(!items.assessmentMode) { closenessMeter.stopAnimations() closenessMeterIncorrectAnswerAnimation.start() mainQuizScreen.closenessMeterValue = closeness } else { optionListView.blockAnswerButtons = false Activity.appendAndAddQuestion() } } onCorrectlyPressed: { if(!items.assessmentMode) { closenessMeter.stopAnimations() particles.burst(30) closenessMeterCorrectAnswerAnimation.start() mainQuizScreen.closenessMeterValue = closeness } else { Activity.assessmentModeQuestions.shift() mainQuizScreen.numberOfCorrectAnswers++ Activity.nextSubLevel(true) } } } } } } Rectangle { id: closenessMeter x: ((background.width - items.bar.barZoom * items.bar.fullButton * 5.6) < (width + 10 * ApplicationInfo.ratio) && background.horizontalLayout) ? background.width - width - 42 * ApplicationInfo.ratio : background.width - width - 10 * ApplicationInfo.ratio y: (background.width - items.bar.barZoom * items.bar.fullButton * 5.6) < (width + 10 * ApplicationInfo.ratio) ? background.height - bar.height - height - 10 * ApplicationInfo.ratio : background.height - height - 10 * ApplicationInfo.ratio height: 40 * ApplicationInfo.ratio width: 150 * ApplicationInfo.ratio radius: width * 0.06 border.width: 2 border.color: "black" opacity: 0.78 visible: !items.assessmentMode Item { width: parent.width - 3 * ApplicationInfo.ratio height: parent.height anchors.centerIn: parent GCText { id: closenessText color: "black" anchors.fill: parent fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: qsTr("Accuracy: %1%").arg(closenessMeterValue) } } SequentialAnimation { id: closenessMeterIncorrectAnswerAnimation onStarted: optionListView.blockAnswerButtons = true NumberAnimation { target: closenessMeter; property: "scale"; to: 1.1; duration: 450 } NumberAnimation { target: closenessMeter; property: "scale"; to: 1.0; duration: 450 } onStopped: optionListView.blockAnswerButtons = false } SequentialAnimation { id: closenessMeterCorrectAnswerAnimation onStarted: optionListView.blockAnswerButtons = true NumberAnimation { target: closenessMeter; property: "scale"; to: 1.1; duration: 450 } NumberAnimation { target: closenessMeter; property: "scale"; to: 1.0; duration: 450 } NumberAnimation { target: closenessMeter; property: "scale"; to: 1.1; duration: 450 } NumberAnimation { target: closenessMeter; property: "scale"; to: 1.0; duration: 450 } ScriptAction { script: { Activity.nextSubLevel() } } } ParticleSystemStarLoader { id: particles clip: false } function stopAnimations() { optionListView.blockAnswerButtons = false closenessMeterCorrectAnswerAnimation.stop() closenessMeterIncorrectAnswerAnimation.stop() } } ProgressBar { id: progressBar height: bar.height * 0.35 width: parent.width * 0.35 readonly property real percentage: (mainQuizScreen.numberOfCorrectAnswers / score.numberOfSubLevels) * 100 readonly property string message: qsTr("%1%").arg(value) value: Math.round(percentage * 10) / 10 - maximumValue: 100 + to: 100 visible: items.assessmentMode y: parent.height - bar.height - height - 10 * ApplicationInfo.ratio x: parent.width - width * 1.1 GCText { id: progressbarText anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" text: parent.message z: 2 } } Rectangle { id: restartAssessmentMessage width: parent.width height: parent.height - bar.height * 1.25 anchors.top: parent.top anchors.margins: 10 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter radius: 4 * ApplicationInfo.ratio visible: items.assessmentMode && (score.currentSubLevel >= score.numberOfSubLevels) z: 4 GCText { anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: mediumSize text: qsTr("Your final score is: %1%.

%2").arg(progressBar.value).arg(progressBar.value <= 90 ? qsTr("You should score above 90% to become a Solar System expert!
Retry to test your skills again or train in normal mode to explore more about the Solar System.") : qsTr("Great! You can replay the assessment to test your knowledge on more questions.")) } // To prevent clicking on options under it MouseArea { anchors.fill: parent } onVisibleChanged: scaleAnimation.start() NumberAnimation { id: scaleAnimation target: restartAssessmentMessage properties: "scale" from: 0 to: 1 duration: 1500 easing.type: Easing.OutBounce } } Score { id: score anchors.bottom: undefined anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top z: 0 } } diff --git a/src/core/BuyMeOverlay.qml b/src/core/BuyMeOverlay.qml index f1daa02a0..60bce02dd 100644 --- a/src/core/BuyMeOverlay.qml +++ b/src/core/BuyMeOverlay.qml @@ -1,121 +1,121 @@ /* GCompris - BuyMeOverlay.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.Controls 2.0 import GCompris 1.0 Item { anchors { fill: parent bottomMargin: bar.height * 1.2 } Rectangle { anchors.fill: parent opacity: 0.5 color: "grey" } /* Activation Instruction */ Rectangle { id: instruction z: 99 anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: - cancelButton.width / 2 top: parent.top topMargin: 40 } width: parent.width - cancelButton.width * 2 height: parent.height/2 radius: 10 border.width: 2 border.color: "black" color: 'white' Row { id: row anchors { fill: parent margins: 10 } Image { id: lock source: "qrc:/gcompris/src/activities/menu/resource/lock.svg" sourceSize.width: 30 * ApplicationInfo.ratio } GCText { id: instructionTxt fontSizeMode: Text.Fit color: "black" style: Text.Outline styleColor: "white" horizontalAlignment: Text.AlignHCenter width: parent.width - lock.width height: instruction.height - row.anchors.margins/2 wrapMode: TextEdit.WordWrap z: 2 text: qsTr("This activity is only available in the full version of GCompris." + "
" + "On https://gcompris.net " + "you will find the instructions to obtain an activation code." + " " + "Then go to the main configuration dialog to enter the code.") } } } MultiPointTouchArea { // Just to catch mouse events anchors.fill: parent } Keys.onEscapePressed: home() Keys.onPressed: { event.accepted = true if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Q) { // Ctrl+Q exit the application Core.quit(page); } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_B) { // Ctrl+B toggle the bar ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden; } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_F) { // Ctrl+F toggle fullscreen ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_M) { // Ctrl+M toggle sound ApplicationSettings.isAudioEffectsEnabled = !ApplicationSettings.isAudioEffectsEnabled } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_W) { // Ctrl+W exit the current activity home() } } // The cancel button GCButtonCancel { id: cancelButton onClose: home() } } diff --git a/src/core/BuyMeOverlayInapp.qml b/src/core/BuyMeOverlayInapp.qml index 5e57f44b2..01a425447 100644 --- a/src/core/BuyMeOverlayInapp.qml +++ b/src/core/BuyMeOverlayInapp.qml @@ -1,136 +1,133 @@ /* GCompris - BuyMeOverlayInapp.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 GCompris 1.0 Item { anchors { fill: parent bottomMargin: bar.height * 1.2 } Rectangle { anchors.fill: parent opacity: 0.5 color: "grey" } /* Activation Instruction */ Rectangle { id: instructionsArea z: 99 anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: - cancelButton.width / 2 top: parent.top topMargin: 40 } width: parent.width - cancelButton.width * 2 height: Math.max(instructionTxt.height, lock.height) + 2*row.anchors.margins radius: 10 border.width: 2 border.color: "black" color: 'white' Row { id: row anchors { fill: parent margins: 10 } Image { id: lock source: "qrc:/gcompris/src/activities/menu/resource/lock.svg" sourceSize.width: 30 * ApplicationInfo.ratio } GCText { id: instructionTxt fontSize: mediumSize color: "black" style: Text.Outline styleColor: "white" horizontalAlignment: Text.AlignHCenter width: parent.width - lock.width wrapMode: TextEdit.WordWrap z: 2 text: qsTr("This activity is only available in the full version of GCompris.") } } - Button { + GCButton { width: instructionsArea.width * 0.9 height: 60 * ApplicationInfo.ratio anchors { horizontalCenter: parent.horizontalCenter top: instructionsArea.bottom topMargin: 10 } text: qsTr("Buy the full version").toUpperCase() - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" onClicked: { if(ApplicationSettings.isDemoMode) ApplicationSettings.isDemoMode = false } } } MultiPointTouchArea { // Just to catch mouse events anchors.fill: parent } Keys.onEscapePressed: home() Keys.onPressed: { event.accepted = true if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Q) { // Ctrl+Q exit the application Core.quit(page); } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_B) { // Ctrl+B toggle the bar ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden; } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_F) { // Ctrl+F toggle fullscreen ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_M) { // Ctrl+M toggle sound ApplicationSettings.isAudioEffectsEnabled = !ApplicationSettings.isAudioEffectsEnabled } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_W) { // Ctrl+W exit the current activity home() } } // The cancel button GCButtonCancel { id: cancelButton onClose: home() } } diff --git a/src/core/DialogAbout.qml b/src/core/DialogAbout.qml index c45c3c0c8..fac0561b5 100644 --- a/src/core/DialogAbout.qml +++ b/src/core/DialogAbout.qml @@ -1,153 +1,152 @@ /* GCompris - DialogAbout.qml * * Copyright (C) 2016 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 GCompris 1.0 /** * GCompris' full screen about dialog. * @ingroup infrastructure * * @sa DialogBackground */ DialogBackground { visible: false title: qsTr("About GCompris") button0Text: qsTr("License") onButton0Hit: { licenseContainer.visible = true } File { id: licenseFile name: "qrc:/gcompris/src/core/COPYING" onError: print(msg) } DialogBackground { id: licenseContainer visible: false anchors.fill: parent title: qsTr("License") onVisibleChanged: { if(!content) { content = licenseFile.read() } } onClose: visible = false } //: Replace this string with your names, one name per line. property string translators: qsTr("translator-credits") === "translator-credits" ? "" : qsTr("translator-credits") + "
" // Let's try to maitain here the contributor list sorted by number of commits // git shortlog -se | sort -nr | cut -c8- | sed 's/ <.*>/,/' | xargs property string developers: "Bruno Coudoin, Johnny Jazeix, Timothée Giet, Holger Kaelberer, Akshat Tandon, Rajdeep Kaur, siddhesh suthar, Aruna Sankaranarayanan, Aman Kumar Gupta, Stephane Mankowski, Amit Tomar, Yuri Chornoivan, Thibaut ROMAIN, Amit Sagtani, Ilya Bizyaev, Stefan Toncu, SagarC Agarwal, Ganesh Harshan, Divyam Madaan, Ayush Agrawal, Rudra Nil Basu, Harry Mecwan, Pulkit Gupta, Manuel Tondeur, Karl Ove Hufthammer, Burkhard Lück, Horia PELLE, Bharath M S, Per Andersson, JB BUTET, Himanshu Vishwakarma, Emmanuel Charruau, Rohit Das, Arkit Vora, Antoni Bella Pérez, Sergey Popov, Séamus Ó Briain, Nitish Chauhan, Imran Tatriev, Harpreet S, Harald H, Alexis Breton, Aleix Pol, Rajat Asthana, rahul yadav, Aseem Arora, Akshay Kumar, Utkarsh Tiwari, Souradeep Barua, Parth Partani, Paolo Gibellini, Nick Richards, Luigi Toscano, Luciano Montanaro, Chaitanya KS, B.J. Cupps, Anu Mittal, Antos Vaclauski, André Marcelo Alvarenga, Alexander Potashev, Ynon Perek, Yask Srivastava, Stefan Asserhäll, Shashwat Dixit, Sayan Biswas, Rishabh Gupta, Răpițeanu Viorel-Cătălin, Mantas Kriaučiūnas, Łukasz Wojniłowicz, Jose Riha, Jonathan Demeyer, Jiri Bohac, Hannah von Reth, Djalil Mesli, Clément Coudoin, Billy Laws, Artur Puzio, Arnold Dumas, Antoś Vaclaŭski, Andrey Cygankov" property string gcVersion: ApplicationInfo.GCVersion property string qtVersion: ApplicationInfo.QTVersion property string gcVersionTxt: qsTr("GCompris %1").arg(gcVersion) property string qtVersionTxt: qsTr("Based on Qt %1").arg(qtVersion) content: "
" + "" + qsTr("GCompris Home Page: https://gcompris.net") + "" + "
" + "
" + gcVersionTxt + " " + qtVersionTxt + "
" + "
" + "" + qsTr("GCompris is a Free Software developed within the KDE community.") + "

" + qsTr("KDE is a world-wide network of software engineers, artists, writers, translators and facilitators " + "who are committed to Free Software development. " + "This community has created hundreds of Free Software applications as part of the KDE " + "frameworks, workspaces and applications.

" + "KDE is a cooperative enterprise in which no single entity controls the " + "efforts or products of KDE to the exclusion of others. Everyone is welcome to join and " + "contribute to KDE, including you.

" + "Visit %2 for " + "more information about the KDE community and the software we produce.") .arg("https://www.gnu.org/philosophy/free-sw.html") .arg("https://www.kde.org/") + "" + "

" + qsTr("Software can always be improved, and the KDE team is ready to " + "do so. However, you - the user - must tell us when " + "something does not work as expected or could be done better.

" + "KDE has a bug tracking system. Visit " + "%1 to report a bug.

" + "If you have a suggestion for improvement then you are welcome to use " + "the bug tracking system to register your wish. Make sure you use the " + "severity called \"Wishlist\".") .arg("https://bugs.kde.org/") + "

" + qsTr("You do not have to be a software developer to be a member of the " + "KDE team. You can join the national teams that translate " + "program interfaces. You can provide graphics, themes, sounds, and " + "improved documentation. You decide!" + "

" + "Visit " + "%1 " + "for information on some projects in which you can participate." + "

" + "If you need more information or documentation, then a visit to " + "%2 " + "will provide you with what you need.") .arg("https://www.kde.org/community/getinvolved/") .arg("https://techbase.kde.org/") + "

" + qsTr("To support development the KDE community has formed the KDE e.V., a non-profit organization " + "legally founded in Germany. KDE e.V. represents the KDE community in legal and financial matters. " + "See %1" + " for information on KDE e.V.

" + "KDE benefits from many kinds of contributions, including financial. " + "We use the funds to reimburse members and others for expenses " + "they incur when contributing. Further funds are used for legal " + "support and organizing conferences and meetings.

" + "We would like to encourage you to support our efforts with a " + "financial donation, using one of the ways described at " + "%2." + "

Thank you very much in advance for your support.") .arg("https://ev.kde.org/") .arg("https://www.kde.org/community/donations/") + "

" + qsTr("A big thanks to the development team: %1").arg(developers) + "

" + qsTr("A big thanks to the translation team: %1") .arg(translators) + - "
" + "Copyright 2000-2018 Timothée Giet and Others" + "
" + "
" + "
" + "Copyright 2000-2019 Timothée Giet and Others" + "
" + "
" } diff --git a/src/core/DialogBackground.qml b/src/core/DialogBackground.qml index a655e46a9..54fdd6bd2 100644 --- a/src/core/DialogBackground.qml +++ b/src/core/DialogBackground.qml @@ -1,173 +1,171 @@ /* GCompris - DialogBackground.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.Controls 2.0 import GCompris 1.0 /** * Base QML component for all full screen dialog screens. * @ingroup components * * Defines the general screen layout used by the following full screen * dialog elements: * * DialogAbout, DialogHelp. * * For a general purpose dialog cf. GCDialog. * * @inherit QtQuick.Rectangle */ Rectangle { id: dialogBackground color: "#696da3" border.color: "black" border.width: 1 z: 1000 property bool isDialog: true property string title property alias titleIcon: titleIcon.source property string content property string contentIcon property alias button0Text: button0.text signal close signal start signal pause signal play signal stop signal button0Hit Row { spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogBackground.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 Row { spacing: 2 padding: 8 Image { id: titleIcon } GCText { id: title text: dialogBackground.title width: dialogBackground.width - (70 + cancel.width) 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 - (30 + title.height * 1.2) border.color: "black" border.width: 2 anchors.margins: 100 Flickable { id: flick anchors.margins: 8 anchors.fill: parent contentWidth: textContent.contentWidth contentHeight: iconImage.height + textContent.contentHeight flickableDirection: Flickable.VerticalFlick clip: true - Button { + GCButton { id: button0 visible: text != "" + theme: "highContrast" onClicked: { dialogBackground.button0Hit() } width: 150 * ApplicationInfo.ratio height: visible ? 40 * ApplicationInfo.ratio : 0 anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 8 } - style: GCButtonStyle { - theme: "highContrast" - } } Image { id: iconImage source: contentIcon visible: contentIcon != "" width: 100 * ApplicationInfo.ratio height: visible ? iconImage.width : 0 sourceSize.width: iconImage.width sourceSize.height: iconImage.width anchors.top: button0.bottom anchors.horizontalCenter: parent.horizontalCenter } GCText { id: textContent text: style + "" + content + "" width: flick.width height: flick.height - button0.height anchors.top: iconImage.bottom fontSize: regularSize wrapMode: TextEdit.Wrap textFormat: TextEdit.RichText property string style: "" } } // The scroll buttons GCButtonScroll { anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: flick.bottom anchors.bottomMargin: 5 * ApplicationInfo.ratio onUp: flick.flick(0, 1400) onDown: flick.flick(0, -1400) upVisible: flick.visibleArea.yPosition <= 0 ? false : true downVisible: flick.visibleArea.yPosition + flick.visibleArea.heightRatio >= 1 ? false : true } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { id: cancel onClose: parent.close() } } diff --git a/src/core/DownloadDialog.qml b/src/core/DownloadDialog.qml index 13bdef977..827fdd686 100644 --- a/src/core/DownloadDialog.qml +++ b/src/core/DownloadDialog.qml @@ -1,299 +1,295 @@ /* GCompris - DownloadDialog.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import QtQuick.Layouts 1.3 -import QtQuick.Controls 1.5 +import QtQuick.Controls 2.0 import "qrc:/gcompris/src/core/core.js" as Core /** * A QML component visualizing download progress. * @ingroup infrastructure * * A GCDialog style dialog providing visual feedback for download progress. * Uses DownloadManager for download control. * * Can be conveniently instantiated dynamically using showDownloadDialog * from core.js. * * @inherit QtQuick.Item * @sa DownloadManager, showDownloadDialog */ Item { id: downloadDialog opacity: 0 anchors { fill: parent } /** * type:Item * Parent object for the dialog in the qml hierarchy. */ property Item main /** * type:bool * Whether to close the dialog automatically when download has finished. * Default is false. */ property bool autohide: false; /** * type:bool * Whether to report download success in a Dialog. * Default is true. */ property bool reportSuccess: true; /** * type:bool * Whether to report download errors in a Dialog. * Default is true. */ property bool reportError: true; /** * type:bool * Whether the dialog has been created dynamically. If set to true, the * component takes care of destroying itself after finished. * Default is false. * * @sa Core.destroyDialog */ property bool dynamic: false /** * type:bool * Whether the 'Background' button should be visible. * Default is true. */ property alias backgroundButtonVisible: backgroundButton.visible /** * type:bool * Whether the 'Abort' button should be visible. * Default is true. */ property alias abortButtonVisible: abortButton.visible /** * type:bool * Fixed font size used in this dialog. Note, fixed font-sizes should be * used in dialog components, to make sure they stay within bounds when * user increases font size. * Default is 14. */ property int fixedFontSize: 14 /// @cond INTERNAL_DOCS // start and stop trigs the animation FIXME: need to document? signal start signal stop // emitted at stop animation end signal close signal finished onStart: opacity = 1 onStop: opacity = 0 onClose: destroy() Behavior on opacity { NumberAnimation { duration: 200 } } onOpacityChanged: opacity === 0 ? close() : null function shutdown() { if (downloadDialog.dynamic) Core.destroyDialog(downloadDialog); else downloadDialog.close(); } /// @endcond Rectangle { anchors.fill: parent opacity: 0.8 color: "grey" MouseArea { // Empty mouseArea to prevent clicking "behind" the Dialog anchors.fill: parent enabled: downloadDialog.opacity != 0 } } Item { id: instruction anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: parent.height * 0.1 } width: parent.width * 0.8 GCText { id: instructionTxt fontSize: mediumSize color: "black" horizontalAlignment: Text.AlignHCenter width: parent.width wrapMode: TextEdit.WordWrap z: 2 height: 60 * ApplicationInfo.ratio text: qsTr("Downloading ...") } Rectangle { anchors.fill: instructionTxt z: 1 opacity: 0.9 radius: 10 border.width: 2 border.color: "white" gradient: Gradient { GradientStop { position: 0.0; color: "#fff" } GradientStop { position: 0.9; color: "#fff" } GradientStop { position: 1.0; color: "#ddd" } } } ProgressBar { id: downloadDialogProgress width: parent.width anchors { horizontalCenter: parent.horizontalCenter top: instructionTxt.bottom topMargin: 10 } visible: true Layout.alignment: Qt.AlignHCenter Layout.rowSpan: 1 Layout.fillWidth: true - minimumValue: 0 - maximumValue: 100 + from: 0 + to: 100 value: 0 } - Button { + GCButton { id: backgroundButton width: parent.width height: 60 * ApplicationInfo.ratio + fixedFontSize: downloadDialog.fixedFontSize + theme: "highContrast" anchors { horizontalCenter: parent.horizontalCenter top: downloadDialogProgress.bottom topMargin: 10 } //: Run this task in background text: qsTr("Background") - style: GCButtonStyle { - fixedFontSize: downloadDialog.fixedFontSize - theme: "highContrast" - } visible: true onClicked: downloadDialog.shutdown(); } - Button { + GCButton { id: abortButton width: parent.width height: 60 * ApplicationInfo.ratio anchors { horizontalCenter: parent.horizontalCenter top: backgroundButton.bottom topMargin: 10 } text: qsTr("Abort") - style: GCButtonStyle { - fixedFontSize: downloadDialog.fixedFontSize - theme: "highContrast" - } + fixedFontSize: downloadDialog.fixedFontSize + theme: "highContrast" visible: true onClicked: { if (DownloadManager.downloadIsRunning()) DownloadManager.abortDownloads(); downloadDialog.finished(); downloadDialog.shutdown(); } } } Connections { target: DownloadManager onError: { //console.warn("DownloadDialog: DM reports error: " + code + ": " + msg); downloadDialog.finished(); if (downloadDialog.reportError && code != 5) { // no error: OperationCanceledError // show error message var messageDialog = Core.showMessageDialog(main, qsTr("Download error (code: %1): %2").arg(code).arg(msg), "", null, "", null, function() { downloadDialog.shutdown(); } ); } else downloadDialog.shutdown(); } onDownloadProgress: downloadDialogProgress.value = 100 * bytesReceived / bytesTotal; onDownloadStarted: { //console.log("dialog: DM reports started: " + resource); downloadDialogProgress.value = 0; } onDownloadFinished: { //console.log("dialog: DM reports finished: " + code); downloadDialog.finished(); if (downloadDialog.reportSuccess && code != 1) // note: errors will be reported by the onError handler { // report success downloadDialog.stop(); var infText = ""; if (code == 0) { // Success infText = qsTr("Your download finished successfully. The data files are now available.") + '\n' + qsTr("Restart any currently active activity."); } else if (code == 2) // NoChange infText = qsTr("Your local data files are up-to-date.") var messageDialog = Core.showMessageDialog(main, infText, "", null, "", null, null ); } else if (downloadDialog.autohide) downloadDialog.shutdown(); } } } diff --git a/src/core/GCButtonStyle.qml b/src/core/GCButton.qml similarity index 92% rename from src/core/GCButtonStyle.qml rename to src/core/GCButton.qml index 7ebc7dca4..3e9d9c120 100644 --- a/src/core/GCButtonStyle.qml +++ b/src/core/GCButton.qml @@ -1,140 +1,143 @@ -/* GCompris - GCButtonStyle.qml +/* GCompris - GCButton.qml * - * Copyright (C) 2014 Bruno Coudoin + * Copyright (C) 2019 Johnny Jazeix * * Authors: - * Bruno Coudoin + * 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 QtQuick.Controls 2.0 import GCompris 1.0 /** - * Provides styling for GCompris' Buttons. + * A QML component representing GCompris' buttons. * @ingroup components * - * @inherit QtQuick.Controls.Styles.ButtonStyle + * @inherit QtQuick.Button */ -ButtonStyle { +Button { + id: control + /** * type:real * Fixed font size of the label in pt. * * Set to a value > 0 for enforcing a fixed font.pointSize for the label, * that won't be updated with ApplicationSettings.baseFontSize. * @sa ApplicationSettings.baseFontSize, GCText.fixFontSize */ property real fixedFontSize: -1 /** * type:string * theme of the button. For now, three themes are accepted: "light" and "dark" and "highContrast" * * Default is dark. */ property string theme: "dark" /** * type:var * existing themes for the button. * A theme is composed of: * the colors of the button when selected: selectedColorGradient0 and selectedColorGradient1. * the colors of the button when not selected: backgroundColorGradient0 and backgroundColorGradient1. * the button's border color * the text color */ property var themes: { "dark": { backgroundColorGradient0: "#23373737", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#13373737", selectedColorGradient1: "#803ACAFF", borderColor: "#FF373737", textColor: "#FF373737" }, "light": { backgroundColorGradient0: "#42FFFFFF", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#23FFFFFF", selectedColorGradient1: "#803ACAFF", borderColor: "white", textColor: "white" }, "highContrast": { backgroundColorGradient0: "#EEFFFFFF", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#AAFFFFFF", selectedColorGradient1: "#803ACAFF", borderColor: "white", textColor: "373737" } } property string textSize: "regular" property var textSizes: { "regular": { fontSize: 14, fontBold: false }, "subtitle": { fontSize: 16, fontBold: true }, "title": { fontSize: 24, fontBold: true } } + + focusPolicy: Qt.NoFocus background: Rectangle { border.width: control.activeFocus ? 4 : 2 border.color: themes[theme].borderColor radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: control.pressed ? themes[theme].selectedColorGradient0 : themes[theme].backgroundColorGradient0 } GradientStop { position: 1 ; color: control.pressed ? themes[theme].selectedColorGradient1 : themes[theme].backgroundColorGradient1 } } } - label: Item { + contentItem: Item { id: labelItem anchors.fill: parent implicitWidth: labelText.implicitWidth implicitHeight: labelText.implicitHeight GCText { id: labelText color: themes[theme].textColor text: control.text fontSize: textSizes[textSize].fontSize font.bold: textSizes[textSize].fontBold anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit Component.onCompleted: { if (fixedFontSize > 0) { labelText.fixFontSize = true; labelText.fontSize = fixedFontSize; } } } } } diff --git a/src/core/GCComboBox.qml b/src/core/GCComboBox.qml index f11aa1da0..e7b02b18b 100644 --- a/src/core/GCComboBox.qml +++ b/src/core/GCComboBox.qml @@ -1,322 +1,322 @@ /* GCompris - GCComboBox.qml * * Copyright (C) 2015 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 2.0 import GCompris 1.0 /** * A QML component unifying comboboxes in GCompris. * @ingroup components * * GCComboBox contains a combobox and a label. * When the combobox isn't active, it is displayed as a button containing the current value * and the combobox label (its description). * Once the button is clicked, the list of all available choices is displayed. * Also, above the list is the combobox label. * * Navigation can be done with keys and mouse/gestures. * As Qt comboboxes, you can either have a js Array or a Qml model as model. * GCComboBox should now be used wherever you'd use a QtQuick combobox. It has * been decided to implement comboboxes ourselves in GCompris because of * some integration problems on some OSes (native dialogs unavailable). */ Item { id: gccombobox focus: true width: parent.width height: flow.height /** * type:Item * Where the list containing all choices will be displayed. * Should be the dialogActivityConfig item if used on config. */ property Item background /** * type:int * Current index of the combobox. */ property int currentIndex: -1 /** * type:string * Current text displayed in the combobox when inactive. */ property string currentText /** * type:alias * Model for the list (user has to specify one). */ property alias model: gridview.model /** * type:string * Text besides the combobox, used to describe what the combobox is for. */ property string label /** * type:bool * Internal value. * If model is an js Array, we access data using modelData and [] else qml Model, we need to use model and get(). */ readonly property bool isModelArray: model.constructor === Array // start and stop trigs the animation signal start signal stop // emitted at stop animation end signal close onCurrentIndexChanged: { currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } /** * type:Flow * Combobox display when inactive: the button with current choice and its label besides. */ Flow { id: flow width: parent.width spacing: 5 * ApplicationInfo.ratio Rectangle { id: button visible: true // Add radius to add some space between text and borders implicitWidth: Math.max(200, currentTextBox.width+radius) implicitHeight: 50 * ApplicationInfo.ratio border.width: 2 border.color: "#373737" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: mouseArea.pressed ? "#C03ACAFF" : "#23373737" } GradientStop { position: 1 ; color: mouseArea.pressed ? "#803ACAFF" : "#13373737" } } // Current value of combobox GCText { id: currentTextBox anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: currentText fontSize: mediumSize color: "#373737" } MouseArea { id: mouseArea anchors.fill: parent onReleased: { popup.visible = true } } } Item { width: labelText.width height: button.height GCText { id: labelText text: label anchors.verticalCenter: parent.verticalCenter fontSize: mediumSize wrapMode: Text.WordWrap } } } /** * type:Item * Combobox display when active: header with the description and the gridview containing all the available choices. */ Item { id: popup visible: false width: parent.width height: parent.height parent: background z: 100 focus: visible // Forward event to activity if key pressed is not one of the handled key // (ctrl+F should still resize the window for example) Keys.onPressed: { if(event.key !== Qt.Key_Back) { background.currentActivity.Keys.onPressed(event) } } Keys.onReleased: { if(event.key === Qt.Key_Back) { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); event.accepted = true } } Keys.onRightPressed: gridview.moveCurrentIndexRight(); Keys.onLeftPressed: gridview.moveCurrentIndexLeft(); Keys.onDownPressed: gridview.moveCurrentIndexDown(); Keys.onUpPressed: gridview.moveCurrentIndexUp(); Keys.onEscapePressed: { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); } Keys.onEnterPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onReturnPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onSpacePressed: { acceptChange(); hidePopUpAndRestoreFocus(); } // Don't accept the list value, restore previous value function discardChange() { if(isModelArray) { for(var i = 0 ; i < model.count ; ++ i) { if(model[currentIndex].text === currentText) { currentIndex = i; break; } } } else { for(var i = 0 ; i < model.length ; ++ i) { if(model.get(currentIndex).text === currentText) { currentIndex = i; break; } } } gridview.currentIndex = currentIndex; } // Accept the change. Updates the currentIndex and text of the button function acceptChange() { currentIndex = gridview.currentIndex; currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } function hidePopUpAndRestoreFocus() { popup.visible = false; // Restore focus on previous activity for keyboard input background.currentActivity.forceActiveFocus(); } Rectangle { id: listBackground anchors.fill: parent radius: 10 color: "grey" Rectangle { id : headerDescription z: 10 width: gridview.width height: gridview.elementHeight GCText { text: label fontSize: mediumSize wrapMode: Text.WordWrap anchors.horizontalCenter: parent.horizontalCenter } GCButtonCancel { id: discardIcon anchors.right: headerDescription.right anchors.top: headerDescription.top MouseArea { anchors.fill: parent onClicked: { popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } GridView { id: gridview z: 4 readonly property int elementHeight: 40 * ApplicationInfo.ratio // each element has a 300 width size minimum. If the screen is larger than it, // we do a grid with cases with 300px for width at minimum. // If you have a better idea/formula to have a different column number, don't hesitate, change it :). readonly property int numberOfColumns: Math.max(1, Math.floor(width / (300 * ApplicationInfo.ratio))) contentHeight: isModelArray ? elementHeight*model.count/numberOfColumns : elementHeight*model.length/numberOfColumns width: listBackground.width height: listBackground.height-headerDescription.height currentIndex: gccombobox.currentIndex flickableDirection: Flickable.VerticalFlick clip: true anchors.top: headerDescription.bottom cellWidth: width / numberOfColumns cellHeight: elementHeight delegate: Component { Rectangle { width: gridview.cellWidth height: gridview.elementHeight color: GridView.isCurrentItem ? "darkcyan" : "beige" border.width: GridView.isCurrentItem ? 3 : 2 radius: 5 Image { id: isSelectedIcon visible: parent.GridView.isCurrentItem source: "qrc:/gcompris/src/core/resource/apply.svg" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 10 sourceSize.width: (gridview.elementHeight * 0.8) } GCText { id: textValue text: isModelArray ? modelData.text : model.text anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { currentIndex = index popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } } } } } diff --git a/src/core/GCCreationHandler.qml b/src/core/GCCreationHandler.qml index 7953dca62..928c489bb 100644 --- a/src/core/GCCreationHandler.qml +++ b/src/core/GCCreationHandler.qml @@ -1,358 +1,349 @@ /* GCompris - GCCreationHandler.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 -import QtQuick.Controls 1.5 +import QtQuick.Controls 2.0 import GCompris 1.0 -import QtQuick.Controls.Styles 1.4 import "qrc:/gcompris/src/core/core.js" as Core Rectangle { id: creationHandler width: parent.width height: parent.height color: "#ABCDEF" border.color: "white" border.width: 2 radius: 20 visible: false z: 2000 signal close signal fileLoaded(var data, var filePath) onClose: { fileNameInput.focus = false fileNameInput.text = "" visible = false viewContainer.selectedFileIndex = -1 creationsList.flick(0, 1400) } MouseArea { anchors.fill: parent onClicked: viewContainer.selectedFileIndex = -1 } property var dataToSave property bool isSaveMode: false readonly property string activityName: ActivityInfoTree.currentActivity.name.split('/')[0] readonly property string sharedDirectoryPath: ApplicationSettings.userDataPath + "/" + activityName + "/" readonly property string fileSavePath: "file://" + sharedDirectoryPath + '/' + fileNameInput.text + ".json" ListModel { id: fileNames } Directory { id: directory } File { id: file onError: console.error("File error: " + msg); } JsonParser { id: parser onError: console.error("Error parsing JSON: " + msg); } Loader { id: replaceFileDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("A file with this name already exists. Do you want to replace it?") button1Text: qsTr("Yes") button2Text: qsTr("No") onButton1Hit: { writeData() active = false } onButton2Hit: active = false } anchors.fill: parent focus: true active: false onStatusChanged: if (status == Loader.Ready) item.start() } function refreshWindow(filterText) { var pathExists = file.exists(sharedDirectoryPath) if(!pathExists) return fileNames.clear() var files = directory.getFiles(sharedDirectoryPath) for(var i = 0; i < files.length; i++) { if(filterText === undefined || filterText === "" || (files[i].toLowerCase()).indexOf(filterText) !== -1) fileNames.append({ "name": files[i] }) } } function loadWindow() { creationHandler.visible = true creationHandler.isSaveMode = false fileNameInput.forceActiveFocus() refreshWindow() } function loadFile(fileName) { var filePath = "file://" + sharedDirectoryPath + fileNames.get(viewContainer.selectedFileIndex).name var data = parser.parseFromUrl(filePath) creationHandler.fileLoaded(data, filePath) creationHandler.close() } function deleteFile() { var filePath = "file://" + sharedDirectoryPath + fileNames.get(viewContainer.selectedFileIndex).name if(file.rmpath(filePath)) { Core.showMessageDialog(creationHandler, qsTr("%1 deleted successfully!").arg(filePath), qsTr("Ok"), null, "", null, null); } else { Core.showMessageDialog(creationHandler, qsTr("Unable to delete %1!").arg(filePath), qsTr("Ok"), null, "", null, null); } viewContainer.selectedFileIndex = -1 refreshWindow() } function saveWindow(data) { creationHandler.visible = true creationHandler.isSaveMode = true creationHandler.dataToSave = data fileNameInput.forceActiveFocus() refreshWindow() } function saveFile() { if(activityName === "" || fileNameInput.text === "") return if(!file.exists(sharedDirectoryPath)) file.mkpath(sharedDirectoryPath) if(file.exists(fileSavePath)) { replaceFileDialog.active = true } else writeData() } function writeData() { file.write(JSON.stringify(creationHandler.dataToSave), fileSavePath) Core.showMessageDialog(creationHandler, qsTr("Saved successfully!"), qsTr("Ok"), null, "", null, null); refreshWindow() } function searchFiles() { viewContainer.selectedFileIndex = -1 refreshWindow(fileNameInput.text.toLowerCase()) } TextField { id: fileNameInput width: parent.width / 2 font.pointSize: NaN font.pixelSize: height * 0.6 height: saveButton.height anchors.verticalCenter: saveButton.verticalCenter anchors.left: parent.left anchors.leftMargin: 20 verticalAlignment: TextInput.AlignVCenter selectByMouse: true maximumLength: 15 placeholderText: creationHandler.isSaveMode ? qsTr("Enter file name") : qsTr("Search") onTextChanged: { if(!creationHandler.isSaveMode) searchFiles() } - style: TextFieldStyle { - textColor: "black" - background: Rectangle { - border.color: "black" - border.width: 1 - radius: fileNameInput.height / 4 - } + color: "black" + background: Rectangle { + border.color: "black" + border.width: 1 + radius: fileNameInput.height / 4 } } - Button { + GCButton { id: saveButton width: 70 * ApplicationInfo.ratio height: Math.min(creationHandler.height / 15, cancelButton.height) visible: creationHandler.isSaveMode text: qsTr("Save") - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" anchors.verticalCenter: cancelButton.verticalCenter anchors.left: fileNameInput.right anchors.leftMargin: 20 onClicked: saveFile() } property real cellWidth: 50 * ApplicationInfo.ratio property real cellHeight: cellWidth * 1.3 Rectangle { id: viewContainer anchors.top: cancelButton.bottom anchors.topMargin: 10 anchors.bottom: buttonRow.top anchors.margins: 20 border.color: "black" border.width: 2 radius: 20 anchors.left: parent.left anchors.right: parent.right property int selectedFileIndex: -1 MouseArea { anchors.fill: parent onClicked: viewContainer.selectedFileIndex = -1 } GridView { id: creationsList model: fileNames width: parent.width - 10 height: parent.height - 10 interactive: true cellHeight: creationHandler.cellHeight cellWidth: creationHandler.cellWidth anchors.top: parent.top anchors.topMargin: 10 anchors.left: parent.left anchors.leftMargin: 5 clip: true MouseArea { anchors.fill: parent enabled: !creationHandler.isSaveMode onClicked: { var itemIndex = creationsList.indexAt(mouseX, mouseY+creationsList.contentY) if(itemIndex == -1) viewContainer.selectedFileIndex = -1 else viewContainer.selectedFileIndex = itemIndex } } delegate: Item { height: creationHandler.cellHeight width: creationHandler.cellWidth readonly property string fileName: fileName.text Rectangle { anchors.fill: parent visible: index === viewContainer.selectedFileIndex color: "#E77936" opacity: 0.4 radius: 10 } Image { id: fileIcon width: creationHandler.cellWidth height: parent.height / 1.5 anchors.top: parent.top anchors.topMargin: 3 source: "qrc:/gcompris/src/core/resource/file_icon.svg" } GCText { id: fileName anchors.top: fileIcon.bottom height: parent.height - fileIcon.height - 15 width: creationHandler.cellWidth font.pointSize: tinySize fontSizeMode: Text.Fit wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter // Exclude ".json" while displaying file name text: name.slice(0, name.length - 5) } } } } Row { id: buttonRow x: parent.width / 20 spacing: 20 anchors.bottom: parent.bottom anchors.bottomMargin: 10 visible: !creationHandler.isSaveMode - Button { + GCButton { id: loadButton width: 70 * ApplicationInfo.ratio height: creationHandler.height / 15 text: qsTr("Load") enabled: viewContainer.selectedFileIndex != -1 - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" onClicked: creationHandler.loadFile() } - Button { + GCButton { id: deleteButton width: 70 * ApplicationInfo.ratio height: creationHandler.height / 15 text: qsTr("Delete") enabled: viewContainer.selectedFileIndex != -1 - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" onClicked: deleteFile() } } GCButtonCancel { id: cancelButton onClose: { parent.close() } } // The scroll buttons GCButtonScroll { anchors.right: viewContainer.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: viewContainer.bottom anchors.bottomMargin: 5 * ApplicationInfo.ratio onUp: creationsList.flick(0, 1400) onDown: creationsList.flick(0, -1400) upVisible: creationsList.visibleArea.yPosition <= 0 ? false : true downVisible: creationsList.visibleArea.yPosition + creationsList.visibleArea.heightRatio >= 1 ? false : true } } diff --git a/src/core/GCDialog.qml b/src/core/GCDialog.qml index b7715d162..e46352333 100644 --- a/src/core/GCDialog.qml +++ b/src/core/GCDialog.qml @@ -1,225 +1,221 @@ /* GCompris - GCDialog.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.Controls 2.0 import GCompris 1.0 /** * A QML component for GCompris dialogs. * @ingroup components * * Contains the following basic layout elements: Title (message), a GCompris * cancel button (GCButtonCancel) at the top right and two optional buttons. * * Can be conveniently instantiated dynamically in conjunction with * showMessageDialog from core.js. * * GCDialog should now be used wherever you'd use a QtQuick dialog. It has * been decided to implement dialogs ourselves in GCompris because of * missing translations of labels of Qt's standard buttons for some language * supported by GCompris, as well as integration problems on some OSes * (Sailfish OS). * * @inherit QtQuick.Item * @sa showMessageDialog */ Item { id: gcdialog /** * type:string * Heading instruction text. */ property alias message: instructionTxt.textIn /** * type:string * Label of the first button. */ property alias button1Text: button1.text /** * type:string * Label of the second button. */ property alias button2Text: button2.text /** * Emitted when the dialog should be started. * * Triggers fading in. */ signal start /** * Emitted when the dialog should be stopped. * * Triggers fading out. */ signal stop /** * Emitted when the dialog has stopped. */ signal close /** * Emitted when the first button has been clicked. */ signal button1Hit /** * Emitted when the second button has been clicked. */ signal button2Hit focus: true opacity: 0 anchors { fill: parent } onStart: opacity = 1 onStop: opacity = 0 onClose: destroy() Behavior on opacity { NumberAnimation { duration: 200 } } onOpacityChanged: opacity === 0 ? close() : null z: 1500 Rectangle { anchors.fill: parent opacity: 0.8 color: "grey" } MultiPointTouchArea { // Just to catch mouse events anchors.fill: parent } /* Message */ Item { id: instruction anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: buttonCancel.height } width: parent.width * 0.8 Rectangle { id: instructionTxtBg anchors.top: instruction.top z: 1 width: parent.width height: gcdialog.height - button1.height * 5 opacity: 0.9 radius: 10 border.width: 2 border.color: "white" gradient: Gradient { GradientStop { position: 0.0; color: "#fff" } GradientStop { position: 0.9; color: "#fff" } GradientStop { position: 1.0; color: "#ddd" } } Flickable { id: flick anchors.margins: 8 anchors.fill: parent contentWidth: instructionTxt.contentWidth contentHeight: instructionTxt.contentHeight flickableDirection: Flickable.VerticalFlick clip: true GCText { id: instructionTxt fontSize: regularSize color: "black" // @FIXME This property breaks the wrapping // 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 textFormat: TextEdit.RichText z: 2 text: style + "" + textIn + "" property string textIn property string style: "" } } } - Button { + GCButton { id: button1 width: parent.width height: (visible ? 60 : 30) * ApplicationInfo.ratio anchors { horizontalCenter: parent.horizontalCenter top: instructionTxtBg.bottom topMargin: 10 } - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" visible: text != "" onClicked: { gcdialog.button1Hit() gcdialog.stop() } } - Button { + GCButton { id: button2 width: parent.width height: (visible ? 60 : 30) * ApplicationInfo.ratio anchors { horizontalCenter: parent.horizontalCenter top: button1.bottom topMargin: 10 } - style: GCButtonStyle { - theme: "highContrast" - } + theme: "highContrast" visible: text != "" onClicked: { gcdialog.button2Hit() gcdialog.stop() } } } // Fixme not working, need to properly get the focus on this dialog Keys.onEscapePressed: { stop() event.accepted = true; } // The cancel button GCButtonCancel { id: buttonCancel onClose: parent.stop() } } diff --git a/src/core/GCDialogCheckBox.qml b/src/core/GCDialogCheckBox.qml index 6cefe6d64..5697f9639 100644 --- a/src/core/GCDialogCheckBox.qml +++ b/src/core/GCDialogCheckBox.qml @@ -1,67 +1,66 @@ /* GCompris - GCDialogCheckBox.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.Controls.Styles 1.4 +import QtQuick.Controls 2.0 import GCompris 1.0 /** * GCompris' CheckBox component. * @ingroup components * - * @inherit QtQuick.Controls.CheckBox + * @inherit QtQuick.Controls2.CheckBox */ CheckBox { id: checkBox - width: parent.width + width: parent.width /** * type:int * Font size of the label text. * By default it is set to 16 i.e. GCText.mediumSize * */ property int labelTextFontSize: 16 - /** * type:int * Height of the indicator image. */ property int indicatorImageHeight: 50 * ApplicationInfo.ratio - style: CheckBoxStyle { - spacing: 10 + spacing: 10 + + focusPolicy: Qt.NoFocus - indicator: Image { - sourceSize.height: indicatorImageHeight - property string suffix: control.enabled ? ".svg" : "_disabled.svg" - source: - control.checked ? "qrc:/gcompris/src/core/resource/apply" + suffix : - "qrc:/gcompris/src/core/resource/cancel" + suffix - } - label: GCText { - fontSize: labelTextFontSize - text: control.text - wrapMode: Text.WordWrap - width: parent.parent.width - 50 * ApplicationInfo.ratio - 10 * 2 - } + indicator: Image { + sourceSize.height: indicatorImageHeight + property string suffix: checkBox.enabled ? ".svg" : "_disabled.svg" + source: + checkBox.checked ? "qrc:/gcompris/src/core/resource/apply" + suffix : + "qrc:/gcompris/src/core/resource/cancel" + suffix + } + contentItem: GCText { + anchors.left: indicator.right + fontSize: labelTextFontSize + text: checkBox.text + wrapMode: Text.WordWrap + width: parent.width - 50 * ApplicationInfo.ratio - 10 * 2 } } diff --git a/src/core/GCProgressBar.qml b/src/core/GCProgressBar.qml new file mode 100644 index 000000000..fdcb6186b --- /dev/null +++ b/src/core/GCProgressBar.qml @@ -0,0 +1,57 @@ +/* GCompris - GCProgressBar.qml + * + * Copyright (C) 2019 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 2.0 +import GCompris 1.0 + +ProgressBar { + id: progressbar + height: progressbarText.height + width: bar.width + + property bool displayText: true + property string message + + background: Rectangle { + height: progressbar.height + width: progressbar.width + } + contentItem: Item { + implicitWidth: 200 + implicitHeight: 4 + + Rectangle { + width: progressbar.visualPosition * parent.width + height: parent.height + radius: 2 + color: "lightblue" + } + GCText { + id: progressbarText + anchors.centerIn: parent + visible: displayText + fontSize: mediumSize + font.bold: true + color: "black" + text: progressbar.message + } + } +} diff --git a/src/core/GCSlider.qml b/src/core/GCSlider.qml index 856f2a93f..8dd7f9610 100644 --- a/src/core/GCSlider.qml +++ b/src/core/GCSlider.qml @@ -1,62 +1,78 @@ /* GCompris - GCSlider.qml * * Copyright (C) 2018 Alexis Breton + * Copyright (C) 2014 Holger Kaelberer * * Authors: * Alexis Breton + * Holger Kaelberer * * 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 2.0 +import GCompris 1.0 /** * A Slider component with GCompris' style. * @ingroup components * - * Provides the "scrollEnabled" property to replace "wheelEnabled" - * that is only available with QtQuick.Controls > 1.6. If using - * QtQuick.Controls >= 1.6, please use the built-in "wheelEnabled" property - * * @inherit QtQuick.Controls.Slider */ Slider { + id: control - /** - * type:bool - * Set to false to disable changing the value by scrolling the mouse. - * Default is true. - * - * If false, the mouse scrolling is disabled while hovering the slider. - * - * Deprecated if using QtQuick.Controls >= 1.6 - */ - property bool scrollEnabled : true + focusPolicy: Qt.NoFocus + snapMode: Slider.SnapAlways + stepSize: 1 + handle: Rectangle { + x: control.leftPadding + control.visualPosition * (control.availableWidth - width) + y: control.topPadding + control.availableHeight / 2 - height / 2 + implicitWidth: 2 * radius + implicitHeight: 2 * radius + radius: 13 + color: control.pressed ? "#f0f0f0" : "#f6f6f6" + border.color: "#bdbebf" + } - style: GCSliderStyle {} - stepSize: 1.0 - tickmarksEnabled: true + background: Rectangle { + x: control.leftPadding + y: control.topPadding + control.availableHeight / 2 - height / 2 + radius: height / 2 + width: control.availableWidth + height: implicitHeight + implicitWidth: 250 * ApplicationInfo.ratio + implicitHeight: 8 * ApplicationInfo.ratio + anchors.verticalCenter: parent.verticalCenter + border.width: 1 + border.color: "#888" + gradient: Gradient { + GradientStop { color: "#bbb" ; position: 0 } + GradientStop { color: "#ccc" ; position: 0.6 } + GradientStop { color: "#ccc" ; position: 1 } + } - // Removes scrolling when hovering sliders if scrollEnabled = false - MouseArea { - anchors.fill: parent - enabled: !scrollEnabled - onWheel: {} - onPressed: mouse.accepted = false - onReleased: mouse.accepted = false + Rectangle { + width: control.visualPosition * parent.width + height: parent.height + border.color: Qt.darker("#f8d600", 1.2) + radius: height/2 + gradient: Gradient { + GradientStop { color: "#ffe85c"; position: 0 } + GradientStop { color: "#f8d600"; position: 1.4 } + } + } } - } diff --git a/src/core/GCSliderStyle.qml b/src/core/GCSliderStyle.qml deleted file mode 100644 index 11191ab28..000000000 --- a/src/core/GCSliderStyle.qml +++ /dev/null @@ -1,62 +0,0 @@ -/* GCompris - GCSliderStyle.qml - * - * Copyright (C) 2014 Holger Kaelberer - * - * Authors: - * Holger Kaelberer - * - * 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 - -/** - * Provides styling for GCompris sliders. - * @ingroup components - * - * @inherit QtQuick.Controls.Styles.SliderStyle - */ -SliderStyle { - groove: Item { - anchors.verticalCenter: parent.verticalCenter - implicitWidth: 250 * ApplicationInfo.ratio - implicitHeight: 8 * ApplicationInfo.ratio - Rectangle { - radius: height/2 - anchors.fill: parent - border.width: 1 - border.color: "#888" - gradient: Gradient { - GradientStop { color: "#bbb" ; position: 0 } - GradientStop { color: "#ccc" ; position: 0.6 } - GradientStop { color: "#ccc" ; position: 1 } - } - } - Item { - width: styleData.handlePosition - height: parent.height - Rectangle { - anchors.fill: parent - border.color: Qt.darker("#f8d600", 1.2) - radius: height/2 - gradient: Gradient { - GradientStop {color: "#ffe85c"; position: 0} - GradientStop {color: "#f8d600"; position: 1.4} - } - } - } - } -} diff --git a/src/core/Loading.qml b/src/core/Loading.qml index 63c672c52..cb0fa6d82 100644 --- a/src/core/Loading.qml +++ b/src/core/Loading.qml @@ -1,117 +1,117 @@ /* GCompris - Loading.qml * * Copyright (C) 2015 Holger Kaelberer * * Authors: * Holger Kaelberer * * 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 2.0 import GCompris 1.0 /** * A QML component presenting a loading overlay to the user * * Should be used whenever the application is performing heavy work, that * would lead to a freeze in the GUI. The interface is simple: *
    *
  • start() starts the loading overlay. As a result interaction with the ui * is no longer possible (via touch/mouse) except for key event handling. *
  • *
  • * stop() stops the loading overlay. A user must take care to call stop() * in all possible (also error-) cases or the application never leaves * the loading overlay again. *
  • *
* * There should be only one single Loading object anchored in the main window * as a sibling to the page stack. Activities are supposed to use this single * instance via the ActivityBase.loading property. * * Note that you can't use the loading animation to signal heavy ui changes that are * issued synchronously as the QML scene won't be updated fast enough to * even show the loading item. Therefore, to avoid freezing the ui, use... *
    *
  • * ... WorkerScript-s for ListModel based ui changes and *
  • *
  • * ... Component.incubateObject() for dynamic ui changes object creation *
  • *
*/ Item { id: root /** * type:bool * Whether the loading icon is active. */ property bool active: true /** * Start the loading overlay. */ function start() { visible = true; active = true; } /** * Stop the loading overlay. */ function stop() { active = false; visible = false; } anchors.fill: parent z: 10001 // should be the highest value in the whole scene visible: false MultiPointTouchArea { // Just to catch mouse events anchors.fill: parent } Rectangle { anchors.fill: parent opacity: 0.8 color: "grey" } Image { id: loadingImage source: "qrc:/gcompris/src/core/resource/loading.svg"; anchors.centerIn: parent sourceSize.width: 1024 width: 150 height: 150 opacity: 0.8 RotationAnimation on rotation { id: rotation running: root.active from: 0 to: 360 loops: Animation.Infinite duration: 1500 } } } diff --git a/src/core/VirtualKey.qml b/src/core/VirtualKey.qml index 33ecd5106..41728a3f3 100644 --- a/src/core/VirtualKey.qml +++ b/src/core/VirtualKey.qml @@ -1,114 +1,112 @@ /* GCompris - VirtualKey.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.5 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.0 Item { id: virtualKey property alias text: button.text property var modifiers: 0 /* Supposed to hold any active key modifiers * in a bitmask from the Qt namespace: * Qt.ShiftModifier/MetaModifier/ControlModifier. * 0 if none. */ property var specialKey: 0 /* Supposed to hold special key values from the * Qt namespace like Qt.Key_Shift/Key_Control/ * Key_Alt if VirtualKey is a special key. * 0 Otherwise */ signal pressed(var virtualKey); Button { id: button text: ((modifiers & Qt.ShiftModifier) && (shiftLabel !== undefined)) ? shiftLabel : label width: parent.width height: virtualKey.height - style: ButtonStyle { - background: Rectangle { - border.width: control.activeFocus ? 2 : 1 - border.color: "black" - radius: 4 - gradient: Gradient { - GradientStop { - position: 0; - color: (control.pressed - || ( virtualKey.specialKey == Qt.Key_Shift - && virtualKey.modifiers & Qt.ShiftModifier)) - ? "#ccc" : "#eee"; - } - GradientStop { - position: 1; - color: (control.pressed - || ( virtualKey.specialKey == Qt.Key_Shift - && virtualKey.modifiers & Qt.ShiftModifier)) - ? "#aaa" : "#ccc"; - } + background: Rectangle { + border.width: button.activeFocus ? 2 : 1 + border.color: "black" + radius: 4 + gradient: Gradient { + GradientStop { + position: 0; + color: (button.pressed + || ( virtualKey.specialKey == Qt.Key_Shift + && virtualKey.modifiers & Qt.ShiftModifier)) + ? "#ccc" : "#eee"; } - } - label: Item { - GCText { - //renderType: Text.NativeRendering - anchors.centerIn: parent - text: control.text - fontSize: 20 - font.bold: false - color: "black" - //antialiasing: true + GradientStop { + position: 1; + color: (button.pressed + || ( virtualKey.specialKey == Qt.Key_Shift + && virtualKey.modifiers & Qt.ShiftModifier)) + ? "#aaa" : "#ccc"; } } } + contentItem: Item { + GCText { + //renderType: Text.NativeRendering + anchors.centerIn: parent + text: button.text + fontSize: 20 + font.bold: false + color: "black" + //antialiasing: true + } + } + SequentialAnimation { id: anim PropertyAction { target: virtualKey; property: 'z'; value: 1 } NumberAnimation { target: button; properties: "scale"; to: 1.3; duration: 100; easing.type: Easing.OutCubic } NumberAnimation { target: button; properties: "scale"; to: 1.0; duration: 100; easing.type: Easing.OutCubic } PropertyAction { target: virtualKey; property: 'z'; value: 0 } } onClicked: { //console.log("### virtualKey.onClicked text=" + virtualKey.text // + " specialKey="+ virtualKey.specialKey // + " modifiers= "+ virtualKey.modifiers); anim.start() virtualKey.pressed(virtualKey); button.focus = false; } } DropShadow { anchors.fill: button cached: false horizontalOffset: 3 verticalOffset: 3 radius: 8.0 samples: 16 color: "#80000000" source: button scale: button.scale } }