diff --git a/gcompris.appdata.xml b/gcompris.appdata.xml index ab608ac68..1d939b684 100644 --- a/gcompris.appdata.xml +++ b/gcompris.appdata.xml @@ -1,682 +1,712 @@ gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop + gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop xxgcompris.desktopxx gcompris.desktop gcompris.desktop CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 + CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 xxCC0-1.0xx CC0-1.0 CC0-1.0 GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ + GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ xxGPL-3.0+xx GPL-3.0+ GPL-3.0+ GCompris Educational Game Joc educatiu GCompris GCompris-Lernspiel GCompris Educational Game Juego educativo GCompris GCompris, opettavainen peli Jeu éducatif GCompris Xogo educativo GCompris Gioco didattico GCompris GCompris 교육용 게임 Educatief spel GCompris GCompris leik-og-lær-spel Gra edukacyjna GCompris Jogo Educativo GCompris Jogo educacional GCompris + Joc educațional GCompris Výuková hra GCompris Izobraževalna igra GCompris GCompris pedagogiskt spel Освітня гра GCompris xxGCompris Educational Gamexx GCompris 教育游戏 GCompris 教育遊戲 Multi-Activity Educational game for children 2 to 10 Joc educatiu amb múltiples activitats per a nens dels 2 a 10 anys Lernspiel mit vielen Aktivitäten für Kinder von 2 bis 10 Jahren Multi-Activity Educational game for children 2 to 10 Juego educativo multiactividad para niños de 2 a 10 años Useampitoimintoinen opettavainen peli 2–10-vuotiaille lapsille Jeu éducatif multi-activités pour les enfants de 2 à 10 ans Xogo educativo con varias actividades para nenos de entre 2 e 10 anos. Gioco didattico multi-attività per bambini da 2 a 10 anni 2-10세 어린이를 위한 다양한 활동이 있는 교육용 게임 Educatief spel met meerdere activiteiten voor kinderen van 2 tot 10 Leik-og-lærspel med mange aktivitetar – for barn frå 2 til 10 år Gra edukacyjna z wieloma aktywnościami dla dzieci w wieku od 2 do 10 lat Jogo Educativo Multi-Actividades para crianças dos 2 aos 10 anos Jogo educacional com várias atividades para crianças de 2 a 10 anos + Joc educațional cu activități multiple pentru copii între 2 și 10 ani Viac-aktivitová výuková hra pre deti od 2 do 10 rokov Izobraževalna igra z več dejavnostmi za otroke med drugim in desetim letom starosti Pedagogiskt multiaktivitetsspel för barn från 2 till 10 år Набір освітніх ігор для дітей від 2 до 10 років xxMulti-Activity Educational game for children 2 to 10xx 为 2 到 10 岁儿童准备的多功能教育游戏 為 2-10 歲孩子設計的教育遊戲

GCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.

El GCompris és una suite de programari educatiu que consisteix de nombroses activitats per a nens i nenes entre els 2 i els 10 anys.

GCompris ist eine Lernsoftware, die verschiedene Aktivitäten für Kinder im Alter von 2 bis 10 Jahren anbietet.

GCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.

GCompris es una suite de software educativo que consta de numerosas actividades para niños de 2 a 10 años.

GCompris on useista toiminnoista koostuva opetusohjelmisto 2–10-vuotiaille lapsille.

GCompris est une suite éducative comprenant de nombreuses activités pour les enfants de 2 à 10 ans.

GCompris é unha colección de programas educativos composta por numerosas actividades para nenos de entre 2 e 10 anos.

GCompris è una raccolta di programmi didattici che comprende numerose attività per bambini da 2 a 10 anni.

GCompris는 2-10세 어린이를 위한 활동을 모아 놓은 교육용 소프트웨어 모음입니다.

GCompris is suite met educatieve software die bestaat uit vele activiteiten voor kinderen vanaf twee tot tien jaar.

GCompris er eit leik-og-lær spel med mange ulike aktivitetar, laga for ungar frå 2 til 10 år.

GCompris to pakiet oprogramowania edukacyjnego z licznymi aktywnościami dla dzieci w wieku od 2 do 10 lat.

O GCompris é um pacote de aplicações educativas que é composto por diversas actividades para as crianças dos 2 aos 10 anos de idade.

GCompris é uma suíte de software educacional que apresenta uma série de atividades para crianças com idade entre 2 e 10 anos.

+

GCompris este un pachet de software educațional compus din numeroase activități pentru copii între 2 și 10 ani.

GCompris je balík výukového softvéru zložený z rôznych aktivít pre deti od 2 do 10 rokov.

Program GCompris je visokokakovostna zbirka izobraževalnih dejavnosti, namenjenih otrokom med drugim in desetim letom starosti.

GCompris är en pedagogisk programsvit som består av en mängd olika aktiviteter för barn från 2 till 10 år gamla.

GCompris — комплекс навчального програмного забезпечення, що складається з багатьох вправ для дітей від 2 до 10 років.

xxGCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.xx

GCompris 是一款教育套件,由针对 2 到 10 岁孩子的许多活动组成。

GCompris 是一套教育軟體的集合,它提供適合兩歲到十歲兒童各種不同的活動。

Some of the activities are game orientated, but nonetheless still educational.

Algunes de les activitats estan orientades a joc, però sempre són educatives.

Některé aktivity jsou v podobě her, ale přesto jsou poučné.

Einige Aktivitäten sind eher spielerisch, aber immer lehrreich.

Some of the activities are game orientated, but nonetheless still educational.

Algunas de las actividades son juegos, aunque siguen siendo educativas.

Jotkin tehtävistä ovat pelimäisiä mutta silti opettavaisia.

Certaines activités sont ludiques mais ont toujours un intérêt pédagogique.

Algunhas das actividades están orientadas ao xogo, pero son aínda así educativas.

Alcune delle attività sono orientate al gioco, ma comunque didattiche.

일부 활동은 기능성 게임을 지향합니다.

Sommige activiteiten zijn spelletjes, maar toch leerzaam.

Nokre av aktivitetane er mest som spel, men er likevel lærerike.

Niektóre z aktywności są bardziej grą, lecz nadal bardzo pouczającą.

Algumas das actividades são apresentadas como jogos, mas são educativas à mesma.

Algumas das atividades são de orientação lúdica, mas mesmo assim ainda educacional.

+

Unele activități sunt orientate pe joacă, dar totuși educative.

Niektoré z aktivít sú herne orientované, ale stále výukové.

Nekatere dejavnosti so bolj podobne igram, vendar še vedno poučne.

Vissa av aktiviteterna är spelorienterade, men trots det ändå pedagogiska.

Деякі із вправ є суто ігровими, але містять і елементи навчання.

xxSome of the activities are game orientated, but nonetheless still educational.xx

一些活动是游戏导向的,不过尽管如此仍是教育性的。

有些活動是遊戲性質,但還是有很棒的教育意義。

Below you can find a list of categories with some of the activities available in that category.

A continuació trobareu una llista de les categories amb algunes de les activitats disponibles en cadascuna.

Sie können Aktivitäten aus folgenden Bereichen in GCompris finden:

Below you can find a list of categories with some of the activities available in that category.

A continuación puede encontrar una lista de categorías con algunas de las actividades disponibles en cada una de ellas.

Alta löydät luettelot luokista sekä maininnan joistakin kuhunkin luokkaan kuuluvista toiminnoista.

Vous pouvez trouver ci-dessous une liste des catégories avec certaines de leurs activités.

A continuación atopará unha lista de categorías e mailas actividades dispoñíbeis en cada unha desas categorías.

Di seguito puoi trovare un elenco di categorie con alcune delle attività disponibili per categoria.

아래에서 분류 목록과 해당 분류의 활동을 찾을 수 있습니다.

Hieronder vindt u een lijst met categorieën met enige van de activiteiten beschikbaar in die categorie.

Nedanfor finn du ei oversikt over kategoriar, saman med nokre av aktivitetane i kvar kategori.

Poniżej znajdziesz wykaz kategorii z niektórymi aktywnościami dostępnymi w tej kategorii.

Em baixo, poderá encontrar uma lista de categorias, com algumas das actividades disponíveis para essa categoria.

Abaixo está relacionada uma lista de categorias com algumas das atividades disponíveis em cada uma delas.

+

Dedesubt puteți găsi o listă de categorii cu unele din activitățile disponibile în acea categorie.

Tu môžete nájsť zoznam kategórií s niektorými aktivitami dostupnými v kategórii.

Spodaj lahko najdete seznam kategorij in nekaj dejavnosti, ki so na voljo v tisti kategoriji.

Nedan finns en lista över kategorier med några av aktiviteterna tillgängliga i varje kategori.

Нижче наведено список категорій із деякими вправами у цих категоріях.

xxBelow you can find a list of categories with some of the activities available in that category.xx

下方您可以看到該類別的活動清單。

Currently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.

Actualment el GCompris ofereix més de 100 activitats i encara se'n desenvolupen més. El GCompris és programari lliure, el qual vol dir que podeu adaptar-lo a les vostres pròpies necessitats, millorar-lo i, el més important, compartir-lo amb nens de tot arreu.

Insgesamt beinhaltet GCompris mehr als 100 Aktivitäten und wird ständig weiterentwickelt. GCompris ist freie Software. Sie haben also die Möglichkeit, sie ihren Bedürfnissen anzupassen und zu verbessern, und gerne auch alle Kinder der Welt von Ihrer Arbeit profitieren zu lassen.

Currently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.

En la actualidad, GCompris ofrece más de 100 actividades y hay muchas más en desarrollo. GCompris es software libre, lo que significa que puede adaptarlo a sus necesidades, mejorarlo y, lo que es más importante, compartirlo con los niños de todo el mundo.

GComprisissa on nykyisellään yli sata toimintoa, ja lisää kehitetään. GCompris on vapaata ohjelmistoa, mikä tarkoittaa, että voit muokata sitä tarpeisiisi, parannella sitä ja mikä tärkeintä jakaa sitä lapsille kaikkialla.

Actuellement, GCompris contient plus de 100 activités et d'autres sont en cours de développement. GCompris est un logiciel libre, ce qui veut dire que vous pouvez l'adapter à vos propres besoins, le modifier et, le plus important, le partager avec des enfants où que ce soit.

Actualmente GCompris ofrece máis de 100 actividades e hai máis en progreso. GCompris é software libre, o que significa que pode adaptalo para axustalo ás súas necesidades, melloralo e, o máis importante, compartilo con nenos de todo o mundo.

Attualmente GCompris offre più di 100 attività e altre sono in fase di sviluppo. GCompris è software libero, che significa che puoi adattarlo alle tue necessità, migliorarlo e, cosa più importante, condividerlo con tutti i bambini.

GCompris는 100개 이상의 활동을 포함하고 있으며 더 많은 활동을 개발하고 있습니다. GCompris는 자유 소프트웨어이며, 필요에 따라서 활동을 개선할 수 있으며 전 세계의 사람들과 공유할 수 있습니다.

Op dit moment biedt GCompris meer dan 100 activiteiten en meer zijn er in ontwikkeling. GCompris is vrije software, dat betekent dat u het kunt aanpassen aan uw eigen behoeften, het verbeteren en, meest belangrijk, het met kinderen overal kun delen.

GCompris har over 100 aktivitetar, og fleire vert utvikla. GCompris er fri programvare. Det vil seia at du kan tilpassa spelet slik du ønskjer, forbetra det og dela det med andre ungar i heile verda.

Obecnie GCompris zapewnia ponad 100 aktywności, a jeszcze więcej jest opracowywanych. GCompris jest darmowym oprogramowaniem, co oznacza, że możesz je dopasować do własnych potrzeb, ulepszać je i co najważniejsze udostępniać je dzieciom.

De momento, o GCompris oferece mais de 100 actividades, estando ainda mais em desenvolvimento. O GCompris é uma aplicação de 'software' livre, o que significa que o poderá adaptar às suas próprias necessidades, melhorá-lo e, o mais importante, partilhá-lo com as crianças em todo o lado.

Atualmente o GCompris oferece mais de 100 atividades e outras mais estão sendo desenvolvidas. O GCompris é um software livre, o que significa que você pode adaptá-lo para as suas próprias necessidades, melhorá-lo e, mais importante, compartilhar com as crianças de todo o mundo.

+

Momentan GCompris oferă peste 100 de activități și multe altele sunt în lucru. GCompris este software liber, ceea ce înseamnă că poate fi adaptat nevoilor dumneavoastră, îmbunătățit, dar mai ales, partajat cu copii de pretutindeni.

Aktuálne GCompris ponúka vyše 100 aktivít a ďalšie sa vyvíjajú. GCompris je slobodný softvér, čo znamená, že si ho môžete prispôsobiť na vlastné potreby, zlepšiť, a hlavne zdieľať s deťmi všade.

GCompris trenutno ponuja več kot 100 dejavnosti, še več pa jih je v razvoju. GCompris je prosta programska oprema, kar pomeni, da jo lahko prilagodite po svoje, še pomembneje pa je, da jo lahko delite z otroki širom sveta.

För närvarande erbjuder GCompris över 100 aktiviteter och ännu fler håller på att utvecklas. GCompris är fri programvara, vilket betyder att man kan anpassa det till sina egna behov, och viktigast av allt, dela det med barn överallt.

У поточній версії GCompris понад 100 вправ. Розробники постійно працюють над новими вправами. GCompris є вільним програмним забезпеченням, це означає, що ви можете адаптувати програму до власних потреб, покращувати її і, що найважливіше, ділитися вашими творіннями із усіма дітьми.

xxCurrently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.xx

目前 GCompris 提供超過 100 個活動,還有更多正在開發中。GCompris 是一套自由軟體,表示您可以根據您自己的需求來改進,並分享給所有的孩子們。

http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png + http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png xxhttp://gcompris.net/screenshots-qt/middle/canal_lock.pngxx http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ + http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ xxhttp://gcompris.net/screenshots-qt/middle/xx http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png + http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png xxhttp://gcompris.net/screenshots-qt/middle/click_on_letter_up.pngxx http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png + http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png xxhttp://gcompris.net/screenshots-qt/middle/clockgame.pngxx http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png + http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png xxhttp://gcompris.net/screenshots-qt/middle/color_mix.pngxx http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png + http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png xxhttp://gcompris.net/screenshots-qt/middle/colors.pngxx http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png + http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png xxhttp://gcompris.net/screenshots-qt/middle/enumerate.pngxx http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png + http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png xxhttp://gcompris.net/screenshots-qt/middle/fifteen.pngxx http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png + http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png xxhttp://gcompris.net/screenshots-qt/middle/hexagon.pngxx http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png + http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png xxhttp://gcompris.net/screenshots-qt/middle/redraw_symmetrical.pngxx http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png + http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png xxhttp://gcompris.net/screenshots-qt/middle/scalesboard.pngxx http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png + http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png xxhttp://gcompris.net/screenshots-qt/middle/traffic.pngxx http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ + http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ xxhttp://gcompris.net/xx http://gcompris.net/ http://gcompris.net/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris + https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris xxhttps://bugs.kde.org/enter_bug.cgi?format=guided&product=gcomprisxx https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net + bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net xxbruno.coudoin@gcompris.netxx bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net
diff --git a/org.kde.gcompris.desktop b/org.kde.gcompris.desktop index 2707928b0..e1630baa1 100644 --- a/org.kde.gcompris.desktop +++ b/org.kde.gcompris.desktop @@ -1,85 +1,85 @@ [Desktop Entry] Name=GCompris Educational Game Name[ca]=Joc educatiu GCompris Name[cs]=Výuková hra GCompris Name[de]=GCompris-Lernspiel Name[en_GB]=GCompris Educational Game Name[es]=Juego educativo GCompris Name[fi]=GCompris, opettavainen peli Name[fr]=Jeu éducatif GCompris Name[gl]=Xogo educativo GCompris Name[it]=Gioco didattico GCompris Name[ko]=GCompris 교육용 게임 Name[nl]=Educatief spel GCompris Name[nn]=GCompris leik-og-lær-spel Name[pl]=Gra edukacyjna GCompris Name[pt]=Jogo Educativo GCompris Name[pt_BR]=Jogo educacional GCompris Name[ro]=Joc educațional GCompris Name[ru]=Набор обучающих игр GCompris Name[sk]=Výuková hra GCompris Name[sl]=Izobraževalna igra GCompris Name[sv]=GCompris pedagogiskt spel Name[tr]=GCompris Eğitici Oyun Name[uk]=Освітня гра GCompris Name[x-test]=xxGCompris Educational Gamexx Name[zh_CN]=GCompris 教育游戏 Name[zh_TW]=GCompris 教育遊戲 GenericName=Educational game GenericName[ca]=Joc educatiu GenericName[cs]=Výuková hra GenericName[de]=Lernspiel GenericName[en_GB]=Educational game GenericName[es]=Juego educativo GenericName[fi]=Opettavainen peli GenericName[fr]=Jeu éducatif GenericName[gl]=Xogo educativo GenericName[it]=Gioco didattico GenericName[ko]=교육용 게임 GenericName[nl]=Educatief spel GenericName[nn]=Leik-og-lær-spel GenericName[pl]=Gra edukacyjna GenericName[pt]=Jogo educativo GenericName[pt_BR]=Jogo educacional GenericName[ro]=Joc educațional GenericName[ru]=Обучающие игры GenericName[sk]=Výuková hra GenericName[sl]=Izobraževalna igra GenericName[sv]=Pedagogiskt spel GenericName[tr]=Eğitici oyun GenericName[uk]=Освітня гра GenericName[x-test]=xxEducational gamexx GenericName[zh_CN]=教育游戏 GenericName[zh_TW]=教育遊戲 Comment=Multi-Activity Educational game for children 2 to 10 Comment[ca]=Joc educatiu amb múltiples activitats per a nens dels 2 a 10 anys Comment[de]=Lernspiel mit vielen Aktivitäten für Kinder von 2 bis 10 Jahren Comment[en_GB]=Multi-Activity Educational game for children 2 to 10 Comment[es]=Juego educativo multiactividad para niños de 2 a 10 años Comment[fi]=Useampitoimintoinen opettavainen peli 2–10-vuotiaille lapsille Comment[fr]=Jeu éducatif multi-activités pour les enfants de 2 à 10 ans Comment[gl]=Xogo educativo con varias actividades para nenos de entre 2 e 10 anos. Comment[it]=Gioco didattico multi-attività per bambini da 2 a 10 anni Comment[ko]=2-10세 어린이를 위한 다양한 활동이 있는 교육용 게임 Comment[nl]=Educatief spel met meerdere activiteiten voor kinderen van 2 tot 10 Comment[nn]=Leik-og-lærspel med mange aktivitetar – for barn frå 2 til 10 år Comment[pl]=Gra edukacyjna z wieloma aktywnościami dla dzieci w wieku od 2 do 10 lat Comment[pt]=Jogo educativo multi-actividades para crianças dos 2 aos 10 anos Comment[pt_BR]=Jogo educacional com várias atividades para crianças de 2 a 10 anos -Comment[ro]=Joc educațional cu multe activități pentru copii de la 2 la 10 ani +Comment[ro]=Joc educațional cu activități multiple pentru copii între 2 și 10 ani Comment[ru]=Обучающие игры для детей от 2 до 10 лет Comment[sk]=Viac-aktivitová výuková hra pre deti od 2 do 10 rokov Comment[sl]=Izobraževalna igra z več dejavnostmi za otroke med drugim in desetim letom starosti Comment[sv]=Pedagogiskt multiaktivitetsspel för barn från 2 till 10 år Comment[tr]=2-10 yaş arası çocuklar için Çoklu Etkileşimli Eğitici oyun Comment[uk]=Набір освітніх ігор для дітей від 2 до 10 років Comment[x-test]=xxMulti-Activity Educational game for children 2 to 10xx Comment[zh_CN]=为 2 到 10 岁儿童准备的多功能教育游戏 Comment[zh_TW]=為 2-10 歲孩子設計的教育遊戲 Exec=gcompris-qt Icon=gcompris-qt Terminal=false Type=Application Categories=Education;Game;KidsGame; StartupNotify=true X-DocPath=gcompris/index.html diff --git a/platforms/sailfishOS/harbour-gcompris-qt.desktop b/platforms/sailfishOS/harbour-gcompris-qt.desktop index 0e4b9c157..247474d90 100644 --- a/platforms/sailfishOS/harbour-gcompris-qt.desktop +++ b/platforms/sailfishOS/harbour-gcompris-qt.desktop @@ -1,85 +1,85 @@ [Desktop Entry] Name=GCompris Educational Game Name[ca]=Joc educatiu GCompris Name[cs]=Výuková hra GCompris Name[de]=GCompris-Lernspiel Name[en_GB]=GCompris Educational Game Name[es]=Juego educativo GCompris Name[fi]=GCompris, opettavainen peli Name[fr]=Jeu éducatif GCompris Name[gl]=Xogo educativo GCompris Name[it]=Gioco didattico GCompris Name[ko]=GCompris 교육용 게임 Name[nl]=Educatief spel GCompris Name[nn]=GCompris leik-og-lær-spel Name[pl]=Gra edukacyjna GCompris Name[pt]=Jogo Educativo GCompris Name[pt_BR]=Jogo educacional GCompris Name[ro]=Joc educațional GCompris Name[ru]=Набор обучающих игр GCompris Name[sk]=Výuková hra GCompris Name[sl]=Izobraževalna igra GCompris Name[sv]=GCompris pedagogiskt spel Name[tr]=GCompris Eğitici Oyun Name[uk]=Освітня гра GCompris Name[x-test]=xxGCompris Educational Gamexx Name[zh_CN]=GCompris 教育游戏 Name[zh_TW]=GCompris 教育遊戲 GenericName=Educational game GenericName[ca]=Joc educatiu GenericName[cs]=Výuková hra GenericName[de]=Lernspiel GenericName[en_GB]=Educational game GenericName[es]=Juego educativo GenericName[fi]=Opettavainen peli GenericName[fr]=Jeu éducatif GenericName[gl]=Xogo educativo GenericName[it]=Gioco didattico GenericName[ko]=교육용 게임 GenericName[nl]=Educatief spel GenericName[nn]=Leik-og-lær-spel GenericName[pl]=Gra edukacyjna GenericName[pt]=Jogo educativo GenericName[pt_BR]=Jogo educacional GenericName[ro]=Joc educațional GenericName[ru]=Обучающие игры GenericName[sk]=Výuková hra GenericName[sl]=Izobraževalna igra GenericName[sv]=Pedagogiskt spel GenericName[tr]=Eğitici oyun GenericName[uk]=Освітня гра GenericName[x-test]=xxEducational gamexx GenericName[zh_CN]=教育游戏 GenericName[zh_TW]=教育遊戲 Comment=Multi-Activity Educational game for children 2 to 10 Comment[ca]=Joc educatiu amb múltiples activitats per a nens dels 2 a 10 anys Comment[de]=Lernspiel mit vielen Aktivitäten für Kinder von 2 bis 10 Jahren Comment[en_GB]=Multi-Activity Educational game for children 2 to 10 Comment[es]=Juego educativo multiactividad para niños de 2 a 10 años Comment[fi]=Useampitoimintoinen opettavainen peli 2–10-vuotiaille lapsille Comment[fr]=Jeu éducatif multi-activités pour les enfants de 2 à 10 ans Comment[gl]=Xogo educativo con varias actividades para nenos de entre 2 e 10 anos. Comment[it]=Gioco didattico multi-attività per bambini da 2 a 10 anni Comment[ko]=2-10세 어린이를 위한 다양한 활동이 있는 교육용 게임 Comment[nl]=Educatief spel met meerdere activiteiten voor kinderen van 2 tot 10 Comment[nn]=Leik-og-lærspel med mange aktivitetar – for barn frå 2 til 10 år Comment[pl]=Gra edukacyjna z wieloma aktywnościami dla dzieci w wieku od 2 do 10 lat Comment[pt]=Jogo educativo multi-actividades para crianças dos 2 aos 10 anos Comment[pt_BR]=Jogo educacional com várias atividades para crianças de 2 a 10 anos -Comment[ro]=Joc educațional cu multe activități pentru copii de la 2 la 10 ani +Comment[ro]=Joc educațional cu activități multiple pentru copii între 2 și 10 ani Comment[ru]=Обучающие игры для детей от 2 до 10 лет Comment[sk]=Viac-aktivitová výuková hra pre deti od 2 do 10 rokov Comment[sl]=Izobraževalna igra z več dejavnostmi za otroke med drugim in desetim letom starosti Comment[sv]=Pedagogiskt multiaktivitetsspel för barn från 2 till 10 år Comment[tr]=2-10 yaş arası çocuklar için Çoklu Etkileşimli Eğitici oyun Comment[uk]=Набір освітніх ігор для дітей від 2 до 10 років Comment[x-test]=xxMulti-Activity Educational game for children 2 to 10xx Comment[zh_CN]=为 2 到 10 岁儿童准备的多功能教育游戏 Comment[zh_TW]=為 2-10 歲孩子設計的教育遊戲 Exec=harbour-gcompris-qt Icon=harbour-gcompris-qt Terminal=false Type=Application Categories=Education;Game;KidsGame; StartupNotify=true X-Nemo-Application-Type=generic diff --git a/src/activities/activities.txt b/src/activities/activities.txt index a1bcf4027..71a5b9c1b 100644 --- a/src/activities/activities.txt +++ b/src/activities/activities.txt @@ -1,108 +1,112 @@ # The list of activities that will be loaded at GCompris start. # Keep it sorted advanced_colors algebra_by algebra_div algebra_minus algebra_plus algorithm align4 align4-2players alphabet-sequence babymatch babyshapes ballcatch braille_alphabets braille_fun canal_lock +chess +chess_2players +chess_partyend chronos clickanddraw clickgame click_on_letter click_on_letter_up clockgame color_mix color_mix_light colors details drawnumber enumerate erase erase_2clic erase_clic fifteen followline football geo-country geography gletters gnumch-equality gnumch-factors gnumch-inequality gnumch-multiples gnumch-primes guessnumber hanoi hanoi_real hexagon imageid imagename instruments intro_gravity leftright lightsoff louis-braille magic-hat-minus magic-hat-plus maze mazeinvisible mazerelative memory memory-enumerate memory-math-add memory-math-add-minus memory-math-add-minus-mult-div memory-math-add-minus-mult-div-tux memory-math-add-minus-tux memory-math-add-tux memory-math-div memory-math-div-tux memory-math-minus memory-math-minus-tux memory-math-mult memory-math-mult-div memory-math-mult-div-tux memory-math-mult-tux memory-sound memory-sound-tux memory-tux memory-wordnumber mining missing-letter money money_back money_back_cents money_cents mosaic numbers-odd-even penalty planegame readingh readingv redraw redraw_symmetrical renewable_energy reversecount scalesboard scalesboard_weight scalesboard_weight_avoirdupois simplepaint smallnumbers smallnumbers2 sudoku superbrain target tic_tac_toe tic_tac_toe_2players traffic +watercycle wordsgame diff --git a/src/activities/advanced_colors/AdvancedColors.qml b/src/activities/advanced_colors/AdvancedColors.qml index 007a63432..5088d8a1a 100644 --- a/src/activities/advanced_colors/AdvancedColors.qml +++ b/src/activities/advanced_colors/AdvancedColors.qml @@ -1,34 +1,32 @@ /* GCompris - AdvancedColors.qml * * Copyright (C) 2015 Bruno Coudoin * * Original activity in the Gtk+ version of GCompris by * Pascal Georges * * 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.1 import GCompris 1.0 import "../colors" import "advanced_colors.js" as Dataset FindIt { mode: "AdvancedColors" dataset: Dataset backgroundImg: "qrc:/gcompris/src/activities/menu/resource/background.svg" - itemWidth: 130 * ApplicationInfo.ratio - itemHeight: 130 * ApplicationInfo.ratio } diff --git a/src/activities/advanced_colors/advanced_colors.js b/src/activities/advanced_colors/advanced_colors.js index de4d2c4c9..fd6a37f71 100644 --- a/src/activities/advanced_colors/advanced_colors.js +++ b/src/activities/advanced_colors/advanced_colors.js @@ -1,707 +1,868 @@ /* GCompris * * Copyright (C) 2014 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 . */ var advanced_colors = [ [ // Level 1 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/coral_butterfly.svg", + //: Look at http://gcompris.net/wiki/Advanced_color_translation to see all the advanced colors + //: Color #FF7F50 "text": qsTr("Find the coral butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/navy_butterfly.svg", + //: Color #000080 "text": qsTr("Find the navy butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/corn_butterfly.svg", + //: Color #FBEC5D "text": qsTr("Find the corn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cobalt_butterfly.svg", + //: Color #0047AB "text": qsTr("Find the cobalt butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cyan_butterfly.svg", + //: Color #00FFFF "text": qsTr("Find the cyan butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/almond_butterfly.svg", + //: Color #AB784E "text": qsTr("Find the almond butterfly") } ], [ // Level 2 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sapphire_butterfly.svg", + //: Color #0F52BA "text": qsTr("Find the sapphire butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/corn_butterfly.svg", + //: Color #FBEC5D "text": qsTr("Find the corn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cobalt_butterfly.svg", + //: Color #0047AB "text": qsTr("Find the cobalt butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cyan_butterfly.svg", + //: Color #00FFFF "text": qsTr("Find the cyan butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/almond_butterfly.svg", + //: Color #AB784E "text": qsTr("Find the almond butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ruby_butterfly.svg", + //: Color #E0115F "text": qsTr("Find the ruby butterfly") } ], [ // Level 3 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sienna_butterfly.svg", + //: Color #882D17 "text": qsTr("Find the sienna butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sage_butterfly.svg", + //: Color #BCB88A "text": qsTr("Find the sage butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/salmon_butterfly.svg", + //: Color #FF8C69 "text": qsTr("Find the salmon butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sepia_butterfly.svg", + //: Color #704214 "text": qsTr("Find the sepia butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sulphur_butterfly.svg", + //: Color #E4BB25 "text": qsTr("Find the sulfur butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/tea_butterfly.svg", + //: Color #DB6D7B "text": qsTr("Find the tea butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/lime_butterfly.svg", + //: Color #BFFF00 "text": qsTr("Find the lime butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/turquoise_butterfly.svg", + //: Color #40E0D0 "text": qsTr("Find the turquoise butterfly") } ], [ // Level 4 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/absinthe_butterfly.svg", + //: Color #73B881 "text": qsTr("Find the absinthe butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mahogany_butterfly.svg", + //: Color #C04000 "text": qsTr("Find the mahogany butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aquamarine_butterfly.svg", + //: Color #7FFFD4 "text": qsTr("Find the aquamarine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/alabaster_butterfly.svg", + //: Color #F2F0E6 "text": qsTr("Find the alabaster butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/amber_butterfly.svg", + //: Color #FFBF00 "text": qsTr("Find the amber butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/amethyst_butterfly.svg", + //: Color #9966CC "text": qsTr("Find the amethyst butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/anise_butterfly.svg", + //: Color #F5EACC "text": qsTr("Find the anise butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/vermilion_butterfly.svg", + //: Color #E34234 "text": qsTr("Find the vermilion butterfly") } ], [ // Level 5 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ceruse_butterfly.svg", + //: Color #EEEBEB "text": qsTr("Find the ceruse butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fawn_butterfly.svg", + //: Color #E5AA70 "text": qsTr("Find the fawn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chartreuse_butterfly.svg", + //: Color #7FFF00 "text": qsTr("Find the chartreuse butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/emerald_butterfly.svg", + //: Color #50C878 "text": qsTr("Find the emerald butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fuchsia_butterfly.svg", + //: Color #CC397B "text": qsTr("Find the fuchsia butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ruby_butterfly.svg", + //: Color #E0115F "text": qsTr("Find the ruby butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/glaucous_butterfly.svg", + //: Color #6082B6 "text": qsTr("Find the glaucous butterfly") } ], [ // Level 6 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/auburn_butterfly.svg", + //: Color #A52A2A "text": qsTr("Find the auburn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/azure_butterfly.svg", + //: Color #458AC6 "text": qsTr("Find the azure butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/greyish-brown_butterfly.svg", + //: Color #9F7F58 "text": qsTr("Find the grayish brown butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/bistre_butterfly.svg", + //: Color #3D2B1F "text": qsTr("Find the bistre butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/crimson_butterfly.svg", + //: Color #DC143C "text": qsTr("Find the crimson butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/celadon_butterfly.svg", + //: Color #ACE1AF "text": qsTr("Find the celadon butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cerulean_butterfly.svg", + //: Color #007BA7 "text": qsTr("Find the cerulean butterfly") } ], [ // Level 7 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dove_butterfly.svg", + //: Color #8B8E8D "text": qsTr("Find the dove butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/garnet_butterfly.svg", + //: Color #943543 "text": qsTr("Find the garnet butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/indigo_butterfly.svg", + //: Color #4B0082 "text": qsTr("Find the indigo butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ivory_butterfly.svg", + //: Color #FFFFF0 "text": qsTr("Find the ivory butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/jade_butterfly.svg", + //: Color #00A86B "text": qsTr("Find the jade butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/lavender_butterfly.svg", + //: Color #B57EDC "text": qsTr("Find the lavender butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/lichen_butterfly.svg", + //: Color #9BC4AF "text": qsTr("Find the lichen butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/wine_butterfly.svg", + //: Color #722F37 "text": qsTr("Find the wine butterfly") } ], [ // Level 8 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/lilac_butterfly.svg", + //: Color #9955BB "text": qsTr("Find the lilac butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/magenta_butterfly.svg", + //: Color #FF00FF "text": qsTr("Find the magenta butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/malachite_butterfly.svg", + //: Color #0BDA51 "text": qsTr("Find the malachite butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/navy_butterfly.svg", + //: Color #000080 "text": qsTr("Find the navy butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/larch_butterfly.svg", + //: Color #D9C3AD "text": qsTr("Find the larch butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mimosa_butterfly.svg", + //: Color #EFC050 "text": qsTr("Find the mimosa butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ochre_butterfly.svg", + //: Color #CC7722 "text": qsTr("Find the ochre butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/olive_butterfly.svg", + //: Color #808000 "text": qsTr("Find the olive butterfly") } ], [ // Level 9 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/opaline_butterfly.svg", + //: Color #F7F9F4 "text": qsTr("Find the opaline butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/vermilion_butterfly.svg", + //: Color #E34234 "text": qsTr("Find the vermilion butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ultramarine_butterfly.svg", + //: Color #120A8F "text": qsTr("Find the ultramarine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mauve_butterfly.svg", + //: Color #E0B0FF "text": qsTr("Find the mauve butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/greyish_blue_butterfly.svg", + //: Color #91A3B0 "text": qsTr("Find the grayish blue butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/pistachio_butterfly.svg", + //: Color #93C572 "text": qsTr("Find the pistachio butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/platinum_butterfly.svg", + //: Color #AEADA5 "text": qsTr("Find the platinum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the purple butterfly") } ], [ // Level 10 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/plum_butterfly.svg", + //: Color #5C3960 "text": qsTr("Find the plum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/prussian_blue_butterfly.svg", + //: Color #003153 "text": qsTr("Find the prussian blue butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/rust_butterfly.svg", + //: Color #B7410E "text": qsTr("Find the rust butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/saffron_butterfly.svg", + //: Color #F4C430 "text": qsTr("Find the saffron butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/vanilla_butterfly.svg", + //: Color #F3E5AB "text": qsTr("Find the vanilla butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/veronese_butterfly.svg", + //: Color #40826D "text": qsTr("Find the veronese butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/verdigris_butterfly.svg", + //: Color #529371 "text": qsTr("Find the verdigris butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dark_purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the dark purple butterfly") } ], [ // Level 11 Now all the colors have been seen, propose here // a new random set of them { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/olive_butterfly.svg", + //: Color #808000 "text": qsTr("Find the olive butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/auburn_butterfly.svg", + //: Color #A52A2A "text": qsTr("Find the auburn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dark_purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the dark purple butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mauve_butterfly.svg", + //: Color #E0B0FF "text": qsTr("Find the mauve butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/azure_butterfly.svg", + //: Color #458AC6 "text": qsTr("Find the azure butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/plum_butterfly.svg", + //: Color #5C3960 "text": qsTr("Find the plum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sepia_butterfly.svg", + //: Color #704214 "text": qsTr("Find the sepia butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") } ], [ // Level 12 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cyan_butterfly.svg", + //: Color #00FFFF "text": qsTr("Find the cyan butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fawn_butterfly.svg", + //: Color #E5AA70 "text": qsTr("Find the fawn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mimosa_butterfly.svg", + //: Color #EFC050 "text": qsTr("Find the mimosa butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ochre_butterfly.svg", + //: Color #CC7722 "text": qsTr("Find the ochre butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/prussian_blue_butterfly.svg", + //: Color #003153 "text": qsTr("Find the prussian blue butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/rust_butterfly.svg", + //: Color #B7410E "text": qsTr("Find the rust butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/salmon_butterfly.svg", + //: Color #FF8C69 "text": qsTr("Find the salmon butterfly") } ], [ // Level 13 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/alabaster_butterfly.svg", + //: Color #F2F0E6 "text": qsTr("Find the alabaster butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/coral_butterfly.svg", + //: Color #FF7F50 "text": qsTr("Find the coral butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ivory_butterfly.svg", + //: Color #FFFFF0 "text": qsTr("Find the ivory butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/mauve_butterfly.svg", + //: Color #E0B0FF "text": qsTr("Find the mauve butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/plum_butterfly.svg", + //: Color #5C3960 "text": qsTr("Find the plum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/rust_butterfly.svg", + //: Color #B7410E "text": qsTr("Find the rust butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/vermilion_butterfly.svg", + //: Color #E34234 "text": qsTr("Find the vermilion butterfly") } ], [ // Level 14 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/navy_butterfly.svg", + //: Color #000080 "text": qsTr("Find the navy butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cerulean_butterfly.svg", + //: Color #007BA7 "text": qsTr("Find the cerulean butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/malachite_butterfly.svg", + //: Color #0BDA51 "text": qsTr("Find the malachite butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/opaline_butterfly.svg", + //: Color #F7F9F4 "text": qsTr("Find the opaline butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/wine_butterfly.svg", + //: Color #722F37 "text": qsTr("Find the wine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/pistachio_butterfly.svg", + //: Color #93C572 "text": qsTr("Find the pistachio butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") } ], [ // Level 15 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dark_purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the dark purple butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/opaline_butterfly.svg", + //: Color #F7F9F4 "text": qsTr("Find the opaline butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/wine_butterfly.svg", + //: Color #722F37 "text": qsTr("Find the wine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fawn_butterfly.svg", + //: Color #E5AA70 "text": qsTr("Find the fawn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/magenta_butterfly.svg", + //: Color #FF00FF "text": qsTr("Find the magenta butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/anise_butterfly.svg", + //: Color #F5EACC "text": qsTr("Find the anise butterfly") } ], [ // Level 16 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fawn_butterfly.svg", + //: Color #E5AA70 "text": qsTr("Find the fawn butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/platinum_butterfly.svg", + //: Color #AEADA5 "text": qsTr("Find the platinum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/indigo_butterfly.svg", + //: Color #4B0082 "text": qsTr("Find the indigo butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dove_butterfly.svg", + //: Color #8B8E8D "text": qsTr("Find the dove butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/crimson_butterfly.svg", + //: Color #DC143C "text": qsTr("Find the crimson butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/veronese_butterfly.svg", + //: Color #40826D "text": qsTr("Find the veronese butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/greyish-brown_butterfly.svg", + //: Color #9F7F58 "text": qsTr("Find the grayish brown butterfly") } ], [ // Level 17 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chestnut_butterfly.svg", + //: Color #954535 "text": qsTr("Find the chestnut butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/greyish_blue_butterfly.svg", + //: Color #91A3B0 "text": qsTr("Find the grayish blue butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/salmon_butterfly.svg", + //: Color #FF8C69 "text": qsTr("Find the salmon butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/amber_butterfly.svg", + //: Color #FFBF00 "text": qsTr("Find the amber butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/chartreuse_butterfly.svg", + //: Color #7FFF00 "text": qsTr("Find the chartreuse butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the purple butterfly") } ], [ // Level 18 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/plum_butterfly.svg", + //: Color #5C3960 "text": qsTr("Find the plum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ultramarine_butterfly.svg", + //: Color #120A8F "text": qsTr("Find the ultramarine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/greyish_blue_butterfly.svg", + //: Color #91A3B0 "text": qsTr("Find the grayish blue butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dark_purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the dark purple butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/lilac_butterfly.svg", + //: Color #9955BB "text": qsTr("Find the lilac butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aquamarine_butterfly.svg", + //: Color #7FFFD4 "text": qsTr("Find the aquamarine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/magenta_butterfly.svg", + //: Color #FF00FF "text": qsTr("Find the magenta butterfly") } ], [ // Level 19 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/anise_butterfly.svg", + //: Color #F5EACC "text": qsTr("Find the anise butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/cerulean_butterfly.svg", + //: Color #007BA7 "text": qsTr("Find the cerulean butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/ivory_butterfly.svg", + //: Color #FFFFF0 "text": qsTr("Find the ivory butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sepia_butterfly.svg", + //: Color #704214 "text": qsTr("Find the sepia butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/verdigris_butterfly.svg", + //: Color #529371 "text": qsTr("Find the verdigris butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/fuchsia_butterfly.svg", + //: Color #CC397B "text": qsTr("Find the fuchsia butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/plum_butterfly.svg", + //: Color #5C3960 "text": qsTr("Find the plum butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the purple butterfly") } ], [ // Level 20 { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/tea_butterfly.svg", + //: Color #DB6D7B "text": qsTr("Find the tea butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/dark_purple_butterfly.svg", + //: Color #32004A "text": qsTr("Find the dark purple butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sienna_butterfly.svg", + //: Color #882D17 "text": qsTr("Find the sienna butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/aubergine_butterfly.svg", + //: Color #614051 "text": qsTr("Find the aubergine butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/rust_butterfly.svg", + //: Color #B7410E "text": qsTr("Find the rust butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/pistachio_butterfly.svg", + //: Color #93C572 "text": qsTr("Find the pistachio butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/sapphire_butterfly.svg", + //: Color #0F52BA "text": qsTr("Find the sapphire butterfly") }, { "image": "qrc:/gcompris/src/activities/advanced_colors/resource/claret_butterfly.svg", + //: Color #7F1734 "text": qsTr("Find the claret butterfly") } ] ] function get() { return advanced_colors } diff --git a/tools/menus/chess_computer.qml b/src/activities/chess/ActivityInfo.qml similarity index 53% rename from tools/menus/chess_computer.qml rename to src/activities/chess/ActivityInfo.qml index 0da28e66f..4b0f57ea5 100644 --- a/tools/menus/chess_computer.qml +++ b/src/activities/chess/ActivityInfo.qml @@ -1,33 +1,34 @@ /* GCompris - ActivityInfo.qml * - * Copyright (C) 2015 Your Name + * Copyright (C) 2015 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 GCompris 1.0 ActivityInfo { - name: "chess_computer/ChessComputer.qml" - difficulty: 2 - icon: "chess_computer/chess_computer.svg" + name: "chess/Chess.qml" + difficulty: 6 + icon: "chess/chess.svg" author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" demo: false - title: qsTr("Learning chess") - description: qsTr("Play chess against the computer in a learning mode") + title: qsTr("Play chess against Tux") + description: "" + //intro: "play chess against Tux" goal: "" - prerequisite: qsTr("Mouse-manipulation") - manual: "" - credit: qsTr("The chess engine is from gnuchess.") - section: "/strategy/chess" + prerequisite: "" + manual: qsTr("In this activity you discover the chess game by playing against the computer. It displays the possible target position for any selected piece which helps the children understand how pieces moves. At first level the computer is fully random to give the most chances to the children. As level increases, better the computer plays.") + credit: qsTr("The chess engine is p4wn <https://github.com/douglasbagnall/p4wn>.") + section: "strategy" } diff --git a/src/activities/chess/CMakeLists.txt b/src/activities/chess/CMakeLists.txt new file mode 100644 index 000000000..7d6da5ce8 --- /dev/null +++ b/src/activities/chess/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/chess *.qml *.svg *.js resource/*) diff --git a/src/activities/chess/Chess.qml b/src/activities/chess/Chess.qml new file mode 100644 index 000000000..450814e7e --- /dev/null +++ b/src/activities/chess/Chess.qml @@ -0,0 +1,368 @@ +/* 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.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 +import GCompris 1.0 + +import "../../core" +import "." +import "chess.js" as Activity + +ActivityBase { + id: activity + + property bool twoPlayers: false + // difficultyByLevel means that at level 1 computer is bad better at last level + property bool difficultyByLevel: true + property variant 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.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 GCAudio audioEffects: activity.audioEffects + property alias background: background + property alias bar: bar + property alias bonus: bonus + property int cellSize: Math.min(background.width / (8 + 2), + background.height / (8 + 3)) + property variant 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 alias trigComputerMove: trigComputerMove + } + + onStart: { Activity.start(items) } + onStop: { Activity.stop() } + + // TODO Imprement a vertical layout + Grid { + anchors { + top: parent.top + topMargin: items.cellSize / 2 + leftMargin: 10 * ApplicationInfo.ratio + rightMargin: 10 * ApplicationInfo.ratio + } + columns: 3 + rows: 1 + width: background.width + spacing: 10 + horizontalItemAlignment: Grid.AlignHCenter + verticalItemAlignment: Grid.AlignVCenter + + Column { + id: controls + spacing: 10 + anchors { + leftMargin: 10 + rightMargin: 10 + } + width: Math.max(undo.width * 1.2, + Math.min( + (background.width * 0.9 - undo.width - chessboard.width), + (background.width - chessboard.width) / 2)) + + GCText { + color: "black" + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + fontSize: smallSize + text: items.message + horizontalAlignment: Text.AlignHCenter + wrapMode: TextEdit.WordWrap + } + + Button { + id: undo + anchors.horizontalCenter: parent.horizontalCenter + height: 30 * ApplicationInfo.ratio + text: qsTr("Undo"); + style: GCButtonStyle {} + onClicked: Activity.undo() + opacity: items.history.length > 0 ? 1 : 0 + Behavior on opacity { + PropertyAnimation { + easing.type: Easing.InQuad + duration: 200 + } + } + } + + Button { + id: redo + anchors.horizontalCenter: parent.horizontalCenter + height: 30 * ApplicationInfo.ratio + text: qsTr("Redo"); + style: GCButtonStyle {} + onClicked: Activity.redo() + opacity: items.redo_stack.length > 0 ? 1 : 0 + Behavior on opacity { + PropertyAnimation { + easing.type: Easing.InQuad + duration: 200 + } + } + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + height: 30 * ApplicationInfo.ratio + text: qsTr("Swap"); + style: GCButtonStyle {} + opacity: items.twoPlayer + onClicked: chessboard.swap() + } + } + + // The chessboard + GridView { + id: chessboard + cellWidth: items.cellSize + cellHeight: items.cellSize + width: items.cellSize * 8 + height: items.cellSize * 8 + interactive: false + keyNavigationWraps: true + model: 64 + layoutDirection: Qt.RightToLeft + delegate: square + rotation: 180 + + Component { + id: square + Rectangle { + color: index % 2 + (Math.floor(index / 8) % 2) == 1 ? + "#FFFFFF99" : '#FF9999FF'; + 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 * (7 - pos % 8) + spacing / 2 + y: items.cellSize * Math.floor(pos / 8) + spacing / 2 + width: items.cellSize - spacing + height: items.cellSize - spacing + 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 ? 'green' : 'transparent' + border.width: parent.acceptMove ? 5 : 0 + border.color: "black" + 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 * (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(piece.Drag.target) { + if(items.from != -1) { + Activity.moveTo(items.from, piece.Drag.target.pos) + } + } else { + var pos = parent.pos + // Force recalc of the old x,y position + parent.pos = 0 + pieces.getPieceAt(pos).move(pos) + } + } + } + } + + function moveTo(from, to) { + items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') + 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: 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 ? 0 : reload) } + onHelpClicked: { + displayDialog(dialogHelp) + } + onPreviousLevelClicked: Activity.previousLevel() + onNextLevelClicked: Activity.nextLevel() + onHomeClicked: activity.home() + onReloadClicked: Activity.initLevel() + } + + Bonus { + id: bonus + Component.onCompleted: win.connect(Activity.nextLevel) + } + } + +} diff --git a/src/activities/chess/Piece.qml b/src/activities/chess/Piece.qml new file mode 100644 index 000000000..f9ad25b10 --- /dev/null +++ b/src/activities/chess/Piece.qml @@ -0,0 +1,107 @@ +/* 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.1 + +Image { + id: piece + property int pos + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + Behavior on x { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + Behavior on y { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + z: 10 + + property string img + property bool acceptMove : false + property int newPos + // color = -1 if no piece, 0 is black and 1 is white + property int isWhite: img.length != 2 ? -1 : img[0] == 'w' ? 1 : 0 + + + SequentialAnimation { + id: hideAnim + NumberAnimation { + target: piece + property: "scale" + duration: 200 + to: 0 + } + PropertyAction { + target: piece + property: 'img' + value: "" + } + PropertyAction { + target: piece + property: 'pos' + value: piece.newPos + } + PropertyAction { + target: piece + property: 'scale' + value: 1 + } + PropertyAction { + target: piece + property: 'z' + value: 2 + } + } + + SequentialAnimation { + id: promotionAnim + PauseAnimation { + duration: 200 + } + NumberAnimation { + target: piece + property: 'scale' + to: 0 + } + PropertyAction { + target: piece + property: 'img' + value: isWhite ? 'wq' : 'bq' + } + NumberAnimation { + target: piece + property: 'scale' + to: 1 + easing.type: Easing.OutElastic + } + } + + function hide(newPos) { + piece.newPos = newPos + hideAnim.start() + } + + function promotion() { + promotionAnim.start() + } + + function move(to) { + piece.newPos = to + piece.pos = to + piece.z = 2 + } +} + diff --git a/src/activities/chess/chess.js b/src/activities/chess/chess.js new file mode 100644 index 000000000..64f1d7805 --- /dev/null +++ b/src/activities/chess/chess.js @@ -0,0 +1,301 @@ +/* GCompris - chess.js + * + * 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 . + */ +.pragma library +.import QtQuick 2.0 as Quick +.import "qrc:/gcompris/src/core/core.js" as Core +.import "engine.js" as Engine + +var url = "qrc:/gcompris/src/activities/chess/resource/" + +var currentLevel +var numberOfLevel +var items +var state + +function start(items_) { + items = items_ + currentLevel = 0 + numberOfLevel = items.fen.length + initLevel() +} + +function stop() { +} + +function initLevel() { + items.bar.level = currentLevel + 1 + state = Engine.p4_fen2state(items.fen[currentLevel][1]) + items.from = -1 + items.gameOver = false + items.redo_stack = [] + refresh() + Engine.p4_prepare(state) + items.positions = 0 // Force a model reload + items.positions = simplifiedState(state['board']) + clearAcceptMove() +} + +function nextLevel() { + if(numberOfLevel <= ++currentLevel ) { + currentLevel = 0 + } + initLevel(); +} + +function previousLevel() { + if(--currentLevel < 0) { + currentLevel = numberOfLevel - 1 + } + initLevel(); +} + +function simplifiedState(state) { + var newState = new Array() + for(var i = state.length - 1; i >= 0; --i) { + if(state[i] != 16) { + var img = "" + switch(state[i]) { + case 2: + img = "wp" + break + case 3: + img = "bp" + break + case 4: + img = "wr" + break + case 5: + img = "br" + break + case 6: + img = "wn" + break + case 7: + img = "bn" + break + case 8: + img = "wb" + break + case 9: + img = "bb" + break + case 10: + img = "wk" + break + case 11: + img = "bk" + break + case 12: + img = "wq" + break + case 13: + img = "bq" + break + default: + break + } + newState.push( + { + 'pos': engineToViewPos(i), + 'img': img + }) + } + } + return newState +} + +function updateMessage(move) { + items.gameOver = false + items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") + if(!move) + return + if((move.flags & (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) + == (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) { + items.message = items.blackTurn ? qsTr("Black mates") : qsTr("White mates") + items.gameOver = true + } else if((move.flags & Engine.P4_MOVE_FLAG_MATE) == Engine.P4_MOVE_FLAG_MATE) { + items.message = qsTr("Drawn game") + items.gameOver = true + } else if((move.flags & Engine.P4_MOVE_FLAG_CHECK) == Engine.P4_MOVE_FLAG_CHECK) { + items.message = items.blackTurn ? qsTr("Black checks") : qsTr("White checks") + } else if(move.flags == Engine.P4_MOVE_ILLEGAL) { + items.message = qsTr("Invalid, your king may be in check") + } +} + +function refresh(move) { + items.blackTurn = state.to_play // 0=w 1=b + items.history = state.history + updateMessage(move) +} + +// Convert view position (QML) to the chess engine coordinate +// +// The engine manages coordinate into a 120 element array, which is conceptually +// a 10x12 board, with the 8x8 board placed at the centre, thus: +// + 0123456789 +// 0 ########## +// 10 ########## +// 20 #RNBQKBNR# +// 30 #PPPPPPPP# +// 40 #........# +// 50 #........# +// 60 #........# +// 70 #........# +// 80 #pppppppp# +// 90 #rnbqkbnr# +//100 ########## +//110 ########## +// +// In QML each cell is in the regular range [0-63] +// +function viewPosToEngine(pos) { + return (Math.floor(pos / 8) + 2) * 10 + pos % 8 + 1 +} + +// Convert chess engine coordinate to view position (QML) +function engineToViewPos(pos) { + var newpos = pos - 21 - (Math.floor((pos - 20) / 10) * 2) + return newpos +} + +// move is the result from the engine move +function visibleMove(move, from, to) { + items.pieces.moveTo(from, to) + // Castle move + if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_KING) + items.pieces.moveTo(from + 3, to - 1) + else if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_QUEEN) + items.pieces.moveTo(from - 4, to + 1) + else if(items.pieces.getPieceAt(to).img == 'wp' && to > 55) + items.pieces.promotion(to) + else if(items.pieces.getPieceAt(to).img == 'bp' && to < 8) + items.pieces.promotion(to) +} + +function computerMove() { + var computer = state.findmove(3) + var move = state.move(computer[0], computer[1]) + if(move.ok) { + visibleMove(move, engineToViewPos(computer[0]), engineToViewPos(computer[1])) + refresh(move) + } + return move +} + +function moveTo(from, to) { + var move = state.move(viewPosToEngine(from), viewPosToEngine(to)) + if(move.ok) { + visibleMove(move, from, to) + refresh(move) + clearAcceptMove() + items.redo_stack = [] + if(!items.twoPlayer) + items.trigComputerMove.start() + items.from = -1; + } else { + // Probably a check makes the move is invalid + updateMessage(move) + } +} + +function undo() { + var redo_stack = items.redo_stack + redo_stack.push(state.history[state.moveno - 1]) + state.jump_to_moveno(state.moveno - 1) + // In computer mode, the white always starts, take care + // of undo after a mate which requires us to revert on + // a white play + if(!items.twoPlayer && state.to_play != 0) { + redo_stack.push(state.history[state.moveno - 1]) + state.jump_to_moveno(state.moveno - 1) + } + items.redo_stack = redo_stack + refresh() + items.positions = [] // Force a model reload + items.positions = simplifiedState(state['board']) +} + +function moveByEngine(engineMove) { + var move = state.move(engineMove[0], engineMove[1]) + visibleMove(move, engineToViewPos(engineMove[0]), engineToViewPos(engineMove[1])) + refresh(move) +} + +function redo() { + var redo_stack = items.redo_stack + moveByEngine(items.redo_stack.pop()) + // In computer mode, the white always starts, take care + // of undo after a mate which requires us to revert on + // a white play + if(!items.twoPlayer && state.to_play != 0) { + items.redoTimer.moveByEngine(items.redo_stack.pop()) + } + + // Force refresh + items.redo_stack = [] + items.redo_stack = redo_stack +} + +// Random move depending on the level +function randomMove() { + if(!items.difficultyByLevel) { + computerMove() + return + } + // At level 2 we let the computer play 20% of the time + // and 80% of the time we make a random move. + if(Math.random() < currentLevel / (numberOfLevel - 1)) { + computerMove() + return + } + // Get all possible moves + var moves = Engine.p4_parse(state, state.to_play, 0, 0) + moves = Core.shuffle(moves) + var move = state.move(moves[0][1], moves[0][2]) + if(move.ok) { + visibleMove(move, engineToViewPos(moves[0][1]), engineToViewPos(moves[0][2])) + refresh(move) + } else { + // Bad move, should not happens + computerMove() + } +} + +// Clear all accept move marker from the chessboard +function clearAcceptMove() { + for(var i=0; i < items.positions.length; ++i) + items.squares.getSquareAt(i)['acceptMove'] = false +} + +// Highlight the possible moves for the piece at position 'from' +function showPossibleMoves(from) { + var result = Engine.p4_parse(state, state.to_play, 0, 0) + clearAcceptMove() + for(var i=0; i < result.length; ++i) { + if(viewPosToEngine(from) == result[i][1]) { + var pos = engineToViewPos(result[i][2]) + items.squares.getSquareAt(pos)['acceptMove'] = true + } + } +} + diff --git a/src/activities/chess/chess.svg b/src/activities/chess/chess.svg new file mode 100644 index 000000000..9d0833e07 --- /dev/null +++ b/src/activities/chess/chess.svg @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/chess/engine.js b/src/activities/chess/engine.js new file mode 100644 index 000000000..9a8a48d66 --- /dev/null +++ b/src/activities/chess/engine.js @@ -0,0 +1,1572 @@ +/* p4wn, AKA 5k chess - by Douglas Bagnall + * + * This code is in the public domain, or as close to it as various + * laws allow. No warranty; no restrictions. + * + * lives at http://p4wn.sf.net/ + */ + +/*Compatibility tricks: + * backwards for old MSIEs (to 5.5) + * sideways for seed command-line javascript.*/ +var p4_log; +if (this.imports !== undefined && + this.printerr !== undefined){//seed or gjs + p4_log = function(){ + var args = Array.prototype.slice.call(arguments); + printerr(args.join(', ')); + }; +} +else if (this.console === undefined){//MSIE + p4_log = function(){}; +} +else { + // disable the console + p4_log = function(){}; + //p4_log = function(){console.log.apply(console, arguments);}; +} + +/*MSIE Date.now backport */ +if (Date.now === undefined) + Date.now = function(){return (new Date).getTime();}; + +/* The pieces are stored as numbers between 2 and 13, inclusive. + * Empty squares are stored as 0, and off-board squares as 16. + * There is some bitwise logic to it: + * piece & 1 -> colour (white: 0, black: 1) + * piece & 2 -> single move piece (including pawn) + * if (piece & 2) == 0: + * piece & 4 -> row and column moves + * piece & 8 -> diagonal moves + */ +var P4_PAWN = 2, P4_ROOK = 4, P4_KNIGHT = 6, P4_BISHOP = 8, P4_QUEEN = 12, P4_KING = 10; +var P4_EDGE = 16; + +/* in order, even indices: , pawn, rook, knight, bishop, king, queen. Only the + * even indices are used.*/ +var P4_MOVES = [[], [], + [], [], + [1,10,-1,-10], [], + [21,19,12,8,-21,-19,-12,-8], [], + [11,9,-11,-9], [], + [1,10,11,9,-1,-10,-11,-9], [], + [1,10,11,9,-1,-10,-11,-9], [] + ]; + +/*P4_VALUES defines the relative value of various pieces. + * + * It follows the 1,3,3,5,9 pattern you learn as a kid, multiplied by + * 20 to give sub-pawn resolution to other factors, with bishops given + * a wee boost over knights. + */ +var P4_VALUES=[0, 0, //Piece values + 20, 20, //pawns + 100, 100, //rooks + 60, 60, //knights + 61, 61, //bishops + 8000, 8000,//kings + 180, 180, //queens + 0]; + +/* A score greater than P4_WIN indicates a king has been taken. It is + * less than the value of a king, in case someone finds a way to, say, + * sacrifice two queens in order to checkmate. + */ +var P4_KING_VALUE = P4_VALUES[10]; +var P4_WIN = P4_KING_VALUE >> 1; + +/* every move, a winning score decreases by this much */ +var P4_WIN_DECAY = 300; +var P4_WIN_NOW = P4_KING_VALUE - 250; + +/* P4_{MAX,MIN}_SCORE should be beyond any possible evaluated score */ + +var P4_MAX_SCORE = 9999; // extremes of evaluation range +var P4_MIN_SCORE = -P4_MAX_SCORE; + +/*initialised in p4_initialise_state */ +var P4_CENTRALISING_WEIGHTS; +var P4_BASE_PAWN_WEIGHTS; +var P4_KNIGHT_WEIGHTS; + +/*P4_DEBUG turns on debugging features */ +var P4_DEBUG = 0; +var P4_INITIAL_BOARD = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"; + +/*use javascript typed arrays rather than plain arrays + * (faster in some browsers, unsupported in others, possibly slower elsewhere) */ +var P4_USE_TYPED_ARRAYS = this.Int32Array !== undefined; + +var P4_PIECE_LUT = { /*for FEN, PGN interpretation */ + P: 2, + p: 3, + R: 4, + r: 5, + N: 6, + n: 7, + B: 8, + b: 9, + K: 10, + k: 11, + Q: 12, + q: 13 +}; + +var P4_ENCODE_LUT = ' PPRRNNBBKKQQ'; + + +function p4_alphabeta_treeclimber(state, count, colour, score, s, e, alpha, beta){ + var move = p4_make_move(state, s, e, P4_QUEEN); + var i; + var ncolour = 1 - colour; + var movelist = p4_parse(state, colour, move.ep, -score); + var movecount = movelist.length; + if(count){ + //branch nodes + var t; + for(i = 0; i < movecount; i++){ + var mv = movelist[i]; + var mscore = mv[0]; + var ms = mv[1]; + var me = mv[2]; + if (mscore > P4_WIN){ //we won! Don't look further. + alpha = P4_KING_VALUE; + break; + } + t = -p4_alphabeta_treeclimber(state, count - 1, ncolour, mscore, ms, me, + -beta, -alpha); + if (t > alpha){ + alpha = t; + } + if (alpha >= beta){ + break; + } + } + if (alpha < -P4_WIN_NOW && ! p4_check_check(state, colour)){ + /* Whatever we do, we lose the king. + * But if it is not check then this is stalemate, and the + * score doesn't apply. + */ + alpha = state.stalemate_scores[colour]; + } + if (alpha < -P4_WIN){ + /*make distant checkmate seem less bad */ + alpha += P4_WIN_DECAY; + } + } + else{ + //leaf nodes + while(beta > alpha && --movecount != -1){ + if(movelist[movecount][0] > alpha){ + alpha = movelist[movecount][0]; + } + } + } + p4_unmake_move(state, move); + return alpha; +} + + +/* p4_prepare() works out weightings for assessing various moves, + * favouring centralising moves early, for example. + * + * It is called before each tree search, not for each parse(), so it + * is OK for it to be a little bit slow. But that also means it drifts + * out of sync with the real board state, especially on deep searches. + */ + +function p4_prepare(state){ + var i, j, x, y, a; + var pieces = state.pieces = [[], []]; + /*convert state.moveno half move count to move cycle count */ + var moveno = state.moveno >> 1; + var board = state.board; + + /* high earliness_weight indicates a low move number. The formula + * should work above moveno == 50, but this is javascript. + */ + var earliness_weight = (moveno > 50) ? 0 : parseInt(6 * Math.exp(moveno * -0.07)); + var king_should_hide = moveno < 12; + var early = moveno < 5; + /* find the pieces, kings, and weigh material*/ + var kings = [0, 0]; + var material = [0, 0]; + var best_pieces = [0, 0]; + for(i = 20; i < 100; i++){ + a = board[i]; + var piece = a & 14; + var colour = a & 1; + if(piece){ + pieces[colour].push([a, i]); + if (piece == P4_KING){ + kings[colour] = i; + } + else{ + material[colour] += P4_VALUES[piece]; + best_pieces[colour] = Math.max(best_pieces[colour], P4_VALUES[piece]); + } + } + } + + /*does a draw seem likely soon?*/ + var draw_likely = (state.draw_timeout > 90 || state.current_repetitions >= 2); + if (draw_likely) + p4_log("draw likely", state.current_repetitions, state.draw_timeout); + state.values = [[], []]; + var qvalue = P4_VALUES[P4_QUEEN]; /*used as ballast in various ratios*/ + var material_sum = material[0] + material[1] + 2 * qvalue; + var wmul = 2 * (material[1] + qvalue) / material_sum; + var bmul = 2 * (material[0] + qvalue) / material_sum; + var multipliers = [wmul, bmul]; + var emptiness = 4 * P4_QUEEN / material_sum; + state.stalemate_scores = [parseInt(0.5 + (wmul - 1) * 2 * qvalue), + parseInt(0.5 + (bmul - 1) * 2 * qvalue)]; + //p4_log("value multipliers (W, B):", wmul, bmul, + // "stalemate scores", state.stalemate_scores); + for (i = 0; i < P4_VALUES.length; i++){ + var v = P4_VALUES[i]; + if (v < P4_WIN){//i.e., not king + state.values[0][i] = parseInt(v * wmul + 0.5); + state.values[1][i] = parseInt(v * bmul + 0.5); + } + else { + state.values[0][i] = v; + state.values[1][i] = v; + } + } + /*used for pruning quiescence search */ + state.best_pieces = [parseInt(best_pieces[0] * wmul + 0.5), + parseInt(best_pieces[1] * bmul + 0.5)]; + + var kx = [kings[0] % 10, kings[1] % 10]; + var ky = [parseInt(kings[0] / 10), parseInt(kings[1] / 10)]; + + /* find the frontmost pawns in each file */ + var pawn_cols = [[], []]; + for (y = 3; y < 9; y++){ + for (x = 1; x < 9; x++){ + i = y * 10 + x; + a = board[i]; + if ((a & 14) != P4_PAWN) + continue; + if ((a & 1) == 0){ + pawn_cols[0][x] = y; + } + else if (pawn_cols[1][x] === undefined){ + pawn_cols[1][x] = y; + } + } + } + var target_king = (moveno >= 20 || material_sum < 5 * qvalue); + var weights = state.weights; + + for (y = 2; y < 10; y++){ + for (x = 1; x < 9; x++){ + i = y * 10 + x; + var early_centre = P4_CENTRALISING_WEIGHTS[i] * earliness_weight; + var plateau = P4_KNIGHT_WEIGHTS[i]; + for (var c = 0; c < 2; c++){ + var dx = Math.abs(kx[1 - c] - x); + var dy = Math.abs(ky[1 - c] - y); + var our_dx = Math.abs(kx[c] - x); + var our_dy = Math.abs(ky[c] - y); + + var d = Math.max(Math.sqrt(dx * dx + dy * dy), 1) + 1; + var mul = multipliers[c]; /*(mul < 1) <==> we're winning*/ + var mul3 = mul * mul * mul; + var at_home = y == 2 + c * 7; + var pawn_home = y == 3 + c * 5; + var row4 = y == 5 + c; + var promotion_row = y == 9 - c * 7; + var get_out = (early && at_home) * -5; + + var knight = parseInt(early_centre * 0.3) + 2 * plateau + get_out; + var rook = parseInt(early_centre * 0.3); + var bishop = parseInt(early_centre * 0.6) + plateau + get_out; + if (at_home){ + rook += (x == 4 || x == 5) * (earliness_weight + ! target_king); + rook += (x == 1 || x == 8) * (moveno > 10 && moveno < 20) * -3; + rook += (x == 2 || x == 7) * (moveno > 10 && moveno < 20) * -1; + } + + /*Queen wants to stay home early, then jump right in*/ + /*keep kings back on home row for a while*/ + var queen = parseInt(plateau * 0.5 + early_centre * (0.5 - early)); + var king = (king_should_hide && at_home) * 2 * earliness_weight; + + /*empty board means pawn advancement is more urgent*/ + var get_on_with_it = Math.max(emptiness * 2, 1); + var pawn = get_on_with_it * P4_BASE_PAWN_WEIGHTS[c ? 119 - i : i]; + if (early){ + /* Early pawn weights are slightly randomised, so each game is different. + */ + if (y >= 4 && y <= 7){ + var boost = 1 + 3 * (y == 5 || y == 6); + pawn += parseInt((boost + p4_random_int(state, 4)) * 0.1 * + early_centre); + } + if (x == 4 || x == 5){ + //discourage middle pawns from waiting at home + pawn -= 3 * pawn_home; + pawn += 3 * row4; + } + } + /*pawn promotion row is weighted as a queen minus a pawn.*/ + if (promotion_row) + pawn += state.values[c][P4_QUEEN] - state.values[c][P4_PAWN]; + + /*pawns in front of a castled king should stay there*/ + pawn += 4 * (y == 3 && ky[c] == 2 && Math.abs(our_dx) < 2 && + kx[c] != 5 && x != 4 && x != 5); + /*passed pawns (having no opposing pawn in front) are encouraged. */ + var cols = pawn_cols[1 - c]; + if (cols[x] == undefined || + (c == 0 && cols[x] < y) || + (c == 1 && cols[x] > y)) + pawn += 2; + + /* After a while, start going for opposite king. Just + * attract pieces into the area so they can mill about in + * the area, waiting for an opportunity. + * + * As prepare is only called at the beginning of each tree + * search, the king could wander out of the targetted area + * in deep searches. But that's OK. Heuristics are + * heuristics. + */ + if (target_king){ + knight += 2 * parseInt(8 * mul / d); + rook += 2 * ((dx < 2) + (dy < 2)); + bishop += 3 * (Math.abs((dx - dy)) < 2); + queen += 2 * parseInt(8 / d) + (dx * dy == 0) + (dx - dy == 0); + /* The losing king wants to stay in the middle, while + the winning king goes in for the kill.*/ + var king_centre_wt = 8 * emptiness * P4_CENTRALISING_WEIGHTS[i]; + king += parseInt(150 * emptiness / (mul3 * d) + king_centre_wt * mul3); + } + weights[P4_PAWN + c][i] = pawn; + weights[P4_KNIGHT + c][i] = knight; + weights[P4_ROOK + c][i] = rook; + weights[P4_BISHOP + c][i] = bishop; + weights[P4_QUEEN + c][i] = queen; + weights[P4_KING + c][i] = king; + + if (draw_likely && mul < 1){ + /*The winning side wants to avoid draw, so adds jitter to its weights.*/ + var range = 3 / mul3; + for (j = 2 + c; j < 14; j += 2){ + weights[j][i] += p4_random_int(state, range); + } + } + } + } + } + state.prepared = true; +} + +function p4_maybe_prepare(state){ + if (! state.prepared) + p4_prepare(state); +} + + +function p4_parse(state, colour, ep, score) { + var board = state.board; + var s, e; //start and end position + var E, a; //E=piece at end place, a= piece moving + var i, j; + var other_colour = 1 - colour; + var dir = (10 - 20 * colour); //dir= 10 for white, -10 for black + var movelist = []; + var captures = []; + var weight; + var pieces = state.pieces[colour]; + var castle_flags = (state.castles >> (colour * 2)) & 3; + var values = state.values[other_colour]; + var all_weights = state.weights; + for (j = pieces.length - 1; j >= 0; j--){ + s = pieces[j][1]; // board position + a = board[s]; //piece number + var weight_lut = all_weights[a]; + weight = score - weight_lut[s]; + a &= 14; + if(a > 2){ //non-pawns + var moves = P4_MOVES[a]; + if(a & 2){ + for(i = 0; i < 8; i++){ + e = s + moves[i]; + E = board[e]; + if(!E){ + movelist.push([weight + values[E] + weight_lut[e], s, e]); + } + else if((E&17)==other_colour){ + captures.push([weight + values[E] + weight_lut[e] + all_weights[E][e], s, e]); + } + } + if(a == P4_KING && castle_flags){ + if((castle_flags & 1) && + (board[s-1] + board[s-2] + board[s-3] == 0) && + p4_check_castling(board, s - 2,other_colour,dir,-1)){//Q side + movelist.push([weight + 12, s, s - 2]); //no analysis, just encouragement + } + if((castle_flags & 2) && (board[s+1]+board[s+2] == 0)&& + p4_check_castling(board, s, other_colour, dir, 1)){//K side + movelist.push([weight + 13, s, s + 2]); + } + } + } + else{//rook, bishop, queen + var mlen = moves.length; + for(i=0;i= 0; i--){ + var mv = movelist[i]; + var score = mv[0]; + s = mv[1]; + e = mv[2]; + if(! board[e]){ + var x = scores[s]; + x.score = Math.max(x.score, score); + } + } + /* moving out of a threat is worth considering, especially + * if it is a pawn and you are not.*/ + for(i = threats.length - 1; i >= 0; i--){ + var mv = threats[i]; + var x = scores[mv[2]]; + if (x !== undefined){ + var S = board[mv[1]]; + var r = (1 + x.piece > 3 + S < 4) * 0.01; + if (x.threatened < r) + x.threatened = r; + } + } + var pieces2 = []; + for (i = 20; i < 100; i++){ + p = scores[i]; + if (p !== undefined){ + p.score += p.threatened * our_values[p.piece]; + pieces2.push(p); + } + } + pieces2.sort(function(a, b){return a.score - b.score;}); + for (i = 0; i < pieces2.length; i++){ + p = pieces2[i]; + pieces[i] = [p.piece, p.pos]; + } + } +} + +function p4_findmove(state, level, colour, ep){ + p4_prepare(state); + p4_optimise_piece_list(state); + var board = state.board; + if (arguments.length == 2){ + colour = state.to_play; + ep = state.enpassant; + } + var movelist = p4_parse(state, colour, ep, 0); + var alpha = P4_MIN_SCORE; + var mv, t, i; + var bs = 0; + var be = 0; + + if (level <= 0){ + for (i = 0; i < movelist.length; i++){ + mv = movelist[i]; + if(movelist[i][0] > alpha){ + alpha = mv[0]; + bs = mv[1]; + be = mv[2]; + } + } + return [bs, be, alpha]; + } + + for(i = 0; i < movelist.length; i++){ + mv = movelist[i]; + var mscore = mv[0]; + var ms = mv[1]; + var me = mv[2]; + if (mscore > P4_WIN){ + p4_log("XXX taking king! it should never come to this"); + alpha = P4_KING_VALUE; + bs = ms; + be = me; + break; + } + t = -state.treeclimber(state, level - 1, 1 - colour, mscore, ms, me, + P4_MIN_SCORE, -alpha); + if (t > alpha){ + alpha = t; + bs = ms; + be = me; + } + } + if (alpha < -P4_WIN_NOW && ! p4_check_check(state, colour)){ + alpha = state.stalemate_scores[colour]; + } + return [bs, be, alpha]; +} + +/*p4_make_move changes the state and returns an object containing + * everything necesary to undo the change. + * + * p4_unmake_move uses the p4_make_move return value to restore the + * previous state. + */ + +function p4_make_move(state, s, e, promotion){ + var board = state.board; + var S = board[s]; + var E = board[e]; + board[e] = S; + board[s] = 0; + var piece = S & 14; + var moved_colour = S & 1; + var end_piece = S; /* can differ from S in queening*/ + //now some stuff to handle queening, castling + var rs = 0, re, rook; + var ep_taken = 0, ep_position; + var ep = 0; + if(piece == P4_PAWN){ + if((60 - e) * (60 - e) > 900){ + /*got to end; replace the pawn on board and in pieces cache.*/ + promotion |= moved_colour; + board[e] = promotion; + end_piece = promotion; + } + else if (((s ^ e) & 1) && E == 0){ + /*this is a diagonal move, but the end spot is empty, so we surmise enpassant */ + ep_position = e - 10 + 20 * moved_colour; + ep_taken = board[ep_position]; + board[ep_position] = 0; + } + else if ((s - e) * (s - e) == 400){ + /*delta is 20 --> two row jump at start*/ + ep = (s + e) >> 1; + } + } + else if (piece == P4_KING && ((s - e) * (s - e) == 4)){ //castling - move rook too + rs = s - 4 + (s < e) * 7; + re = (s + e) >> 1; //avg of s,e=rook's spot + rook = moved_colour + P4_ROOK; + board[rs] = 0; + board[re] = rook; + //piece_locations.push([rook, re]); + } + + var old_castle_state = state.castles; + if (old_castle_state){ + var mask = 0; + var shift = moved_colour * 2; + var side = moved_colour * 70; + var s2 = s - side; + var e2 = e + side; + //wipe both our sides if king moves + if (s2 == 25) + mask |= 3 << shift; + //wipe one side on any move from rook points + else if (s2 == 21) + mask |= 1 << shift; + else if (s2 == 28) + mask |= 2 << shift; + //or on any move *to* opposition corners + if (e2 == 91) + mask |= 4 >> shift; + else if (e2 == 98) + mask |= 8 >> shift; + state.castles &= ~mask; + } + + var old_pieces = state.pieces.concat(); + var our_pieces = old_pieces[moved_colour]; + var dest = state.pieces[moved_colour] = []; + for (var i = 0; i < our_pieces.length; i++){ + var x = our_pieces[i]; + var pp = x[0]; + var ps = x[1]; + if (ps != s && ps != rs){ + dest.push(x); + } + } + dest.push([end_piece, e]); + if (rook) + dest.push([rook, re]); + + if (E || ep_taken){ + var their_pieces = old_pieces[1 - moved_colour]; + dest = state.pieces[1 - moved_colour] = []; + var gone = ep_taken ? ep_position : e; + for (i = 0; i < their_pieces.length; i++){ + var x = their_pieces[i]; + if (x[1] != gone){ + dest.push(x); + } + } + } + + return { + /*some of these (e.g. rook) could be recalculated during + * unmake, possibly more cheaply. */ + s: s, + e: e, + S: S, + E: E, + ep: ep, + castles: old_castle_state, + rs: rs, + re: re, + rook: rook, + ep_position: ep_position, + ep_taken: ep_taken, + pieces: old_pieces + }; +} + +function p4_unmake_move(state, move){ + var board = state.board; + if (move.ep_position){ + board[move.ep_position] = move.ep_taken; + } + board[move.s] = move.S; + board[move.e] = move.E; + //move.piece_locations.length--; + if(move.rs){ + board[move.rs] = move.rook; + board[move.re] = 0; + //move.piece_locations.length--; + } + state.pieces = move.pieces; + state.castles = move.castles; +} + + +function p4_insufficient_material(state){ + var knights = false; + var bishops = undefined; + var i; + var board = state.board; + for(i = 20; i < 100; i++){ + var piece = board[i] & 14; + if(piece == 0 || piece == P4_KING){ + continue; + } + if (piece == P4_KNIGHT){ + /* only allow one knight of either colour, never with a bishop */ + if (knights || bishops !== undefined){ + return false; + } + knights = true; + } + else if (piece == P4_BISHOP){ + /*any number of bishops, but on only one colour square */ + var x = i & 1; + var y = parseInt(i / 10) & 1; + var parity = x ^ y; + if (knights){ + return false; + } + else if (bishops === undefined){ + bishops = parity; + } + else if (bishops != parity){ + return false; + } + } + else { + return false; + } + } + return true; +} + +/* p4_move(state, s, e, promotion) + * s, e are start and end positions + * + * promotion is the desired pawn promotion if the move gets a pawn to the other + * end. + * + * return value contains bitwise flags +*/ + +var P4_MOVE_FLAG_OK = 1; +var P4_MOVE_FLAG_CHECK = 2; +var P4_MOVE_FLAG_MATE = 4; +var P4_MOVE_FLAG_CAPTURE = 8; +var P4_MOVE_FLAG_CASTLE_KING = 16; +var P4_MOVE_FLAG_CASTLE_QUEEN = 32; +var P4_MOVE_FLAG_DRAW = 64; + +var P4_MOVE_ILLEGAL = 0; +var P4_MOVE_MISSED_MATE = P4_MOVE_FLAG_CHECK | P4_MOVE_FLAG_MATE; +var P4_MOVE_CHECKMATE = P4_MOVE_FLAG_OK | P4_MOVE_FLAG_CHECK | P4_MOVE_FLAG_MATE; +var P4_MOVE_STALEMATE = P4_MOVE_FLAG_OK | P4_MOVE_FLAG_MATE; + +function p4_move(state, s, e, promotion){ + var board = state.board; + var colour = state.to_play; + var other_colour = 1 - colour; + if (s != parseInt(s)){ + if (e === undefined){ + var mv = p4_interpret_movestring(state, s); + s = mv[0]; + e = mv[1]; + if (s == 0) + return {flags: P4_MOVE_ILLEGAL, ok: false}; + promotion = mv[2]; + } + else {/*assume two point strings: 'e2', 'e4'*/ + s = p4_destringify_point(s); + e = p4_destringify_point(e); + } + } + if (promotion === undefined) + promotion = P4_QUEEN; + var E=board[e]; + var S=board[s]; + + /*See if this move is even slightly legal, disregarding check. + */ + var i; + var legal = false; + p4_maybe_prepare(state); + var moves = p4_parse(state, colour, state.enpassant, 0); + for (i = 0; i < moves.length; i++){ + if (e == moves[i][2] && s == moves[i][1]){ + legal = true; + break; + } + } + if (! legal) { + return {flags: P4_MOVE_ILLEGAL, ok: false}; + } + + /*Try the move, and see what the response is.*/ + var changes = p4_make_move(state, s, e, promotion); + + /*is it check? */ + if (p4_check_check(state, colour)){ + p4_unmake_move(state, changes); + p4_log('in check', changes); + return {flags: P4_MOVE_ILLEGAL, ok: false, string: "in check!"}; + } + /*The move is known to be legal. We won't be undoing it.*/ + + var flags = P4_MOVE_FLAG_OK; + + state.enpassant = changes.ep; + state.history.push([s, e, promotion]); + + /*draw timeout: 50 moves without pawn move or capture is a draw */ + if (changes.E || changes.ep_position){ + state.draw_timeout = 0; + flags |= P4_MOVE_FLAG_CAPTURE; + } + else if ((S & 14) == P4_PAWN){ + state.draw_timeout = 0; + } + else{ + state.draw_timeout++; + } + if (changes.rs){ + flags |= (s > e) ? P4_MOVE_FLAG_CASTLE_QUEEN : P4_MOVE_FLAG_CASTLE_KING; + } + var shortfen = p4_state2fen(state, true); + var repetitions = (state.position_counts[shortfen] || 0) + 1; + state.position_counts[shortfen] = repetitions; + state.current_repetitions = repetitions; + if (state.draw_timeout > 100 || repetitions >= 3 || + p4_insufficient_material(state)){ + flags |= P4_MOVE_FLAG_DRAW; + } + state.moveno++; + state.to_play = other_colour; + + if (p4_check_check(state, other_colour)){ + flags |= P4_MOVE_FLAG_CHECK; + } + /* check for (stale|check)mate, by seeing if there is a move for + * the other side that doesn't result in check. (In other words, + * reduce the pseudo-legal-move list down to a legal-move list, + * and check it isn't empty). + * + * We don't need to p4_prepare because other colour pieces can't + * have moved (just disappeared) since previous call. Also, + * setting the promotion piece is unnecessary, because all + * promotions block check equally well. + */ + var is_mate = true; + var replies = p4_parse(state, other_colour, changes.ep, 0); + for (i = 0; i < replies.length; i++){ + var m = replies[i]; + var change2 = p4_make_move(state, m[1], m[2], P4_QUEEN); + var check = p4_check_check(state, other_colour); + p4_unmake_move(state, change2); + if (!check){ + is_mate = false; + break; + } + } + if (is_mate) + flags |= P4_MOVE_FLAG_MATE; + + var movestring = p4_move2string(state, s, e, S, promotion, flags, moves); + p4_log("successful move", s, e, movestring, flags); + state.prepared = false; + return { + flags: flags, + string: movestring, + ok: true + }; +} + + +function p4_move2string(state, s, e, S, promotion, flags, moves){ + var piece = S & 14; + var src, dest; + var mv, i; + var capture = flags & P4_MOVE_FLAG_CAPTURE; + + src = p4_stringify_point(s); + dest = p4_stringify_point(e); + if (piece == P4_PAWN){ + if (capture){ + mv = src.charAt(0) + 'x' + dest; + } + else + mv = dest; + if (e > 90 || e < 30){ //end row, queening + if (promotion === undefined) + promotion = P4_QUEEN; + mv += '=' + P4_ENCODE_LUT.charAt(promotion); + } + } + else if (piece == P4_KING && (s-e) * (s-e) == 4) { + if (e < s) + mv = 'O-O-O'; + else + mv = 'O-O'; + } + else { + var row_qualifier = ''; + var col_qualifier = ''; + var pstr = P4_ENCODE_LUT.charAt(S); + var sx = s % 10; + var sy = parseInt(s / 10); + + /* find any other pseudo-legal moves that would put the same + * piece in the same place, for which we'd need + * disambiguation. */ + var co_landers = []; + for (i = 0; i < moves.length; i++){ + var m = moves[i]; + if (e == m[2] && s != m[1] && state.board[m[1]] == S){ + co_landers.push(m[1]); + } + } + if (co_landers.length){ + for (i = 0; i < co_landers.length; i++){ + var c = co_landers[i]; + var cx = c % 10; + var cy = parseInt(c / 10); + if (cx == sx)/*same column, so qualify by row*/ + row_qualifier = src.charAt(1); + if (cy == sy) + col_qualifier = src.charAt(0); + } + if (row_qualifier == '' && col_qualifier == ''){ + /*no co-landers on the same rank or file, so one or the other will do. + * By convention, use the column (a-h) */ + col_qualifier = src.charAt(0); + } + } + mv = pstr + col_qualifier + row_qualifier + (capture ? 'x' : '') + dest; + } + if (flags & P4_MOVE_FLAG_CHECK){ + if (flags & P4_MOVE_FLAG_MATE) + mv += '#'; + else + mv += '+'; + } + else if (flags & P4_MOVE_FLAG_MATE) + mv += ' stalemate'; + return mv; +} + + +function p4_jump_to_moveno(state, moveno){ + p4_log('jumping to move', moveno); + if (moveno === undefined || moveno > state.moveno) + moveno = state.moveno; + else if (moveno < 0){ + moveno = state.moveno + moveno; + } + var state2 = p4_fen2state(state.beginning); + var i = 0; + while (state2.moveno < moveno){ + var m = state.history[i++]; + p4_move(state2, m[0], m[1], m[2]); + } + /* copy the replayed state across, not all that deeply, but + * enough to cover, eg, held references to board. */ + var attr, dest; + for (attr in state2){ + var src = state2[attr]; + if (attr instanceof Array){ + dest = state[attr]; + dest.length = 0; + for (i = 0; i < src.length; i++){ + dest[i] = src[i]; + } + } + else { + state[attr] = src; + } + } + state.prepared = false; +} + + +/* write a standard FEN notation + * http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation + * */ +function p4_state2fen(state, reduced){ + var piece_lut = ' PpRrNnBbKkQq'; + var board = state.board; + var fen = ''; + //fen does Y axis backwards, X axis forwards */ + for (var y = 9; y > 1; y--){ + var count = 0; + for (var x = 1; x < 9; x++){ + var piece = board[y * 10 + x]; + if (piece == 0) + count++; + else{ + if (count) + fen += count.toString(); + fen += piece_lut.charAt(piece); + count = 0; + } + } + if (count) + fen += count; + if (y > 2) + fen += '/'; + } + /*white or black */ + fen += ' ' + 'wb'.charAt(state.to_play) + ' '; + /*castling */ + if (state.castles){ + var lut = [2, 'K', 1, 'Q', 8, 'k', 4, 'q']; + for (var i = 0; i < 8; i += 2){ + if (state.castles & lut[i]){ + fen += lut[i + 1]; + } + } + } + else + fen += '-'; + /*enpassant */ + if (state.enpassant !== 0){ + fen += ' ' + p4_stringify_point(state.enpassant); + } + else + fen += ' -'; + if (reduced){ + /*if the 'reduced' flag is set, the move number and draw + *timeout are not added. This form is used to detect draws by + *3-fold repetition.*/ + return fen; + } + fen += ' ' + state.draw_timeout + ' '; + fen += (state.moveno >> 1) + 1; + return fen; +} + +function p4_stringify_point(p){ + var letters = " abcdefgh"; + var x = p % 10; + var y = (p - x) / 10 - 1; + return letters.charAt(x) + y; +} + +function p4_destringify_point(p){ + var x = parseInt(p.charAt(0), 19) - 9; //a-h <-> 10-18, base 19 + var y = parseInt(p.charAt(1)) + 1; + if (y >= 2 && y < 10 && x >= 1 && x < 9) + return y * 10 + x; + return undefined; +} + +/* read a standard FEN notation + * http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation + * */ +function p4_fen2state(fen, state){ + if (state === undefined) + state = p4_initialise_state(); + var board = state.board; + var fenbits = fen.split(' '); + var fen_board = fenbits[0]; + var fen_toplay = fenbits[1]; + var fen_castles = fenbits[2]; + var fen_enpassant = fenbits[3]; + var fen_timeout = fenbits[4]; + var fen_moveno = fenbits[5]; + if (fen_timeout === undefined) + fen_timeout = 0; + //fen does Y axis backwards, X axis forwards */ + var y = 90; + var x = 1; + var i; + for (var j = 0; j < fen_board.length; j++){ + var c = fen_board.charAt(j); + if (c == '/'){ + x = 1; + y -= 10; + if (y < 20) + break; + continue; + } + var piece = P4_PIECE_LUT[c]; + if (piece && x < 9){ + board[y + x] = piece; + x++; + } + else { + var end = Math.min(x + parseInt(c), 9); + for (; x < end; x++){ + board[y + x] = 0; + } + } + } + state.to_play = (fen_toplay.toLowerCase() == 'b') ? 1 : 0; + state.castles = 0; + for (i = 0; i < fen_castles.length; i++){ + var bit = {k: 8, q: 4, K: 2, Q: 1}[fen_castles.charAt(i)]; + state.castles |= (bit || 0); + } + state.enpassant = (fen_enpassant != '-') ? p4_destringify_point(fen_enpassant) : 0; + state.draw_timeout = parseInt(fen_timeout); + if (fen_moveno === undefined){ + /*have a guess based on entropy and pieces remaining*/ + var pieces = 0; + var mix = 0; + var p, q, r; + for (y = 20; y < 100; y+=10){ + for (x = 1; x < 9; x++){ + p = board[y + x] & 15; + pieces += (!!p); + if (x < 8){ + q = board[y + x + 1]; + mix += (!q) != (!p); + } + if (y < 90){ + q = board[y + x + 10]; + mix += (!q) != (!p); + } + } + } + fen_moveno = Math.max(1, parseInt((32 - pieces) * 1.3 + (4 - fen_castles.length) * 1.5 + ((mix - 16) / 5))); + //p4_log("pieces", pieces, "mix", mix, "estimate", fen_moveno); + } + state.moveno = 2 * (parseInt(fen_moveno) - 1) + state.to_play; + state.history = []; + state.beginning = fen; + state.prepared = false; + state.position_counts = {}; + /* Wrap external functions as methods. */ + state.move = function(s, e, promotion){ + return p4_move(this, s, e, promotion); + }; + state.findmove = function(level){ + return p4_findmove(this, level); + }; + state.jump_to_moveno = function(moveno){ + return p4_jump_to_moveno(this, moveno); + }; + return state; +} + +/* +Weights would all fit within an Int8Array *except* for the last row +for pawns, which is close to the queen value (180, max is 127). + +Int8Array seems slightly quicker in Chromium 18, no different in +Firefox 12. + +Int16Array is no faster, perhaps slower than Int32Array. + +Int32Array is marginally slower than plain arrays with Firefox 12, but +significantly quicker with Chromium. + */ + +var P4_ZEROS = []; +function p4_zero_array(){ + if (P4_USE_TYPED_ARRAYS) + return new Int32Array(120); + if (P4_ZEROS.length == 0){ + for(var i = 0; i < 120; i++){ + P4_ZEROS[i] = 0; + } + } + return P4_ZEROS.slice(); +} + +/* p4_initialise_state() creates the board and initialises weight + * arrays etc. Some of this is really only needs to be done once. + */ + +function p4_initialise_state(){ + var board = p4_zero_array(); + P4_CENTRALISING_WEIGHTS = p4_zero_array(); + P4_BASE_PAWN_WEIGHTS = p4_zero_array(); + P4_KNIGHT_WEIGHTS = p4_zero_array(); + for(var i = 0; i < 120; i++){ + var y = parseInt(i / 10); + var x = i % 10; + var dx = Math.abs(x - 4.5); + var dy = Math.abs(y - 5.5); + P4_CENTRALISING_WEIGHTS[i] = parseInt(6 - Math.pow((dx * dx + dy * dy) * 1.5, 0.6)); + //knights have a flat topped centre (bishops too, but less so). + P4_KNIGHT_WEIGHTS[i] = parseInt(((dx < 2) + (dy < 2) * 1.5) + + (dx < 3) + (dy < 3)) - 2; + P4_BASE_PAWN_WEIGHTS[i] = parseInt('000012347000'.charAt(y)); + if (y > 9 || y < 2 || x < 1 || x > 8) + board[i] = 16; + } + var weights = []; + for (i = 0; i < 14; i++){ + weights[i] = p4_zero_array(); + } + var state = { + board: board, + weights: weights, + history: [], + treeclimber: p4_alphabeta_treeclimber + }; + p4_random_seed(state, P4_DEBUG ? 1 : Date.now()); + return state; +} + +function p4_new_game(){ + return p4_fen2state(P4_INITIAL_BOARD); +} + +/*convert an arbitrary movestring into a pair of integers offsets into + * the board. The string might be in any of these forms: + * + * "d2-d4" "d2d4" "d4" -- moving a pawn + * + * "b1-c3" "b1c3" "Nc3" "N1c3" "Nbc3" "Nb1c3" -- moving a knight + * + * "b1xc3" "b1xc3" "Nxc3" -- moving a knight, also happens to capture. + * + * "O-O" "O-O-O" -- special cases for castling ("e1-c1", etc, also work) + * + * Note that for the "Nc3" (pgn) format, some knowledge of the board + * is necessary, so the state parameter is required. If it is + * undefined, the other forms will still work. + */ + +function p4_interpret_movestring(state, str){ + /* Ignore any irrelevant characters, then tokenise. + * + */ + var FAIL = [0, 0]; + var algebraic_re = /^\s*([RNBQK]?[a-h]?[1-8]?)[ :x-]*([a-h][1-8]?)(=[RNBQ])?[!?+#e.p]*\s*$/; + var castle_re = /^\s*([O0o]-[O0o](-[O0o])?)\s*$/; + var position_re = /^[a-h][1-8]$/; + + var m = algebraic_re.exec(str); + if (m == null){ + /*check for castling notation (O-O, O-O-O) */ + m = castle_re.exec(str); + if (m){ + s = 25 + state.to_play * 70; + if (m[2])/*queenside*/ + e = s - 2; + else + e = s + 2; + } + else + return FAIL; + } + var src = m[1]; + var dest = m[2]; + var queen = m[3]; + var s, e, q; + var moves, i; + if (src == '' || src == undefined){ + /* a single coordinate pawn move */ + e = p4_destringify_point(dest); + s = p4_find_source_point(state, e, 'P' + dest.charAt(0)); + } + else if (/^[RNBQK]/.test(src)){ + /*pgn format*/ + e = p4_destringify_point(dest); + s = p4_find_source_point(state, e, src); + } + else if (position_re.test(src) && position_re.test(dest)){ + s = p4_destringify_point(src); + e = p4_destringify_point(dest); + } + else if (/^[a-h]$/.test(src)){ + e = p4_destringify_point(dest); + s = p4_find_source_point(state, e, 'P' + src); + } + if (s == 0) + return FAIL; + + if (queen){ + /* the chosen queen piece */ + q = P4_PIECE_LUT[queen.charAt(1)]; + } + return [s, e, q]; +} + + +function p4_find_source_point(state, e, str){ + var colour = state.to_play; + var piece = P4_PIECE_LUT[str.charAt(0)]; + piece |= colour; + var s, i; + + var row, column; + /* can be specified as Na, Na3, N3, and who knows, N3a? */ + for (i = 1; i < str.length; i++){ + var c = str.charAt(i); + if (/[a-h]/.test(c)){ + column = str.charCodeAt(i) - 96; + } + else if (/[1-8]/.test(c)){ + /*row goes 2 - 9 */ + row = 1 + parseInt(c); + } + } + var possibilities = []; + p4_prepare(state); + var moves = p4_parse(state, colour, + state.enpassant, 0); + for (i = 0; i < moves.length; i++){ + var mv = moves[i]; + if (e == mv[2]){ + s = mv[1]; + if (state.board[s] == piece && + (column === undefined || column == s % 10) && + (row === undefined || row == parseInt(s / 10)) + ){ + var change = p4_make_move(state, s, e, P4_QUEEN); + if (! p4_check_check(state, colour)) + possibilities.push(s); + p4_unmake_move(state, change); + } + } + } + p4_log("finding", str, "that goes to", e, "got", possibilities); + + if (possibilities.length == 0){ + return 0; + } + else if (possibilities.length > 1){ + p4_log("p4_find_source_point seems to have failed", + state, e, str, + possibilities); + } + return possibilities[0]; +} + + +/*random number generator based on + * http://burtleburtle.net/bob/rand/smallprng.html + */ +function p4_random_seed(state, seed){ + seed &= 0xffffffff; + state.rng = (P4_USE_TYPED_ARRAYS) ? new Uint32Array(4) : []; + state.rng[0] = 0xf1ea5eed; + state.rng[1] = seed; + state.rng[2] = seed; + state.rng[3] = seed; + for (var i = 0; i < 20; i++) + p4_random31(state); +} + +function p4_random31(state){ + var rng = state.rng; + var b = rng[1]; + var c = rng[2]; + /* These shifts amount to rotates. + * Note the three-fold right shift '>>>', meaning an unsigned shift. + * The 0xffffffff masks are needed to keep javascript to 32bit. (supposing + * untyped arrays). + */ + var e = rng[0] - ((b << 27) | (b >>> 5)); + rng[0] = b ^ ((c << 17) | (c >>> 15)); + rng[1] = (c + rng[3]) & 0xffffffff; + rng[2] = (rng[3] + e) & 0xffffffff; + rng[3] = (e + rng[0]) & 0xffffffff; + return rng[3] & 0x7fffffff; +} + +function p4_random_int(state, top){ + /* uniform integer in range [0 < n < top), supposing top < 2 ** 31 + * + * This method is slightly (probably pointlessly) more accurate + * than converting to 0-1 float, multiplying and truncating, and + * considerably more accurate than a simple modulus. + * Obviously it is a bit slower. + */ + /* mask becomes one less than the next highest power of 2 */ + var mask = top; + mask--; + mask |= mask >>> 1; + mask |= mask >>> 2; + mask |= mask >>> 4; + mask |= mask >>> 8; + mask |= mask >>> 16; + var r; + do{ + r = p4_random31(state) & mask; + } while (r >= top); + return r; +} diff --git a/src/activities/chess/resource/COPYRIGHT b/src/activities/chess/resource/COPYRIGHT new file mode 100644 index 000000000..3faf11601 --- /dev/null +++ b/src/activities/chess/resource/COPYRIGHT @@ -0,0 +1,2 @@ +Original images taken from openclipart: +https://openclipart.org/detail/24125/chess-symbols-set diff --git a/src/activities/chess/resource/README b/src/activities/chess/resource/README new file mode 100644 index 000000000..3d37cfdcd --- /dev/null +++ b/src/activities/chess/resource/README @@ -0,0 +1,2 @@ +backgroung taken from openclipart (public domain license): +https://openclipart.org/detail/222623/simple-scenery-2 diff --git a/src/activities/chess/resource/background.svg b/src/activities/chess/resource/background.svg new file mode 100644 index 000000000..5331a4de9 --- /dev/null +++ b/src/activities/chess/resource/background.svg @@ -0,0 +1,172 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/chess/resource/bb.svg b/src/activities/chess/resource/bb.svg new file mode 100644 index 000000000..8ee625a5f --- /dev/null +++ b/src/activities/chess/resource/bb.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/chess/resource/bk.svg b/src/activities/chess/resource/bk.svg new file mode 100644 index 000000000..36c0580d5 --- /dev/null +++ b/src/activities/chess/resource/bk.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/chess/resource/bn.svg b/src/activities/chess/resource/bn.svg new file mode 100644 index 000000000..2ead5616f --- /dev/null +++ b/src/activities/chess/resource/bn.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/chess/resource/bp.svg b/src/activities/chess/resource/bp.svg new file mode 100644 index 000000000..7c124bb13 --- /dev/null +++ b/src/activities/chess/resource/bp.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/activities/chess/resource/bq.svg b/src/activities/chess/resource/bq.svg new file mode 100644 index 000000000..82a94d027 --- /dev/null +++ b/src/activities/chess/resource/bq.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/chess/resource/br.svg b/src/activities/chess/resource/br.svg new file mode 100644 index 000000000..7c8284337 --- /dev/null +++ b/src/activities/chess/resource/br.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/chess/resource/wb.svg b/src/activities/chess/resource/wb.svg new file mode 100644 index 000000000..5bca41b42 --- /dev/null +++ b/src/activities/chess/resource/wb.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/src/activities/chess/resource/wk.svg b/src/activities/chess/resource/wk.svg new file mode 100644 index 000000000..4bc584e3d --- /dev/null +++ b/src/activities/chess/resource/wk.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/chess/resource/wn.svg b/src/activities/chess/resource/wn.svg new file mode 100644 index 000000000..658bc09bb --- /dev/null +++ b/src/activities/chess/resource/wn.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/chess/resource/wp.svg b/src/activities/chess/resource/wp.svg new file mode 100644 index 000000000..e69711b36 --- /dev/null +++ b/src/activities/chess/resource/wp.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/chess/resource/wq.svg b/src/activities/chess/resource/wq.svg new file mode 100644 index 000000000..9d77a1cdb --- /dev/null +++ b/src/activities/chess/resource/wq.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/chess/resource/wr.svg b/src/activities/chess/resource/wr.svg new file mode 100644 index 000000000..a73f29d1e --- /dev/null +++ b/src/activities/chess/resource/wr.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/tools/menus/chess_movelearn.qml b/src/activities/chess_2players/ActivityInfo.qml similarity index 59% rename from tools/menus/chess_movelearn.qml rename to src/activities/chess_2players/ActivityInfo.qml index d57bc45f8..20825973f 100644 --- a/tools/menus/chess_movelearn.qml +++ b/src/activities/chess_2players/ActivityInfo.qml @@ -1,33 +1,34 @@ /* GCompris - ActivityInfo.qml * - * Copyright (C) 2015 Your Name + * Copyright (C) 2015 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 GCompris 1.0 ActivityInfo { - name: "chess_movelearn/ChessMovelearn.qml" - difficulty: 2 - icon: "chess_movelearn/chess_movelearn.svg" + name: "chess_2players/Chess2Players.qml" + difficulty: 6 + icon: "chess_2players/chess_2players.svg" author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" demo: false - title: qsTr("Learning chess") - description: qsTr("Chess training. Catch the computer's pawns.") + title: qsTr("Play chess against your friend") + description: "" + //intro: "play chess against your friend" goal: "" - prerequisite: qsTr("Mouse-manipulation") - manual: "" - credit: qsTr("The chess engine is from gnuchess.") - section: "/strategy/chess" + prerequisite: "" + manual: qsTr("In this activity you discover the chess game by playing against a friend. It displays the possible target position for any selected piece which helps the children understand how pieces moves.") + credit: "" + section: "strategy" } diff --git a/src/activities/chess_2players/CMakeLists.txt b/src/activities/chess_2players/CMakeLists.txt new file mode 100644 index 000000000..da69421f7 --- /dev/null +++ b/src/activities/chess_2players/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/chess_2players *.qml *.svg) diff --git a/src/activities/advanced_colors/AdvancedColors.qml b/src/activities/chess_2players/Chess2Players.qml similarity index 63% copy from src/activities/advanced_colors/AdvancedColors.qml copy to src/activities/chess_2players/Chess2Players.qml index 007a63432..b5711f4d8 100644 --- a/src/activities/advanced_colors/AdvancedColors.qml +++ b/src/activities/chess_2players/Chess2Players.qml @@ -1,34 +1,29 @@ -/* GCompris - AdvancedColors.qml +/* GCompris - chess_2players.qml * * Copyright (C) 2015 Bruno Coudoin * - * Original activity in the Gtk+ version of GCompris by - * Pascal Georges + * 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.1 -import GCompris 1.0 -import "../colors" -import "advanced_colors.js" as Dataset +import "../../core" +import "../chess" -FindIt { - mode: "AdvancedColors" - dataset: Dataset - backgroundImg: "qrc:/gcompris/src/activities/menu/resource/background.svg" - itemWidth: 130 * ApplicationInfo.ratio - itemHeight: 130 * ApplicationInfo.ratio +Chess { + twoPlayers: true } diff --git a/tools/menus/resource/chess_computer.svg b/src/activities/chess_2players/chess_2players.svg similarity index 73% rename from tools/menus/resource/chess_computer.svg rename to src/activities/chess_2players/chess_2players.svg index 47a3a23f3..31d965eb6 100644 --- a/tools/menus/resource/chess_computer.svg +++ b/src/activities/chess_2players/chess_2players.svg @@ -1,201 +1,204 @@ + sodipodi:docname="chess.svg"> + inkscape:window-width="3200" + inkscape:window-height="1717" + inkscape:window-x="-8" + inkscape:window-y="0" + showgrid="false" + inkscape:window-maximized="1" /> image/svg+xml + + transform="translate(105.1162,-4.328315)" + style="stroke:none"> - + style="fill:#8e3737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m -91.587427,15.109586 36.205369,-1.325264 -2.946797,15.405497 1.928019,17.039929 -19.281378,1.789013 -15.388828,0 0.811559,-20.617955 z" + style="fill:#a36868;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.08291566px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccc" /> + d="m -56.876295,45.889722 32.828556,-1.374772 -1.190315,14.691694 1.181712,16.83938 -16.839381,1.683938 -13.885102,0 0,-20.207257 z" + style="fill:#a36868;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.02026463px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccc" /> + + + style="fill:#825119;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - - - - diff --git a/tools/menus/chess_partyend.qml b/src/activities/chess_partyend/ActivityInfo.qml similarity index 57% rename from tools/menus/chess_partyend.qml rename to src/activities/chess_partyend/ActivityInfo.qml index d288a44b2..90b6c0bba 100644 --- a/tools/menus/chess_partyend.qml +++ b/src/activities/chess_partyend/ActivityInfo.qml @@ -1,33 +1,34 @@ /* GCompris - ActivityInfo.qml * - * Copyright (C) 2015 Your Name + * Copyright (C) 2015 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 GCompris 1.0 ActivityInfo { - name: "chess_partyend/ChessPartyend.qml" - difficulty: 2 + name: "chess_partyend/ChessPartyEnd.qml" + difficulty: 6 icon: "chess_partyend/chess_partyend.svg" author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" demo: false - title: qsTr("Learning chess") - description: qsTr("Play the end of the chess game against the computer") + title: qsTr("End of chess game") + description: qsTr("Play the end of the chess game against Tux") + //intro: "Play the end of the chess game against Tux" goal: "" - prerequisite: qsTr("Mouse-manipulation") - manual: "" - credit: qsTr("The chess engine is from gnuchess.") - section: "/strategy/chess" + prerequisite: "" + manual: qsTr("In this activity you discover the chess game by playing only the end of a game. It displays the possible target position for any selected piece which helps the children understand how pieces moves.") + credit: qsTr("The chess engine is p4wn <https://github.com/douglasbagnall/p4wn>.") + section: "strategy" } diff --git a/src/activities/chess_partyend/CMakeLists.txt b/src/activities/chess_partyend/CMakeLists.txt new file mode 100644 index 000000000..b1e976245 --- /dev/null +++ b/src/activities/chess_partyend/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/chess_partyend *.qml *.svg) diff --git a/src/activities/chess_partyend/ChessPartyEnd.qml b/src/activities/chess_partyend/ChessPartyEnd.qml new file mode 100644 index 000000000..c0b0fce15 --- /dev/null +++ b/src/activities/chess_partyend/ChessPartyEnd.qml @@ -0,0 +1,46 @@ +/* GCompris - chess_2players.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.1 + +import "../../core" +import "../chess" + +Chess { + twoPlayers: false + difficultyByLevel: false + fen: [ + ["", "4k3/8/8/8/8/8/8/K4QQ1 w - -"], + ["", "1k6/8/8/8/8/8/8/K4RR1 w - -"], + ["", "8/8/8/1B6/1R6/8/8/k3K3 w - -"], + ["", "8/8/8/3N4/3N4/3B4/3B4/k3K3 w - -"], + ["checkmate in 1", "8/8/8/8/8/6K1/4Q3/6k1 w - - 21 61"], + ["mate in 1", "5k2/8/5K2/4Q3/5P2/8/8/8 w - - 3 61"], + ["zugzwang", "8/8/p1p5/1p5p/1P5p/8/PPP2K1p/4R1rk w - - 0 1"], + ["earlyish", "rnq1nrk1/pp3pbp/6p1/3p4/3P4/5N2/PP2BPPP/R1BQK2R w KQ -"], + ["checkmate in 2", "4kb2/3r1p2/2R3p1/6B1/p6P/P3p1P1/P7/5K2 w - - 0 36"], + ["“leonid's position”", "q2k2q1/2nqn2b/1n1P1n1b/2rnr2Q/1NQ1QN1Q/3Q3B/2RQR2B/Q2K2Q1 w - -"], + ["sufficient material - opposing bishops", "8/6BK/7B/6b1/7B/8/B7/7k w - - 40 40"], + ["checkmate in 1", "rnbqkr2/ppppbp1p/8/3NQ3/8/8/PPPP1nPP/R1B1KBNR w - -"], + ["checkmate in 2", "6k1/p4p2/1p4p1/2p4p/4Pnq1/1PQ5/P1P2PPP/3R2K1 w - -"], + ["checkmate in 5, sacrifice your queen", "5rk1/1p3p1p/6p1/q2P4/2n5/P6Q/KB2p3/2R5 w - -"] + ] +} diff --git a/src/activities/chess_partyend/chess_partyend.svg b/src/activities/chess_partyend/chess_partyend.svg new file mode 100644 index 000000000..9d0833e07 --- /dev/null +++ b/src/activities/chess_partyend/chess_partyend.svg @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/click_on_letter/resource/levels-es.json b/src/activities/click_on_letter/resource/levels-es.json new file mode 100644 index 000000000..b5699370e --- /dev/null +++ b/src/activities/click_on_letter/resource/levels-es.json @@ -0,0 +1,14 @@ +[ + { "level": 1, "questions": "aeiouy", "answers": "aeiouy" }, + { "level": 2, "questions": "aeiouy", "answers": "aeiouycs" }, + { "level": 3, "questions": "aeiouy", "answers": "aeiouycksvxz" }, + { "level": 4, "questions": "ckpsvxwz", "answers": "ckpsvxwz" }, + { "level": 5, "questions": "bfglmnqt", "answers": "bfglmnqt" }, + { "level": 6, "questions": "bdgqpnmu", "answers": "bdgqpnmu" }, + { "level": 7, "questions": "ilthwvae", "answers": "ilthwvae" }, + { "level": 8, "questions": "abcdefgh", "answers": "abcdefgh" }, + { "level": 9, "questions": "ijklmnop", "answers": "ijklmnop" }, + { "level": 10, "questions": "qrstuvwxyz", "answers": "qrstuvwxyz" }, + { "level": 11, "questions": "bcdfghjklmnpqrstvwxz", "answers": "bcdfghjklmnpqrstvwxz" }, + { "level": 12, "questions": "mnño", "answers": "emnñopq" } +] diff --git a/src/activities/colors/Colors.qml b/src/activities/colors/Colors.qml index f7e9431fb..d7fc30f98 100644 --- a/src/activities/colors/Colors.qml +++ b/src/activities/colors/Colors.qml @@ -1,33 +1,31 @@ /* GCompris - Colors.qml * * Copyright (C) 2014 Bruno Coudoin * * Original activity in the Gtk+ version of GCompris by * Pascal Georges * * 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.1 import GCompris 1.0 import "colors.js" as Dataset FindIt { mode: "Colors" dataset: Dataset backgroundImg: "qrc:/gcompris/src/activities/colors/resource/background.svg" - itemWidth: 130 * ApplicationInfo.ratio - itemHeight: 130 * ApplicationInfo.ratio } diff --git a/src/activities/colors/FindIt.qml b/src/activities/colors/FindIt.qml index 22ef6dd55..d4328c764 100644 --- a/src/activities/colors/FindIt.qml +++ b/src/activities/colors/FindIt.qml @@ -1,239 +1,245 @@ /* GCompris - FindIt.qml * * Copyright (C) 2015 Bruno Coudoin * * Original activity in the Gtk+ version of GCompris by * Pascal Georges (pascal.georges1@free.fr) * * 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.1 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "findit.js" as Activity ActivityBase { id: activity focus: true property var dataset property string backgroundImg - property int itemWidth - property int itemHeight property string mode: "" onStart: { focus = true; } pageComponent: Image { id: background focus: true fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width source: backgroundImg property bool keyboardMode: false signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property alias background: background property alias bar: bar property alias bonusTimer: bonusTimer property alias containerModel: containerModel property alias questionItem: questionItem // On startup we want to queue the first sound but not after property bool firstQuestion: true property bool audioOk: false property alias score: score } onStart: Activity.start(items, dataset, mode) onStop: Activity.stop() Keys.onPressed: { if(event.key === Qt.Key_Space) { container.currentItem.select() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onEnterPressed: container.currentItem.select(); Keys.onReturnPressed: container.currentItem.select(); Keys.onRightPressed: container.moveCurrentIndexRight(); Keys.onLeftPressed: container.moveCurrentIndexLeft(); Keys.onDownPressed: container.moveCurrentIndexDown(); Keys.onUpPressed: container.moveCurrentIndexUp(); ListModel { id: containerModel } GridView { id: container model: containerModel x: background.width * 0.2 y: background.height * 0.2 width: background.width * 0.7 height: background.height * 0.6 interactive: false cellWidth: itemHeight + 10 cellHeight: itemWidth + 10 keyNavigationWraps: true + + property int itemWidth: Math.min((parent.width * 0.6) / (count / 2), + (parent.height * 0.5) / (count / 3)) + property int itemHeight: itemWidth + delegate: ColorItem { audioVoices: activity.audioVoices source: model.image audioSrc: model.audio ? model.audio : "" question: model.text - sourceSize.height: itemHeight - sourceSize.width: itemWidth + sourceSize.height: container.itemHeight + sourceSize.width: container.itemWidth } add: Transition { PathAnimation { path: Path { PathCurve { x: background.width / 3} PathCurve { y: background.height / 3} PathCurve {} } easing.type: Easing.InOutQuad duration: 2000 } } highlight: Rectangle { width: container.cellWidth - container.spacing height: container.cellHeight - container.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 } } } } GCText { id: questionItem anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 10 fontSize: largeSize + width: parent.width * 0.9 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap font.weight: Font.DemiBold style: Text.Outline styleColor: "black" color: "white" function initQuestion() { text = Activity.getCurrentTextQuestion() if(Activity.getCurrentAudioQuestion()) { if(items.firstQuestion) items.audioOk = activity.audioVoices.append(Activity.getCurrentAudioQuestion()) else items.audioOk = activity.audioVoices.play(Activity.getCurrentAudioQuestion()) items.firstQuestion = false } opacity = 1.0 } onOpacityChanged: opacity == 0 ? initQuestion() : "" Behavior on opacity { PropertyAnimation { duration: 500 } } } DropShadow { anchors.fill: questionItem cached: false horizontalOffset: 3 verticalOffset: 3 radius: 8.0 samples: 16 color: "#80000000" source: questionItem } 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() } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.height: visible ? 80 * ApplicationInfo.ratio : 1 z: bar.z + 1 visible: items.audioOk anchors { bottom: parent.bottom right: parent.right margins: 10 * ApplicationInfo.ratio } onClicked: if (ApplicationSettings.isAudioVoicesEnabled) questionItem.initQuestion() } Score { id: score anchors.bottom: repeatItem.top anchors.right: repeatItem.right anchors.bottomMargin: 30 anchors.margins: 0 } Timer { id: bonusTimer interval: 2000 property bool win function good() { win = true start() } function bad() { win = false start() } onTriggered: win ? bonus.good("flower") : bonus.bad("flower") } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/gletters/gletters.js b/src/activities/gletters/gletters.js index 4afc844b1..823710a7a 100644 --- a/src/activities/gletters/gletters.js +++ b/src/activities/gletters/gletters.js @@ -1,417 +1,417 @@ /* GCompris - gletters.js * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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 . */ /* ToDo / open issues: * - adjust wordlist filenames once we have an ApplicationInfo.dataPath() or so * - make uppercaseOnly be taken from (activity-) settings */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo .import "qrc:/gcompris/src/core/core.js" as Core var currentLevel = 0; var currentSubLevel = 0; var level = null; var maxLevel = 0; var maxSubLevel = 0; var items; var uppercaseOnly; var mode; //speed calculations, common: var speed = 0; // how fast letters fall var fallSpeed = 0; // how often new letters are dropped var incFallSpeed = 1000; // how much drop rate increases per sublevel var incSpeed = 10; // how much speed increases per sublevel // gletters: var fallRateBase = 40; // default for how fast letters fall (smaller is faster) var fallRateMult = 80; // default for how much falling speed increases per level (smaller is faster) var dropRateBase = 5000; // default for how often new letters are dropped var dropRateMult = 100; // default for how much drop rate increases per level // wordsgame: var wgMaxFallSpeed = 7000; var wgMaxSpeed = 150; var wgMinFallSpeed = 3000; var wgMinSpeed = 50; var wgDefaultFallSpeed = 8000; var wgDefaultSpeed = 170; var wgAddSpeed = 20; var wgAddFallSpeed = 1000; var wgMaxFallingItems; var droppedWords; var currentWord = null; // reference to the word currently typing, null if n/a var wordComponent = null; var successRate // Falling speed depends on it function start(items_, uppercaseOnly_, _mode) { items = items_; uppercaseOnly = uppercaseOnly_; mode = _mode; currentLevel = 0; currentSubLevel = 0; var locale = items.locale == "system" ? "$LOCALE" : items.locale // register the voices for the locale GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(GCompris.ApplicationInfo.getVoicesLocale(items.locale))); items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( items.ourActivity.dataSetUrl + "default-"+locale+".json")); // If wordlist is empty, we try to load from short locale and if not present again, we switch to default one var localeUnderscoreIndex = locale.indexOf('_') // probably exist a better way to see if the list is empty if(items.wordlist.maxLevel == 0) { var localeShort; // We will first look again for locale xx (without _XX if exist) if(localeUnderscoreIndex > 0) { localeShort = locale.substring(0, localeUnderscoreIndex) } else { localeShort = locale; } // If not found, we will use the default file items.wordlist.useDefault = true items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( items.ourActivity.dataSetUrl + "default-"+localeShort+".json")); // We remove the using of default file for next time we enter this function items.wordlist.useDefault = false } maxLevel = items.wordlist.maxLevel; droppedWords = new Array(); initLevel(); } function stop() { deleteWords(); wordComponent = null items.wordDropTimer.stop(); } function initLevel() { items.audioVoices.clearQueue() items.bar.level = currentLevel + 1; wgMaxFallingItems = 3 successRate = 1.0 // initialize level deleteWords(); level = items.wordlist.getLevelWordList(currentLevel + 1); maxSubLevel = items.wordlist.getMaxSubLevel(currentLevel + 1); if (maxSubLevel == 0) { // If "sublevels" length is not set in wordlist, use the words length maxSubLevel = level.words.length } items.score.numberOfSubLevels = maxSubLevel; setSpeed(); /*console.log("Gletters: initializing level " + (currentLevel + 1) + " maxSubLvl=" + maxSubLevel + " wordCount=" + level.words.length + " speed=" + speed + " fallspeed=" + fallSpeed);*/ { /* populate VirtualKeyboard for mobile: * 1. for < 10 letters print them all in the same row * 2. for > 10 letters create 3 rows with equal amount of keys per row * if possible, otherwise more keys in the upper rows * 3. if we have both upper- and lowercase letters activate the shift * key*/ // first generate a map of needed letters var letters = new Array(); items.keyboard.shiftKey = false; for (var i = 0; i < level.words.length; i++) { if(mode ==='letter') { // The word is a letter, even if it has several chars (digraph) var letter = level.words[i]; var isUpper = (letter == letter.toLocaleUpperCase()); if (isUpper && letters.indexOf(letter.toLocaleLowerCase()) !== -1) items.keyboard.shiftKey = true; else if (!isUpper && letters.indexOf(letter.toLocaleUpperCase()) !== -1) items.keyboard.shiftKey = true; else if (letters.indexOf(letter) === -1) letters.push(level.words[i]); } else { // We split each word in char to create the keyboard for (var j = 0; j < level.words[i].length; j++) { var letter = level.words[i].charAt(j); var isUpper = (letter == letter.toLocaleUpperCase()); if (isUpper && letters.indexOf(letter.toLocaleLowerCase()) !== -1) items.keyboard.shiftKey = true; else if (!isUpper && letters.indexOf(letter.toLocaleUpperCase()) !== -1) items.keyboard.shiftKey = true; else if (letters.indexOf(letter) === -1) letters.push(level.words[i].charAt(j)); } } } letters.sort(); // generate layout from letter map var layout = new Array(); var row = 0; var offset = 0; while (offset < letters.length-1) { var cols = letters.length <= 10 ? letters.length : (Math.ceil((letters.length-offset) / (3 - row))); layout[row] = new Array(); for (var j = 0; j < cols; j++) layout[row][j] = { label: letters[j+offset] }; offset += j; row++; } items.keyboard.layout = layout; } items.wordlist.initRandomWord(currentLevel + 1) initSubLevel() } function initSubLevel() { currentWord = null; if (currentSubLevel != 0) { // increase speed speed = Math.max(speed - incSpeed, wgMinSpeed); items.wordDropTimer.interval = fallSpeed = Math.max(fallSpeed - incFallSpeed, wgMinFallSpeed); } items.score.currentSubLevel = currentSubLevel + 1; if (currentSubLevel == 0 || droppedWords.length <= 1) // note, last word is still fading out dropWord(); //console.log("Gletters: initializing subLevel " + (currentSubLevel + 1) + " words=" + JSON.stringify(level.words)); } function processKeyPress(text) { var typedText = uppercaseOnly ? text.toLocaleUpperCase() : text; if (currentWord !== null) { // check against a currently typed word if (!currentWord.checkMatch(typedText)) { currentWord = null; audioCrashPlay() } else { playLetter(text) } } else { // no current word, check against all available words var found = false for (var i = 0; i< droppedWords.length; i++) { if (droppedWords[i].checkMatch(typedText)) { // typed correctly currentWord = droppedWords[i]; playLetter(text) found = true break; } } if(!found) { audioCrashPlay() } } if (currentWord !== null && currentWord.isCompleted()) { // win! currentWord.won(); // note: deleteWord() is triggered after fadeout successRate += 0.1 currentWord = null nextSubLevel(); } } function setSpeed() { if (mode == "letter") { speed = (level.speed !== undefined) ? level.speed : (fallRateBase + Math.floor(fallRateMult / (currentLevel + 1))); fallSpeed = (level.fallspeed !== undefined) ? level.fallspeed : Math.floor((dropRateBase + (dropRateMult * (currentLevel + 1)))); } else { // wordsgame speed = (level.speed !== undefined) ? level.speed : wgDefaultSpeed - (currentLevel+1)*wgAddSpeed; fallSpeed = (level.fallspeed !== undefined) ? level.fallspeed : wgDefaultFallSpeed - (currentLevel+1)*wgAddFallSpeed if(speed < wgMinSpeed ) speed = wgMinSpeed; if(speed > wgMaxSpeed ) speed = wgMaxSpeed; if(fallSpeed < wgMinFallSpeed ) fallSpeed = wgMinFallSpeed; if(fallSpeed > wgMaxFallSpeed ) fallSpeed = wgMaxFallSpeed; } items.wordDropTimer.interval = fallSpeed; } function deleteWords() { if (droppedWords === undefined || droppedWords.length < 1) return; for (var i = 0; i< droppedWords.length; i++) droppedWords[i].destroy(); droppedWords.length = 0; } function deleteWord(w) { if (droppedWords === undefined || droppedWords.length < 1) return; if (w == currentWord) currentWord = null; for (var i = 0; i< droppedWords.length; i++) if (droppedWords[i] == w) { droppedWords[i].destroy(); droppedWords.splice(i, 1); break; } } function createWord() { if (wordComponent.status == 1 /* Component.Ready */) { var text = items.wordlist.getRandomWord(); if(!text) { items.wordDropTimer.restart(); return } // if uppercaseOnly case does not matter otherwise it does if (uppercaseOnly) text = text.toLocaleUpperCase(); var word if(items.ourActivity.getImage(text)) { word = wordComponent.createObject( items.background, { "text": text, "image": items.ourActivity.getImage(text), // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, }); } else if(items.ourActivity.getDominoValues(text).length) { word = wordComponent.createObject( items.background, { "text": text, "dominoValues": items.ourActivity.getDominoValues(text), // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, }); } else { word = wordComponent.createObject( items.background, { "text": text, // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, "mode": mode, }); } if (word === null) console.log("Gletters: Error creating word object"); else { droppedWords[droppedWords.length] = word; // speed to duration: var duration = (items.main.height / 2) * speed / successRate; /* console.debug("Gletters: dropping new word " + word.text + " duration=" + duration + " (speed=" + speed + ")" + " num=" + droppedWords.length);*/ word.startMoving(duration); } items.wordDropTimer.restart(); } else if (wordComponent.status == 3 /* Component.Error */) { console.log("Gletters: error creating word component: " + wordComponent.errorString()); } } function dropWord() { // Do not create too many falling items if(droppedWords.length > wgMaxFallingItems) { items.wordDropTimer.restart(); return } if (wordComponent !== null) createWord(); else { var text = items.wordlist.getRandomWord(); items.wordlist.appendRandomWord(text) var fallingItem if(items.ourActivity.getImage(text)) fallingItem = "FallingImage.qml" else if(items.ourActivity.getDominoValues(text).length) fallingItem = "FallingDomino.qml" else fallingItem = "FallingWord.qml" wordComponent = Qt.createComponent("qrc:/gcompris/src/activities/gletters/" + fallingItem); if (wordComponent.status == 1 /* Component.Ready */) createWord(); else if (wordComponent.status == 3 /* Component.Error */) { console.log("Gletters: error creating word component: " + wordComponent.errorString()); } else wordComponent.statusChanged.connect(createWord); } } function appendRandomWord(word) { items.wordlist.appendRandomWord(word) } function audioCrashPlay() { if(successRate > 0.5) successRate -= 0.1 items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav") } function nextLevel() { if(maxLevel <= ++currentLevel ) { currentLevel = 0 } currentSubLevel = 0; initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = maxLevel - 1 } currentSubLevel = 0; initLevel(); } function nextSubLevel() { if( ++currentSubLevel >= maxSubLevel) { currentSubLevel = 0 items.bonusTimer.start(); } else initSubLevel(); } function playLetter(letter) { var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/"+locale+"/alphabet/" + Core.getSoundFilenamForChar(letter))) } function focusTextInput() { - if (!GCompris.ApplicationInfo.isMobile && items) + if (!GCompris.ApplicationInfo.isMobile && items && items.textinput) items.textinput.forceActiveFocus(); } diff --git a/src/activities/instruments/Instruments.qml b/src/activities/instruments/Instruments.qml index dadd85350..3efad3b0d 100644 --- a/src/activities/instruments/Instruments.qml +++ b/src/activities/instruments/Instruments.qml @@ -1,34 +1,32 @@ /* GCompris - Instruments.qml * * Copyright (C) 2014 Bruno Coudoin * * Original activity in the Gtk+ version of GCompris by * Sylvain Dechy * * 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.1 import GCompris 1.0 import "../colors" import "instruments.js" as Dataset FindIt { mode: "Instruments" dataset: Dataset backgroundImg: "qrc:/gcompris/src/activities/instruments/resource/background.svg" - itemWidth: 130 * ApplicationInfo.ratio - itemHeight: 130 * ApplicationInfo.ratio } diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index e7e4b77c8..e27716661 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -1,479 +1,479 @@ /* 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.2 import "../../core" import GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core import QtGraphicalEffects 1.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: menuActivity focus: true activityInfo: ActivityInfoTree.rootMenu onHome: { if(pageView.depth === 1) { Core.quit(main); } else { pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == menuActivity) focus = true; } } onDisplayDialog: pageView.push(dialog) // @cond INTERNAL_DOCS property string url: "qrc:/gcompris/src/activities/menu/resource/" property variant sections: [ { icon: menuActivity.url + "all.svg", tag: "favorite" }, { icon: menuActivity.url + "computer.svg", tag: "computer" }, { icon: menuActivity.url + "discovery.svg", tag: "discovery" }, { icon: menuActivity.url + "experience.svg", tag: "experiment" }, { icon: menuActivity.url + "fun.svg", tag: "fun" }, { icon: menuActivity.url + "math.svg", tag: "math" }, { icon: menuActivity.url + "puzzle.svg", tag: "puzzle" }, { icon: menuActivity.url + "reading.svg", tag: "reading" }, { icon: menuActivity.url + "strategy.svg", tag: "strategy" }, ] property string currentTag: sections[0].tag /// @endcond pageComponent: Image { id: background source: menuActivity.url + "background.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop function loadActivity() { pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: if (status == Loader.Ready) loadActivity() } // Filters property bool horizontal: main.width > main.height property int sectionIconWidth: horizontal ? Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1)) : Math.min(100 * ApplicationInfo.ratio, (main.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: { if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_S) { // Ctrl+S toggle show / hide section ApplicationSettings.sectionVisible = !ApplicationSettings.sectionVisible } else if(event.key === Qt.Key_Space) { currentActiveGrid.currentItem.selectCurrentItem() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onTabPressed: currentActiveGrid = ((currentActiveGrid == activitiesGrid) ? section : activitiesGrid); Keys.onEnterPressed: currentActiveGrid.currentItem.selectCurrentItem(); Keys.onReturnPressed: currentActiveGrid.currentItem.selectCurrentItem(); Keys.onRightPressed: currentActiveGrid.moveCurrentIndexRight(); Keys.onLeftPressed: currentActiveGrid.moveCurrentIndexLeft(); Keys.onDownPressed: currentActiveGrid.moveCurrentIndexDown(); Keys.onUpPressed: currentActiveGrid.moveCurrentIndexUp(); GridView { id: section model: sections width: horizontal ? main.width : sectionCellWidth height: horizontal ? sectionCellHeight : main.height - bar.height x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellHeight 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() { particles.burst(10) ActivityInfoTree.filterByTag(modelData.tag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() menuActivity.currentTag = modelData.tag section.currentIndex = index } } } delegate: sectionDelegate highlight: Item { width: sectionCellWidth height: sectionCellHeight Rectangle { anchors.fill: parent color: "#99FFFFFF" } 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: 190 * ApplicationInfo.ratio - property int iconHeight: 190 * ApplicationInfo.ratio + property int iconWidth: 180 * ApplicationInfo.ratio + property int iconHeight: 180 * 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: iconHeight * 1.5 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 on each activity top right.") } 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 layer.enabled: true anchors { top: 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: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { source: "qrc:/gcompris/src/activities/" + icon; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter sourceSize.height: iconHeight 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 ? "" : menuActivity.url + "lock.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: menuActivity.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, { 'audioVoices': audioVoices, 'audioEffects': audioEffects, 'menu': menuActivity, '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.effect: OpacityMask { id: activitiesOpacity source: activitiesGrid maskSource: activitiesMask anchors.fill: activitiesGrid } } Bar { id: bar content: BarEnumContent { value: help | exit | config | about } onAboutClicked: { displayDialog(dialogAbout) } onHelpClicked: { displayDialog(dialogHelp) } onConfigClicked: { 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: menuActivity content: Component { ConfigurationItem { id: configItem width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio } } onSaveData: { dialogActivityConfig.configItem.save(); } onClose: { ActivityInfoTree.filterByTag(menuActivity.currentTag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() home() } } } diff --git a/src/activities/money/MoneyCore.qml b/src/activities/money/MoneyCore.qml index a93579be5..f38823dcb 100644 --- a/src/activities/money/MoneyCore.qml +++ b/src/activities/money/MoneyCore.qml @@ -1,305 +1,307 @@ /* GCompris - MoneyCore.qml * * Copyright (C) 2014 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.1 import "../../core" import "money.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property variant dataset pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "/background.svg" 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 GCAudio audioEffects: activity.audioEffects property alias answerModel: answerModel property alias pocketModel: pocketModel property alias store: store property alias instructions: instructions property alias tux: tux property alias tuxMoney: tuxMoney property alias bar: bar property alias bonus: bonus } onStart: { Activity.start(items, dataset) } onStop: { Activity.stop() } Column { id: column spacing: 10 x: parent.width * 0.05 y: parent.height * 0.05 width: parent.width * 0.9 property int nbColumns: 5 property int nbLines: 2 property int itemWidth: Math.min(width / nbColumns - 10 - 10 / nbColumns, parent.height * 0.4 / nbLines - 10 - 10 / nbLines) property int itemHeight: itemWidth * 0.59 // === The Answer Area === Rectangle { id: answerArea height: (column.itemHeight + 10) * column.nbLines width: column.width color: "#55333333" border.color: "black" border.width: 2 radius: 5 Flow { anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 10 anchors.rightMargin: 10 anchors.fill: parent spacing: 10 add: Transition { NumberAnimation { properties: "x" from: parent.width * 0.05 easing.type: Easing.InOutQuad } } move: Transition { NumberAnimation { properties: "x,y" easing.type: Easing.InOutQuad } } Repeater { id: answer model: ListModel { id: answerModel } Image { source: Activity.url + img sourceSize.height: column.itemHeight height: column.itemHeight MultiPointTouchArea { anchors.fill: parent onReleased: Activity.unpay(index) } } } } } // === The Store Area === property int nbStoreColumns: activity.dataset === "BACK_WITHOUT_CENTS" || activity.dataset === "BACK_WITH_CENTS" ? store.model.length + 1 : store.model.length property int itemStoreWidth: - Math.min( (width - storeAreaFlow.anchors.leftMargin - - storeAreaFlow.anchors.rightMargin - - storeAreaFlow.spacing * nbStoreColumns - 1) / nbStoreColumns, - (parent.height - answerArea.height - instructionsArea.realHeight - pocketArea.height - bar.height) / 2) + Math.min((column.width - storeAreaFlow.spacing * nbStoreColumns) / nbStoreColumns, + (parent.height - answerArea.height - + pocketArea.height - bar.height) * 0.8) - instructionsArea.realHeight property int itemStoreHeight: itemStoreWidth Rectangle { id: storeArea height: (column.itemStoreHeight + 10) width: column.width color: "#55333333" border.color: "black" border.width: 2 radius: 5 Flow { id: storeAreaFlow anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 20 anchors.rightMargin: 20 anchors.fill: parent spacing: 40 add: Transition { NumberAnimation { properties: "x" from: parent.width * 0.05 duration: 300 } } Image { id: tux visible: activity.dataset === "BACK_WITHOUT_CENTS" || activity.dataset === "BACK_WITH_CENTS" source: Activity.url + "/tux.svg" sourceSize.height: column.itemStoreHeight + sourceSize.width: column.itemStoreHeight Repeater { id: tuxMoney Image { source: Activity.url + modelData.img sourceSize.height: column.itemStoreHeight * 0.4 x: tux.x + index * 20 y: tux.y + tux.height / 2 + index * 20 } } } Repeater { id: store Image { source: Activity.url + modelData.img sourceSize.height: column.itemStoreHeight + sourceSize.width: column.itemStoreHeight GCText { text: modelData.price - fontSize: 20 + fontSize: 16 font.weight: Font.DemiBold style: Text.Outline styleColor: "black" color: "white" anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + anchors.top: parent.top + anchors.topMargin: index % 2 == 0 ? 0 : parent.height - height } } } } } // == The instructions Area == Rectangle { id: instructionsArea height: instructions.height width: column.width color: "#55333333" border.color: "black" border.width: 2 radius: 5 anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 10 anchors.rightMargin: 10 visible: bar.level === 1 - property int realHeight: bar.level === 1 ? height : 0 + property int realHeight: bar.level === 1 ? height + column.spacing : 0 GCText { id: instructions horizontalAlignment: Text.AlignHCenter width: column.width wrapMode: Text.WordWrap fontSize: regularSize } } // === The Pocket Area === Rectangle { id: pocketArea height: (column.itemHeight + 10) * column.nbLines width: column.width color: "#661111AA" border.color: "black" border.width: 2 radius: 5 Flow { anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 10 anchors.rightMargin: 10 anchors.fill: parent spacing: 10 add: Transition { NumberAnimation { properties: "x" from: parent.width * 0.05 easing.type: Easing.InOutQuad } } move: Transition { NumberAnimation { properties: "x,y" easing.type: Easing.InOutQuad } } Repeater { id: pocket model: ListModel { id: pocketModel } Image { source: Activity.url + img sourceSize.height: column.itemHeight height: column.itemHeight MultiPointTouchArea { anchors.fill: parent onReleased: Activity.pay(index) } } } } } } 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) } } } diff --git a/src/activities/money/resource/cake.svg b/src/activities/money/resource/cake.svg index 09870768b..7de125880 100644 --- a/src/activities/money/resource/cake.svg +++ b/src/activities/money/resource/cake.svg @@ -1,128 +1,141 @@ + image/svg+xml \ No newline at end of file + d="m 120.1869,654.4434 c -1.7232,-4.5282 -0.3471,-7.862 2.1077,-5.6183 m -11.0587,15.792 c -1.7132,-4.5026 -1.8966,-15.929 0.6034,-12.4074 1.3665,1.9252 6.1376,12.815 4.4791,13.3397 m 14.2538,-2.1934 c -2.8563,-4.5515 -4.412,-8.6837 -2.0416,-11.2372 1.6376,-1.7642 3.2188,3.5461 4.4425,6.8208 m -18.8345,31.6852 c 5.0034,13.149 7.5486,-11.3081 -1.0731,-7.0986 -0.0634,0.0311 0.1051,1.5041 1.0731,7.0986 z m 8.3687,-19.6701 c -3.3527,5.0338 -1.9287,11.5557 2.5186,8.7572 3.2539,-1.3827 3.5921,-11.4361 0.4613,-9.9743 m -26.02529,36.8256 c 3.92309,10.6397 6.35179,-9.5811 1.19349,-2.6129 m 37.7575,-7.7359 c -5.0013,-2.8251 -7.9041,-10.2209 -5.0787,-14.074 2.1603,-2.3017 4.695,3.6584 5.7043,5.8006 m 1.4563,-27.6103 c 7.0041,18.4068 15.6853,0.992 2.4654,-8.9069 m 22.4122,5.4272 c -2.6067,9.1029 -8.7345,-19.7895 -2.6874,-11.9415 0.5393,0.7002 3.333,3.8322 2.9037,6.2685 M 125.9836,709.5 c -5.6341,-14.8061 5.9072,-5.2814 4.4489,2.6959 m 12.3721,9.3442 c -7.8522,-12.5121 4.9067,-8.148 4.2191,1.2243 m 6.4152,-13.7642 c -9.3524,-18.2683 7.4639,-9.9165 3.754,-3.9484 M 110.36,724.84 c -5.271,17.1606 9.3938,9.2527 4.5072,-0.8615" + style="font-size:12px;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.25987899" + inkscape:connector-curvature="0" /> \ No newline at end of file diff --git a/src/activities/readingh/Readingh.qml b/src/activities/readingh/Readingh.qml index a12ff5dec..9883084eb 100644 --- a/src/activities/readingh/Readingh.qml +++ b/src/activities/readingh/Readingh.qml @@ -1,321 +1,306 @@ /* GCompris - readingh.qml * * Copyright (C) 2015 Johnny Jazeix * * Authors: * Bruno Coudoin (GTK+ version) * 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.1 import GCompris 1.0 import "../../core" import "readingh.js" as Activity +import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity onStart: focus = true onStop: {} /* mode of the activity, "readingh" (horizontal) or "readingv" (vertical):*/ property string mode: "readingh" pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "reading-bg.svg" signal start signal stop sourceSize.width: parent.width fillMode: Image.Stretch Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } // system locale by default property string locale: "system" // 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 wordlist: wordlist property alias wordDropTimer: wordDropTimer property alias locale: background.locale property alias iAmReady: iAmReady property alias answerButtonFound: answerButtonFound property alias answerButtonNotFound: answerButtonNotFound property alias answerButtonsFlow: answerButtonsFlow property alias wordDisplayRepeater: wordDisplayRepeater property string textToFind property int currentIndex } onStart: { Activity.start(items, mode) } onStop: { Activity.stop() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox - property alias leftToRightBox: leftToRightBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig width: dialogActivityConfig.width label: qsTr("Select your locale") } } - GCDialogCheckBox { - id: leftToRightBox - width: 300 * ApplicationInfo.ratio - text: qsTr("Left to right mode") - checked: wordDisplayList.layoutDirection == Qt.LeftToRight - } } } } onClose: home() onLoadData: { if(dataToSave) { if(dataToSave["locale"]) { background.locale = dataToSave["locale"]; } - if(dataToSave["leftToRight"] == false) { - wordDisplayList.layoutDirection = Qt.RightToLeft; - } - else { // true or undefined (default value) - wordDisplayList.layoutDirection = Qt.LeftToRight; - } } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; - var leftToRightMode = dialogActivityConfig.loader.item.leftToRightBox.checked; // Remove .UTF-8 if(newLocale.indexOf('.') != -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } dataToSave = { "locale": newLocale, - "leftToRight": leftToRightMode } background.locale = newLocale; - wordDisplayList.layoutDirection = leftToRightMode ? Qt.LeftToRight : Qt.RightToLeft; - // Restart the activity with new information if(oldLocale !== newLocale) { background.stop(); + wordDisplayList.layoutDirection = Core.isLeftToRightLocale(background.locale) ? Qt.LeftToRight : Qt.RightToLeft; 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: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus // Do not pass automatically at next level, allowing the child to do more than one try, or add sublevels? Component.onCompleted: { win.connect(resetClickInProgress) loose.connect(resetClickInProgress) } } // used to know if we already click on "Yes" or "No" property bool isClickInProgress: false // used to avoid multiple clicks between the begin and end of bonus play property bool isClickInProgress2: false function resetClickInProgress() { isClickInProgress = false; isClickInProgress2 = false; Activity.initLevel() } Flow { id: wordDisplayList spacing: 20 x: 70/800*parent.width y: 100/600*parent.height width: 350/800*parent.width-x height: 520/600*parent.height-y flow: mode == "readingh" ? Flow.LeftToRight : Flow.TopToBottom - layoutDirection: Qt.LeftToRight + layoutDirection: Core.isLeftToRightLocale(locale) ? Qt.LeftToRight : Qt.RightToLeft Repeater { id: wordDisplayRepeater model: Activity.words property int idToHideBecauseOverflow: 0 delegate: GCText { text: modelData opacity: iAmReady.visible ? false : (index == items.currentIndex ? 1 : 0) onOpacityChanged: { /* Handle case where we go over the image On these cases, we hide all above items to restart to 0 As we don't replay the same level and always replace the model, we do not care about restoring visible to true */ if((x+width > wordDisplayList.width) || (y+height > wordDisplayList.height)) { var i = wordDisplayRepeater.idToHideBecauseOverflow; for(; i < index; ++i) { wordDisplayRepeater.itemAt(i).visible=false } wordDisplayRepeater.idToHideBecauseOverflow = i } } } } } GCText { id: wordToFindBox x: 430/800*parent.width y: 90/600*parent.height text: qsTr("Check if the word
%1
is displayed").arg(items.textToFind) color: "black" horizontalAlignment: Text.AlignHCenter width: background.width/3 height: background.height/5 fontSizeMode: Text.Fit } ReadyButton { id: iAmReady onClicked: Activity.run() x: background.width / 2 y: background.height / 2 anchors.verticalCenter: undefined anchors.horizontalCenter: undefined } Flow { id: answerButtonsFlow x: iAmReady.x y: iAmReady.y width: wordToFindBox.width AnswerButton { id : answerButtonFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 80 * ApplicationInfo.ratio textLabel: qsTr("Yes, I saw it!") isCorrectAnswer: Activity.words ? Activity.words.indexOf(items.textToFind) != -1 : false onCorrectlyPressed: if(isClickInProgress && !isClickInProgress2) { bonus.good("flower"); isClickInProgress2 = true } onIncorrectlyPressed: if(isClickInProgress && !isClickInProgress2) { bonus.bad("flower"); isClickInProgress2 = true } onPressed: { if(!isClickInProgress) { isClickInProgress = true } } } AnswerButton { id : answerButtonNotFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 80 * ApplicationInfo.ratio textLabel: qsTr("No, it was not there!") isCorrectAnswer: !answerButtonFound.isCorrectAnswer onCorrectlyPressed: if(isClickInProgress && !isClickInProgress2) { bonus.good("flower"); isClickInProgress2 = true } onIncorrectlyPressed: if(isClickInProgress && !isClickInProgress2) { bonus.bad("flower"); isClickInProgress2 = true } onPressed: { if(!isClickInProgress) { isClickInProgress = true } } } } Wordlist { id: wordlist defaultFilename: Activity.dataSetUrl + "default-en.json" // To switch between locales: xx_XX stored in configuration and // possibly correct xx if available (ie fr_FR for french but dataset is fr.) useDefault: false filename: "" onError: console.log("Reading: Wordlist error: " + msg); } Timer { id: wordDropTimer repeat: true interval: 1000 onTriggered: Activity.dropWord(); } } } diff --git a/src/activities/target/Target.qml b/src/activities/target/Target.qml index b02e55dc9..e49da0737 100644 --- a/src/activities/target/Target.qml +++ b/src/activities/target/Target.qml @@ -1,236 +1,236 @@ /* GCompris - target.qml * * Copyright (C) 2014 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.1 import GCompris 1.0 import "../../core" import "target.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Item { id: background anchors.fill: parent signal start signal stop signal targetReached Keys.onPressed: { if(items.currentArrow != items.nbArrow) return if(event.key === Qt.Key_Backspace) { backspace() } appendText(event.text) } 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 targetModel: targetItem.model property alias targetItem: targetItem property alias userEntry: userEntry property int currentArrow property int nbArrow property int currentSubLevel property int numberOfSubLevel property bool arrowFlying onNbArrowChanged: { arrowRepeater.init(nbArrow) } } onStart: { Activity.start(items) } onStop: { Activity.stop() } TargetItem { id: targetItem } onTargetReached: { items.arrowFlying = false if(items.currentArrow == items.nbArrow) { targetItem.stop() targetItem.scoreText += " = " userEntry.text = "?" } } Arrow { id: arrowRepeater } Image { id: cross anchors.centerIn: parent source: Activity.url + "cross.svg" opacity: items.currentArrow != items.nbArrow ? 1 : 0 sourceSize.width: 50 * ApplicationInfo.ratio } MouseArea { id: mouseArea anchors.fill: parent enabled: items.currentArrow != items.nbArrow && !items.arrowFlying onClicked: { activity.audioEffects.play(Activity.url + 'arrow.wav') items.arrowFlying = true if(items.currentArrow != items.nbArrow) { arrowRepeater.itemAt(items.currentArrow).opacity = 1 arrowRepeater.itemAt(items.currentArrow++).scale = 0.5 } } } GCText { id: scoreItem anchors.horizontalCenter: parent.horizontalCenter width: parent.width text: targetItem.scoreText fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } function backspace() { userEntry.text = userEntry.text.slice(0, -1) if(userEntry.text.length === 0) { userEntry.text = "?" } else { if(targetItem.scoreTotal == userEntry.text) bonus.good("flower") } } function appendText(text) { if(text === keyboard.backspace) { backspace() return } var number = parseInt(text) if(isNaN(number)) return if(userEntry.text === "?") { userEntry.text = "" } - if(userEntry.text.length >= 3) { + if(userEntry.text.length > ('' + targetItem.scoreTotal).length) { return } userEntry.text += text if(targetItem.scoreTotal == userEntry.text) bonus.good("flower") } GCText { id: userEntry anchors.top: scoreItem.bottom width: parent.width fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter hide: items.currentArrow == items.nbArrow ? false : true layout: [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" }, { label: keyboard.backspace } ] ] onKeypress: background.appendText(text) onError: console.log("VirtualKeyboard error: " + msg); } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Score { id: score anchors.right: parent.right anchors.top: parent.top anchors.bottom: undefined currentSubLevel: items.currentSubLevel + 1 numberOfSubLevels: items.numberOfSubLevel } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/watercycle/ActivityInfo.qml b/src/activities/watercycle/ActivityInfo.qml new file mode 100644 index 000000000..9550a6668 --- /dev/null +++ b/src/activities/watercycle/ActivityInfo.qml @@ -0,0 +1,34 @@ +/* GCompris - ActivityInfo.qml + * + * Copyright (C) 2015 Sagar Chand Agarwal (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 GCompris 1.0 + +ActivityInfo { + name: "watercycle/Watercycle.qml" + difficulty: 3 + icon: "watercycle/watercycle.svg" + author: "Sagar Chand Agarwal <atomsagar@gmail.com>" + demo: true + title: qsTr("Watercycle") + description: qsTr("Tux has come back from a long fishing party on his boat. Bring the water system back up so he can take a shower.") + //intro: "Click on the various active elements that make up the water supply. Then press the shower button for Tux." + goal: qsTr("Learn the water cycle") + prerequisite: "" + manual: qsTr("Click on different active elements: sun, cloud, pumping station, and the sewage treatment plant, in order to reactivate the entire water system. When the system is back up and Tux is in the shower, push the shower button for him.") + credit: qsTr("Drawing by Stephane Cabaraux.") + section: "experiment" +} diff --git a/src/activities/watercycle/CMakeLists.txt b/src/activities/watercycle/CMakeLists.txt new file mode 100644 index 000000000..b1969ac4d --- /dev/null +++ b/src/activities/watercycle/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/watercycle *.qml *.svg *.js resource/*) diff --git a/src/activities/watercycle/Watercycle.qml b/src/activities/watercycle/Watercycle.qml new file mode 100644 index 000000000..63c42c2b5 --- /dev/null +++ b/src/activities/watercycle/Watercycle.qml @@ -0,0 +1,795 @@ +/* GCompris - watercycle.qml + * + * Copyright (C) 2015 Sagar Chand Agarwal + * + * Authors: + * Bruno Coudoin (GTK+ version) + * Sagar Chand Agarwal (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.1 +import GCompris 1.0 + +import "../../core" +import "." + +ActivityBase { + id: activity + + onStart: focus = true + onStop: {} + + property string url: "qrc:/gcompris/src/activities/watercycle/resource/" + + pageComponent: Item { + id: background + anchors.fill: parent + + signal start + signal stop + + Component.onCompleted: { + activity.start.connect(start) + activity.stop.connect(stop) + } + + onStart: { + shower.hide() + river.level = 0 + } + + QtObject { + id: items + property var dataset: { + "none": "", + "start": qsTr("Sun is the main component of water cycle. Click on the sun to start the water cycle."), + "sun": qsTr("As the sun rises, the water of the sea starts heating and evaporates."), + "cloud": qsTr(" Water vapor condenses to form cloud and when clouds become heavy, they rain. Click on the cloud."), + "rain": qsTr("Rain causes rivers to swell up and this water is transported to us via motor pumps through water-tower." + + " Click on the motor pump to supply water to residents."), + "tower": qsTr("See the tower filled with water. Activate the sewage treatment station by clicking on it."), + "shower": qsTr("Great, click on the shower, as Tux arrives home."), + "done": qsTr("Fantastic, you have completed water cycle. You can continue playing.") + } + + property bool cycleDone: false + property GCAudio audioEffects: activity.audioEffects + } + + IntroMessage { + id: message + anchors { + top: parent.top + topMargin: 10 + right: parent.right + rightMargin: 5 + left: parent.left + leftMargin: 5 + } + z: 100 + onIntroDone: { + anim.running = true + info.visible = true + sun_area.enabled = true + } + intro: [ + qsTr("The water cycle (also known as the hydrologic cycle) is the journey water takes" + +" as it circulates from the land to the sky and back again." + +" The sun's heat provides energy to evaporate water from water bodies like oceans."), + qsTr("Plants also lose water to the air through transpiration. The water vapor eventually, " + +"cools forming tiny droplets in clouds. When the clouds meet cool air over land, " + +"precipitation is triggered and fall down as rain.") , + qsTr("Some of the water is trapped between rock or clay layers, called groundwater. " + +"But most of the water flows as runoff, eventually returning to the seas via rivers."), + qsTr("Your goal is to complete water cycle before Tux reaches home. " + +"Click on the different components which make up the water cycle. " + +"First click on sun, then cloud, water pumping station near the river, " + +"sewage treatment, and at last regulate the switch to provide water to Tux's shower.") + ] + } + + Image { + id: sky + anchors.top: parent.top + sourceSize.width: parent.width + source: activity.url + "sky.svg" + height: (background.height - landscape.paintedHeight) / 2 + landscape.paintedHeight * 0.3 + z: 1 + } + + Image { + id: sea + anchors { + left: parent.left + bottom: parent.bottom + } + sourceSize.width: parent.width + source: activity.url + "sea.svg" + height: (background.height - landscape.paintedHeight) / 2 + landscape.paintedHeight * 0.7 + z:3 + } + + Image { + id: landscape + anchors.fill: parent + sourceSize.width: parent.width + source: activity.url + "landscape.svg" + z: 6 + } + + Image { + id: tuxboat + opacity: 1 + source: activity.url + "boat.svg" + sourceSize.width: parent.width*0.15 + sourceSize.height: parent.height*0.15 + anchors{ + bottom: parent.bottom + bottomMargin: 15 + } + x:0 + z:30 + + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + NumberAnimation on x { + id: anim + running: false + to: parent.width - tuxboat.width + duration: 15000 + easing.type: Easing.InOutSine + onRunningChanged: { + if(!anim.running) + { + items.audioEffects.play('qrc:/gcompris/src/activities/watercycle/resource/harbor2.wav') + tuxboat.opacity = 0 + boatparked.opacity = 1 + shower.stop() + if(!sun.hasRun) + info.setText('start') + } else { + items.audioEffects.play('qrc:/gcompris/src/activities/watercycle/resource/harbor1.wav') + } + } + } + } + + Image { + id: boatparked + source: activity.url + "boat_parked.svg" + sourceSize.width: parent.width*0.15 + sourceSize.height: parent.height*0.15 + opacity: 0 + anchors { + right: parent.right + bottom: parent.bottom + bottomMargin: 20 + } + z: 29 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + } + + Image { + id: sun + source: activity.url + "sun.svg" + sourceSize.width: parent.width * 0.05 + anchors { + left: parent.left + top: parent.top + leftMargin: parent.width*0.05 + topMargin: parent.height * 0.28 + } + z: 2 + property bool hasRun: false + MouseArea { + id: sun_area + anchors.fill: sun + onClicked: { + if(cloud.opacity == 0) + sun.up() + } + } + Behavior on anchors.topMargin { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 5000 } } + function up() { + items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/bleep.wav') + info.setText('sun') + sun.hasRun = true + sun.anchors.topMargin = parent.height * 0.05 + vapor.up() + } + function down() { + sun.anchors.topMargin = parent.height * 0.28 + } + } + + Image { + id: vapor + opacity: 0 + state: "vapor" + source: activity.url + "vapor.svg" + sourceSize.width: parent.width*0.05 + anchors { + left: sun.left + } + y: background.height * 0.28 + z: 10 + + SequentialAnimation { + id: vaporAnim + loops: 2 + NumberAnimation { + target: vapor + property: "opacity" + duration: 200 + from: 0 + to: 1 + } + NumberAnimation { + target: vapor + property: "y" + duration: 5000 + from: background.height * 0.28 + to: background.height * 0.1 + } + NumberAnimation { + target: vapor + property: "opacity" + duration: 200 + from: 1 + to: 0 + } + NumberAnimation { + target: vapor + property: "y" + duration: 0 + to: background.height * 0.28 + } + onRunningChanged: { + if(!running) + info.setText('cloud') + } + } + function up() { + vaporAnim.start() + cloud.up() + } + function down() { + } + } + + + Image { + id: cloud + opacity: 0 + source: activity.url + "cloud.svg" + sourceSize.width: parent.width * 0.20 + fillMode: Image.PreserveAspectFit + width: 0 + anchors { + top: parent.top + topMargin: parent.height * 0.05 + } + x: parent.width * 0.05 + z: 11 + MouseArea { + id: cloud_area + anchors.fill: cloud + onClicked: { + sun.down() + rain.up() + } + } + ParallelAnimation { + id: cloudanimOn + running: false + PropertyAnimation { + target: cloud + property: 'opacity' + easing.type: Easing.InOutQuad + duration: 5000 + from: 0 + to: 1 + } + PropertyAnimation { + target: cloud + property: 'width' + easing.type: Easing.InOutQuad + duration: 15000 + from: 0 + to: cloud.sourceSize.width + } + PropertyAnimation { + target: cloud + property: 'x' + easing.type: Easing.InOutQuad + duration: 15000 + from: background.width * 0.05 + to: background.width * 0.4 + } + } + SequentialAnimation { + id: cloudanimOff + running: false + PropertyAnimation { + target: cloud + property: 'opacity' + easing.type: Easing.InOutQuad + duration: 3000 + from: 1 + to: 0 + } + PropertyAnimation { + target: cloud + property: 'width' + easing.type: Easing.InOutQuad + duration: 0 + to: 0 + } + PropertyAnimation { + target: cloud + property: 'x' + easing.type: Easing.InOutQuad + duration: 0 + to: background.width * 0.05 + } + } + + function up() { + cloudanimOn.start() + } + function down() { + opacity = 0 + width = 0 + x = parent.width * 0.05 + } + } + + Image { + id: rain + source: activity.url + "rain.svg" + sourceSize.height: cloud.height * 2 + opacity: 0 + anchors { + top: cloud.bottom + } + x: cloud.x + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 300 } } + SequentialAnimation{ + id: rainAnim + running: false + loops: 10 + NumberAnimation { + target: rain + property: "scale" + duration: 500 + to: 0.95 + } + NumberAnimation { + target: rain + property: "scale" + duration: 500 + to: 1 + } + onRunningChanged: { + if(!running) { + rain.down() + cloud.down() + } + } + } + function up() { + items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/water.wav') + info.setText('rain') + opacity = 1 + rainAnim.start() + } + function down() { + opacity = 0 + } + } + + Image { + id: river + source: activity.url + "river.svg" + sourceSize.width: parent.width * 0.415 + sourceSize.height: parent.height * 0.74 + width: parent.width * 0.415 + height: parent.height * 0.74 + opacity: level > 0 ? 1 : 0 + anchors { + top: parent.top + left: parent.left + topMargin: parent.height*0.1775 + leftMargin: parent.width*0.293 + } + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 5000 } } + property double level: 0 + } + + Image { + id: reservoir1 + source: activity.url + "reservoir1.svg" + sourceSize.width: parent.width*0.06 + width: parent.width*0.06 + height: parent.height*0.15 + anchors { + top: parent.top + left: parent.left + topMargin: parent.height*0.2925 + leftMargin: parent.width*0.3225 + } + opacity: river.level > 0.2 ? 1 : 0 + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 5000 } } + } + + Image { + id: reservoir2 + source: activity.url + "reservoir2.svg" + sourceSize.width: parent.width*0.12 + width: parent.width*0.12 + height: parent.height*0.155 + anchors { + top: parent.top + left: parent.left + topMargin: parent.height*0.2925 + leftMargin: parent.width*0.285 + } + opacity: river.level > 0.5 ? 1 : 0 + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 5000 } } + } + + Image { + id: reservoir3 + source: activity.url + "reservoir3.svg" + sourceSize.width: parent.width*0.2 + width: parent.width*0.2 + height: parent.height*0.17 + anchors { + top: parent.top + left: parent.left + topMargin: parent.height*0.29 + leftMargin: parent.width*0.25 + } + opacity: river.level > 0.8 ? 1 : 0 + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 5000 } } + } + + Image { + id: waterplant + source: activity.url + "motor.svg" + sourceSize.width: parent.width*0.07 + sourceSize.height: parent.height*0.08 + anchors { + top: parent.top + left:parent.left + topMargin: parent.height*0.38 + leftMargin: parent.width*0.4 + } + z: 20 + property bool running: false + MouseArea { + id: motor_area + enabled: river.level > 0.2 + anchors.fill: parent + onClicked: { + items.audioEffects.play('qrc:/gcompris/src/activities/watercycle/resource/bubble.wav') + info.setText('tower') + waterplant.running = true + } + } + } + + Image { + id: fillpipe + anchors.fill: parent + sourceSize.width: parent.width + width: parent.width + source: activity.url + "fillwater.svg" + opacity: waterplant.running ? 1 : 0.1 + z: 9 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 300 } } + } + + Image { + id: sewageplant + source: activity.url + "waste.svg" + sourceSize.height: parent.height * 0.15 + anchors { + top: parent.top + left: parent.left + topMargin: parent.height*0.74 + leftMargin: parent.width*0.66 + } + z: 11 + property bool running: false + MouseArea { + id: waste_area + enabled: river.opacity == 1 + anchors.fill: parent + onClicked: { + items.audioEffects.play('qrc:/gcompris/src/activities/watercycle/resource/bubble.wav') + info.setText('shower') + sewageplant.running = true + } + } + } + + Image { + id: wastepipe + anchors.fill: parent + sourceSize.width: parent.width + width: parent.width + source: activity.url + "wastewater.svg" + opacity: sewageplant.running ? 1 : 0.1 + z: 10 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 300 } } + } + + Image { + id: tower + source: activity.url + "watertower.svg" + sourceSize.width: parent.width*0.18 + sourceSize.height: parent.height*0.15 + anchors { + top: parent.top + right: parent.right + topMargin: parent.height*0.225 + rightMargin: parent.width*0.175 + } + z: 10 + property double level: 0 + + Image { + id: towerfill + scale: tower.level + source: activity.url + "watertowerfill.svg" + sourceSize.width: tower.width*0.4 + anchors { + top: tower.top + left:tower.left + topMargin: tower.height*0.13 + leftMargin: tower.width*0.3 + } + Behavior on scale { PropertyAnimation { duration: timer.interval } } + } + } + + Image { + id: shower + source: activity.url + "shower.svg" + sourceSize.height: parent.height*0.2 + sourceSize.width: parent.width*0.15 + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: parent.height* 0.32 + rightMargin: parent.width*0.012 + } + z: 10 + visible: false + property bool on: false + + MouseArea { + id: shower_area + anchors.fill: parent + onClicked: { + if(!shower.on && + river.opacity == 1 && wastepipe.opacity > 0.8 && + fillpipe.opacity > 0.8 && tower.level > 0.5) + shower.start() + else + shower.stop() + } + } + + function start() { + shower.on = true + shower.visible = true + showerhot.visible = true + tuxbath.visible = true + showercold.visible = false + tuxoff.visible = false + + if(!items.cycleDone) { + info.setText('done') + bonus.good('smiley') + items.cycleDone = true + } + items.audioEffects.play('qrc:/gcompris/src/activities/watercycle/resource/apert.wav') + } + + function stop() { + shower.on = false + shower.visible = true + showerhot.visible = false + tuxbath.visible = false + showercold.visible = true + tuxoff.visible = true + } + function hide() { + shower.visible = false + shower.on = false + tuxoff.visible = false + showercold.visible = false + showerhot.visible = false + tuxbath.visible = false + } + } + + Image { + id: tuxoff + source:activity.url + "tuxoff.svg" + sourceSize.width: shower.height * 0.4 + anchors { + horizontalCenter: shower.horizontalCenter + verticalCenter: shower.verticalCenter + verticalCenterOffset: shower.height*0.1 + horizontalCenterOffset: -shower.width*0.05 + } + z: 10 + visible: false + } + + Image { + id: tuxbath + source: activity.url + "tuxbath.svg" + sourceSize.width: shower.height * 0.5 + anchors { + horizontalCenter: shower.horizontalCenter + verticalCenter: shower.verticalCenter + verticalCenterOffset: shower.height*0.1 + horizontalCenterOffset: -shower.width*0.05 + } + z: 10 + visible: false + } + + Image { + id: showerhot + source: activity.url + "showerhot.svg" + sourceSize.width: shower.width * 0.1 + anchors { + right: shower.right + top: shower.top + rightMargin: shower.width*0.15 + topMargin: shower.height*0.25 + } + z: 10 + visible: false + } + + Image { + id: showercold + source: activity.url + "showercold.svg" + sourceSize.width: shower.width * 0.1 + anchors { + right: shower.right + top: shower.top + rightMargin: shower.width*0.15 + topMargin: shower.height*0.25 + } + z: 10 + visible: false + } + + // Manage stuff that changes periodically + Timer { + id: timer + interval: 100 + running: true + repeat: true + onTriggered: { + if(rain.opacity > 0.9 && river.level < 1) { + river.level += 0.01 + } + if(river.level > 0 && fillpipe.opacity > 0.9 && tower.level < 1 && !shower.on) { + river.level -= 0.02 + tower.level += 0.05 + } + if(tower.level > 0 && shower.on) { + tower.level -= 0.02 + } + if(tower.level <= 0 && boatparked.opacity) { + shower.stop() + } + } + } + + GCText { + id: info + visible: true + fontSize: smallSize + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + anchors { + top: parent.top + topMargin: 10 *ApplicationInfo.ratio + right: parent.right + rightMargin: 5 * ApplicationInfo.ratio + left: parent.left + leftMargin: parent.width * 0.50 + } + width: parent.width + wrapMode: Text.WordWrap + z: 100 + onTextChanged: textanim.start() + property string newKey + + SequentialAnimation { + id: textanim + NumberAnimation { + target: info + property: "opacity" + duration: 200 + from: 1 + to: 0 + } + PropertyAction { + target: info + property: 'text' + value: items.dataset[info.newKey] + } + NumberAnimation { + target: info + property: "opacity" + duration: 200 + from: 0 + to: 1 + } + } + + function setText(key) { + if(newKey != key) { + newKey = key + textanim.start() + } + } + } + + Rectangle { + id: infoBg + z: 99 + anchors.fill: info + color: '#8ebfc7' + radius: width * 0.01 + opacity: info.text ? 0.7 : 0 + Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 200 } } + } + + DialogHelp { + id: dialogHelp + onClose: home() + } + + Bar { + id: bar + content: BarEnumContent { value: help | home } + onHelpClicked: { + displayDialog(dialogHelp) + } + onHomeClicked: activity.home() + } + + Bonus { + id:bonus + } + + } +} diff --git a/src/activities/watercycle/resource/apert.wav b/src/activities/watercycle/resource/apert.wav new file mode 100644 index 000000000..b7f4c0d29 Binary files /dev/null and b/src/activities/watercycle/resource/apert.wav differ diff --git a/src/activities/watercycle/resource/boat.svg b/src/activities/watercycle/resource/boat.svg new file mode 100644 index 000000000..33c62b386 --- /dev/null +++ b/src/activities/watercycle/resource/boat.svg @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/boat_parked.svg b/src/activities/watercycle/resource/boat_parked.svg new file mode 100644 index 000000000..241858bba --- /dev/null +++ b/src/activities/watercycle/resource/boat_parked.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/bubble.wav b/src/activities/watercycle/resource/bubble.wav new file mode 100644 index 000000000..16b618a1a Binary files /dev/null and b/src/activities/watercycle/resource/bubble.wav differ diff --git a/src/activities/watercycle/resource/cloud.svg b/src/activities/watercycle/resource/cloud.svg new file mode 100644 index 000000000..fbdf96568 --- /dev/null +++ b/src/activities/watercycle/resource/cloud.svg @@ -0,0 +1,100 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/fillwater.svg b/src/activities/watercycle/resource/fillwater.svg new file mode 100644 index 000000000..17d98bb15 --- /dev/null +++ b/src/activities/watercycle/resource/fillwater.svg @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/harbor1.wav b/src/activities/watercycle/resource/harbor1.wav new file mode 100644 index 000000000..b59d2f676 Binary files /dev/null and b/src/activities/watercycle/resource/harbor1.wav differ diff --git a/src/activities/watercycle/resource/harbor2.wav b/src/activities/watercycle/resource/harbor2.wav new file mode 100644 index 000000000..1b3f3ce3a Binary files /dev/null and b/src/activities/watercycle/resource/harbor2.wav differ diff --git a/src/activities/watercycle/resource/landscape.svg b/src/activities/watercycle/resource/landscape.svg new file mode 100644 index 000000000..0bd990acd --- /dev/null +++ b/src/activities/watercycle/resource/landscape.svg @@ -0,0 +1,5634 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/motor.svg b/src/activities/watercycle/resource/motor.svg new file mode 100644 index 000000000..4af4e167f --- /dev/null +++ b/src/activities/watercycle/resource/motor.svg @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/rain.svg b/src/activities/watercycle/resource/rain.svg new file mode 100644 index 000000000..549a40288 --- /dev/null +++ b/src/activities/watercycle/resource/rain.svg @@ -0,0 +1,584 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/reservoir1.svg b/src/activities/watercycle/resource/reservoir1.svg new file mode 100644 index 000000000..3ae548737 --- /dev/null +++ b/src/activities/watercycle/resource/reservoir1.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/reservoir2.svg b/src/activities/watercycle/resource/reservoir2.svg new file mode 100644 index 000000000..c33cf3628 --- /dev/null +++ b/src/activities/watercycle/resource/reservoir2.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/reservoir3.svg b/src/activities/watercycle/resource/reservoir3.svg new file mode 100644 index 000000000..e7e153f43 --- /dev/null +++ b/src/activities/watercycle/resource/reservoir3.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/river.svg b/src/activities/watercycle/resource/river.svg new file mode 100644 index 000000000..fc16907e2 --- /dev/null +++ b/src/activities/watercycle/resource/river.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/sea.svg b/src/activities/watercycle/resource/sea.svg new file mode 100644 index 000000000..57e842865 --- /dev/null +++ b/src/activities/watercycle/resource/sea.svg @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/shower.svg b/src/activities/watercycle/resource/shower.svg new file mode 100644 index 000000000..9126c04a9 --- /dev/null +++ b/src/activities/watercycle/resource/shower.svg @@ -0,0 +1,217 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/showercold.svg b/src/activities/watercycle/resource/showercold.svg new file mode 100644 index 000000000..c00875bea --- /dev/null +++ b/src/activities/watercycle/resource/showercold.svg @@ -0,0 +1,86 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/showerhot.svg b/src/activities/watercycle/resource/showerhot.svg new file mode 100644 index 000000000..4e3baaa77 --- /dev/null +++ b/src/activities/watercycle/resource/showerhot.svg @@ -0,0 +1,86 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/sky.svg b/src/activities/watercycle/resource/sky.svg new file mode 100644 index 000000000..dcf57ab06 --- /dev/null +++ b/src/activities/watercycle/resource/sky.svg @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/sun.svg b/src/activities/watercycle/resource/sun.svg new file mode 100644 index 000000000..7c611dc5a --- /dev/null +++ b/src/activities/watercycle/resource/sun.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/tuxbath.svg b/src/activities/watercycle/resource/tuxbath.svg new file mode 100644 index 000000000..8cdc1d675 --- /dev/null +++ b/src/activities/watercycle/resource/tuxbath.svg @@ -0,0 +1,362 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/tuxoff.svg b/src/activities/watercycle/resource/tuxoff.svg new file mode 100644 index 000000000..2340886e0 --- /dev/null +++ b/src/activities/watercycle/resource/tuxoff.svg @@ -0,0 +1,182 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/vapor.svg b/src/activities/watercycle/resource/vapor.svg new file mode 100644 index 000000000..6a9fe83b3 --- /dev/null +++ b/src/activities/watercycle/resource/vapor.svg @@ -0,0 +1,168 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/waste.svg b/src/activities/watercycle/resource/waste.svg new file mode 100644 index 000000000..bbc7261b6 --- /dev/null +++ b/src/activities/watercycle/resource/waste.svg @@ -0,0 +1,608 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/wastewater.svg b/src/activities/watercycle/resource/wastewater.svg new file mode 100644 index 000000000..6b3409dfb --- /dev/null +++ b/src/activities/watercycle/resource/wastewater.svg @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 28/10/2008 + + + Cabaraux Stéphane + + + + + Cabaraux Stéphane + + + + + Gnurps + + + + + + + gcompris + watercycle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/watertower.svg b/src/activities/watercycle/resource/watertower.svg new file mode 100644 index 000000000..528e4df17 --- /dev/null +++ b/src/activities/watercycle/resource/watertower.svg @@ -0,0 +1,214 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/watercycle/resource/watertowerfill.svg b/src/activities/watercycle/resource/watertowerfill.svg new file mode 100644 index 000000000..203d7eb40 --- /dev/null +++ b/src/activities/watercycle/resource/watertowerfill.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/activities/watercycle/watercycle.svg b/src/activities/watercycle/watercycle.svg new file mode 100644 index 000000000..594ffe78d --- /dev/null +++ b/src/activities/watercycle/watercycle.svg @@ -0,0 +1,170 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/ApplicationInfo.cpp b/src/core/ApplicationInfo.cpp index eedeeb2fb..76d57dca1 100644 --- a/src/core/ApplicationInfo.cpp +++ b/src/core/ApplicationInfo.cpp @@ -1,228 +1,230 @@ /* GCompris - ApplicationSettingsDefault.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "ApplicationInfo.h" #include #include #include #include #include #include #include #include #include #include #include QQuickWindow *ApplicationInfo::m_window = NULL; ApplicationInfo *ApplicationInfo::m_instance = NULL; ApplicationInfo::ApplicationInfo(QObject *parent): QObject(parent) { m_isMobile = false; #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(SAILFISHOS) m_isMobile = true; #endif #if defined(Q_OS_ANDROID) // Put android before checking linux/unix as it is also a linux m_platform = Android; #elif (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) m_platform = Linux; #elif defined(Q_OS_WIN) m_platform = Windows; #elif defined(Q_OS_MAC) m_platform = MacOSX; #elif defined(Q_OS_IOS) m_platform = Ios; #elif defined(Q_OS_BLACKBERRY) m_platform = Blackberry; #elif defined(SAILFISHOS) m_platform = SailfishOS; #endif QRect rect = qApp->primaryScreen()->geometry(); m_ratio = qMin(qMax(rect.width(), rect.height())/800. , qMin(rect.width(), rect.height())/520.); // calculate a factor for font-scaling, cf. // http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio qreal refDpi = 216.; qreal refHeight = 1776.; qreal refWidth = 1080.; qreal height = qMax(rect.width(), rect.height()); qreal width = qMin(rect.width(), rect.height()); qreal dpi = qApp->primaryScreen()->logicalDotsPerInch(); m_fontRatio = qMax(qreal(1.0), qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth))); m_isPortraitMode = m_isMobile ? rect.height() > rect.width() : false; m_applicationWidth = m_isMobile ? rect.width() : 1120; if (m_isMobile) connect(qApp->primaryScreen(), SIGNAL(physicalSizeChanged(QSizeF)), this, SLOT(notifyPortraitMode())); // Get all symbol fonts to remove them QFontDatabase database; m_excludedFonts = database.families(QFontDatabase::Symbol); // Get fonts from rcc const QStringList fontFilters = {"*.otf", "*.ttf"}; m_fontsFromRcc = QDir(":/gcompris/src/core/resource/fonts").entryList(fontFilters); } ApplicationInfo::~ApplicationInfo() { m_instance = NULL; } void ApplicationInfo::setApplicationWidth(const int newWidth) { if (newWidth != m_applicationWidth) { m_applicationWidth = newWidth; emit applicationWidthChanged(); } } QString ApplicationInfo::getResourceDataPath() { return QString("qrc:/gcompris/data"); } QString ApplicationInfo::getFilePath(const QString &file) { #if defined(Q_OS_ANDROID) return QString("assets:/%1").arg(file); #elif defined(Q_OS_MAC) return QString("%1/rcc/%2").arg(QCoreApplication::applicationDirPath(), file); #else return QString("%1/%2/rcc/%3").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER, file); #endif } QString ApplicationInfo::getAudioFilePath(const QString &file) { QString localeName = getVoicesLocale(ApplicationSettings::getInstance()->locale()); QString filename = file; filename.replace("$LOCALE", localeName); filename.replace("$CA", COMPRESSED_AUDIO); if(file.startsWith('/') || file.startsWith(QLatin1String("qrc:")) || file.startsWith(':')) return filename; else return getResourceDataPath() + '/' + filename; } QString ApplicationInfo::getLocaleFilePath(const QString &file) { QString localeShortName = localeShort(); QString filename = file; filename.replace("$LOCALE", localeShortName); return filename; } QStringList ApplicationInfo::getSystemExcludedFonts() { return m_excludedFonts; } QStringList ApplicationInfo::getFontsFromRcc() { return m_fontsFromRcc; } void ApplicationInfo::notifyPortraitMode() { int width = qApp->primaryScreen()->geometry().width(); int height = qApp->primaryScreen()->geometry().height(); setIsPortraitMode(height > width); } void ApplicationInfo::setIsPortraitMode(const bool newMode) { if (m_isPortraitMode != newMode) { m_isPortraitMode = newMode; emit portraitModeChanged(); } } void ApplicationInfo::setWindow(QQuickWindow *window) { m_window = window; } void ApplicationInfo::screenshot(QString const &path) { QImage img = m_window->grabWindow(); img.save(path); } void ApplicationInfo::notifyFullscreenChanged() { if(ApplicationSettings::getInstance()->isFullscreen()) m_window->showFullScreen(); else m_window->showNormal(); } // return the shortest possible locale name for the given locale, describing // a unique voices dataset QString ApplicationInfo::getVoicesLocale(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); + if(_locale == "C") + _locale = "en_US"; } // locales we have country-specific voices for: if (_locale.startsWith(QLatin1String("pt_BR")) || _locale.startsWith(QLatin1String("zh_CN")) || _locale.startsWith(QLatin1String("zh_TW"))) return QLocale(_locale).name(); // short locale for all the rest: return localeShort(_locale); } QObject *ApplicationInfo::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) /* * Connect the fullscreen change signal to applicationInfo in order to change * the QQuickWindow value */ ApplicationInfo* appInfo = getInstance(); connect(ApplicationSettings::getInstance(), SIGNAL(fullscreenChanged()), appInfo, SLOT(notifyFullscreenChanged())); return appInfo; } void ApplicationInfo::init() { qmlRegisterSingletonType("GCompris", 1, 0, "ApplicationInfo", systeminfoProvider); } diff --git a/src/core/ApplicationSettings.h b/src/core/ApplicationSettings.h index ba1cf3f3d..aabdb8a81 100644 --- a/src/core/ApplicationSettings.h +++ b/src/core/ApplicationSettings.h @@ -1,447 +1,449 @@ /* GCompris - ApplicationSettingsDefault.cpp * * Copyright (C) 2014 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef APPLICATIONSETTINGS_H #define APPLICATIONSETTINGS_H #include #include #include #include #include #include #include #define GC_DEFAULT_LOCALE "system" /** * @class ApplicationSettings * @short Singleton that contains GCompris' persistent settings. * @ingroup infrastructure * * Settings are persisted using QSettings, which stores them in platform * specific locations. * * The settings are subdivided in different groups of settings. * * [General] settings are mostly changeable by users in the DialogConfig * dialog. * * [Admin] and [Internal] settings are not changeable by the * user and used for internal purposes. Should only be changed if you really know * what you are doing. * * The [Favorite] group is auto-generated from the favorite activities * selected by a user. * * Besides these global settings there is one group for each activity that * stores persistent settings. * * Settings defaults are defined in the source code. * * @sa DialogActivityConfig */ class ApplicationSettings : public QObject { Q_OBJECT /* General group */ /** * Whether to show locked activities. * False if in Demo mode, true otherwise. */ Q_PROPERTY(bool showLockedActivities READ showLockedActivities WRITE setShowLockedActivities NOTIFY showLockedActivitiesChanged) /** * Whether audio voices/speech should be enabled. */ Q_PROPERTY(bool isAudioVoicesEnabled READ isAudioVoicesEnabled WRITE setIsAudioVoicesEnabled NOTIFY audioVoicesEnabledChanged) /** * Whether audio effects should be enabled. */ Q_PROPERTY(bool isAudioEffectsEnabled READ isAudioEffectsEnabled WRITE setIsAudioEffectsEnabled NOTIFY audioEffectsEnabledChanged) /** * Whether GCompris should run in fullscreen mode. */ Q_PROPERTY(bool isFullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged) /** * Whether on-screen keyboard should be enabled per default in activities * that use it. */ Q_PROPERTY(bool isVirtualKeyboard READ isVirtualKeyboard WRITE setVirtualKeyboard NOTIFY virtualKeyboardChanged) /** * Locale string for currently active language. */ Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged) /** * Currently selected font. */ Q_PROPERTY(QString font READ font WRITE setFont NOTIFY fontChanged) /** * Whether currently active font is a shipped font (or a system font). * * Updated automatically. * @sa font */ Q_PROPERTY(bool isEmbeddedFont READ isEmbeddedFont WRITE setIsEmbeddedFont NOTIFY embeddedFontChanged) /** * Font Capitalization * * Force all texts to be rendered in UpperCase, LowerCase or MixedCase (default) * @sa font */ Q_PROPERTY(quint32 fontCapitalization READ fontCapitalization WRITE setFontCapitalization NOTIFY fontCapitalizationChanged) /** * Whether downloads/updates of resource files should be done automatically, * without user-interaction. * * Note, that on Android GCompris currently can't distinguish Wifi * from mobile data connections (cf. Qt ticket #30394). */ Q_PROPERTY(bool isAutomaticDownloadsEnabled READ isAutomaticDownloadsEnabled WRITE setIsAutomaticDownloadsEnabled NOTIFY automaticDownloadsEnabledChanged) /** * Minimum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMin READ filterLevelMin WRITE setFilterLevelMin NOTIFY filterLevelMinChanged) /** * Maximum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMax READ filterLevelMax WRITE setFilterLevelMax NOTIFY filterLevelMaxChanged) /** * Whether in demo mode. */ Q_PROPERTY(bool isDemoMode READ isDemoMode WRITE setDemoMode NOTIFY demoModeChanged) /** * Whether kiosk mode is currently active. */ Q_PROPERTY(bool isKioskMode READ isKioskMode WRITE setKioskMode NOTIFY kioskModeChanged) /** * Whether the section selection row is visible in the menu view. */ Q_PROPERTY(bool sectionVisible READ sectionVisible WRITE setSectionVisible NOTIFY sectionVisibleChanged) /** * Current base font-size used for font scaling. * * This setting is the basis for application-wide font-scaling. A value * of 0 means to use the font-size as set by the application. Other values * between @ref baseFontSizeMin and @ref baseFontSizeMax enforce * font-scaling. * * @sa GCText.fontSize baseFontSizeMin baseFontSizeMax */ Q_PROPERTY(int baseFontSize READ baseFontSize WRITE setBaseFontSize NOTIFY baseFontSizeChanged) /** * Minimum allowed value for font-scaling. * * Constant value: -7 */ Q_PROPERTY(int baseFontSizeMin READ baseFontSizeMin CONSTANT) /** * Maximum allowed value for font-scaling. * * Constant value: +7 */ Q_PROPERTY(int baseFontSizeMax READ baseFontSizeMax CONSTANT) // admin group /** * Base-URL for resource downloads. * * @sa DownloadManager */ Q_PROPERTY(QString downloadServerUrl READ downloadServerUrl WRITE setDownloadServerUrl NOTIFY downloadServerUrlChanged) // internal group Q_PROPERTY(quint32 exeCount READ exeCount WRITE setExeCount NOTIFY exeCountChanged) // no group Q_PROPERTY(bool isBarHidden READ isBarHidden WRITE setBarHidden NOTIFY barHiddenChanged) public: /// @cond INTERNAL_DOCS explicit ApplicationSettings(QObject *parent = 0); ~ApplicationSettings(); static void init(); // It is not recommended to create a singleton of Qml Singleton registered // object but we could not found a better way to let us access ApplicationInfo // on the C++ side. All our test shows that it works. static ApplicationSettings *getInstance() { if(!m_instance) { m_instance = new ApplicationSettings(); } return m_instance; } static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); bool showLockedActivities() const { return m_showLockedActivities; } void setShowLockedActivities(const bool newMode) { m_showLockedActivities = newMode; emit showLockedActivitiesChanged(); } bool isAudioVoicesEnabled() const { return m_isAudioVoicesEnabled; } void setIsAudioVoicesEnabled(const bool newMode) { m_isAudioVoicesEnabled = newMode; emit audioVoicesEnabledChanged(); } bool isAudioEffectsEnabled() const { return m_isAudioEffectsEnabled; } void setIsAudioEffectsEnabled(const bool newMode) { m_isAudioEffectsEnabled = newMode; emit audioEffectsEnabledChanged(); } bool isFullscreen() const { return m_isFullscreen; } void setFullscreen(const bool newMode) { - m_isFullscreen = newMode; - emit fullscreenChanged(); + if(m_isFullscreen != newMode) { + m_isFullscreen = newMode; + emit fullscreenChanged(); + } } bool isVirtualKeyboard() const { return m_isVirtualKeyboard; } void setVirtualKeyboard(const bool newMode) { m_isVirtualKeyboard = newMode; emit virtualKeyboardChanged(); } QString locale() const { return m_locale; } void setLocale(const QString newLocale) { m_locale = newLocale; emit localeChanged(); } QString font() const { return m_font; } void setFont(const QString newFont) { m_font = newFont; emit fontChanged(); } bool isEmbeddedFont() const { return m_isEmbeddedFont; } void setIsEmbeddedFont(const bool newIsEmbeddedFont) { m_isEmbeddedFont = newIsEmbeddedFont; emit embeddedFontChanged(); } quint32 fontCapitalization() const { return m_fontCapitalization; } void setFontCapitalization(quint32 newFontCapitalization) { m_fontCapitalization = newFontCapitalization; emit fontCapitalizationChanged(); } bool isAutomaticDownloadsEnabled() const; void setIsAutomaticDownloadsEnabled(const bool newIsAutomaticDownloadsEnabled); quint32 filterLevelMin() const { return m_filterLevelMin; } void setFilterLevelMin(const quint32 newFilterLevelMin) { m_filterLevelMin = newFilterLevelMin; emit filterLevelMinChanged(); } quint32 filterLevelMax() const { return m_filterLevelMax; } void setFilterLevelMax(const quint32 newFilterLevelMax) { m_filterLevelMax = newFilterLevelMax; emit filterLevelMaxChanged(); } bool isDemoMode() const { return m_isDemoMode; } void setDemoMode(const bool newMode); bool isKioskMode() const { return m_isKioskMode; } void setKioskMode(const bool newMode) { m_isKioskMode = newMode; emit kioskModeChanged(); } // Payment API // Call a payment system to sync our demoMode state with it void checkPayment(); // Called by the payment system void bought(const bool isBought) { if(m_isDemoMode != !isBought) { m_isDemoMode = !isBought; emit demoModeChanged(); } } bool sectionVisible() const { return m_sectionVisible; } void setSectionVisible(const bool newMode) { qDebug() << "c++ setSectionVisible=" << newMode; m_sectionVisible = newMode; emit sectionVisibleChanged(); } QString downloadServerUrl() const { return m_downloadServerUrl; } void setDownloadServerUrl(const QString newDownloadServerUrl) { m_downloadServerUrl = newDownloadServerUrl; emit downloadServerUrlChanged(); } quint32 exeCount() const { return m_exeCount; } void setExeCount(const quint32 newExeCount) { m_exeCount = newExeCount; emit exeCountChanged(); } bool isBarHidden() const { return m_isBarHidden; } void setBarHidden(const bool newBarHidden) { m_isBarHidden = newBarHidden; emit barHiddenChanged(); } int baseFontSize() const { return m_baseFontSize; } void setBaseFontSize(const int newBaseFontSize) { m_baseFontSize = qMax(qMin(newBaseFontSize, baseFontSizeMax()), baseFontSizeMin()); emit baseFontSizeChanged(); } int baseFontSizeMin() const { return m_baseFontSizeMin; } int baseFontSizeMax() const { return m_baseFontSizeMax; } protected slots: Q_INVOKABLE void notifyShowLockedActivitiesChanged(); Q_INVOKABLE void notifyAudioVoicesEnabledChanged(); Q_INVOKABLE void notifyAudioEffectsEnabledChanged(); Q_INVOKABLE void notifyFullscreenChanged(); Q_INVOKABLE void notifyVirtualKeyboardChanged(); Q_INVOKABLE void notifyLocaleChanged(); Q_INVOKABLE void notifyFontChanged(); Q_INVOKABLE void notifyFontCapitalizationChanged(); Q_INVOKABLE void notifyEmbeddedFontChanged(); Q_INVOKABLE void notifyAutomaticDownloadsEnabledChanged(); Q_INVOKABLE void notifyFilterLevelMinChanged(); Q_INVOKABLE void notifyFilterLevelMaxChanged(); Q_INVOKABLE void notifyDemoModeChanged(); Q_INVOKABLE void notifyKioskModeChanged(); Q_INVOKABLE void notifySectionVisibleChanged(); Q_INVOKABLE void notifyDownloadServerUrlChanged(); Q_INVOKABLE void notifyExeCountChanged(); Q_INVOKABLE void notifyBarHiddenChanged(); public slots: Q_INVOKABLE bool isFavorite(const QString &activity); Q_INVOKABLE void setFavorite(const QString &activity, bool favorite); Q_INVOKABLE void saveBaseFontSize(); /// @endcond /** * Stores per-activity configuration @p data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @param data Map of configuration data so save. */ Q_INVOKABLE void saveActivityConfiguration(const QString &activity, const QVariantMap &data); /** * Loads per-activity configuration data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @returns Map of configuration items. */ Q_INVOKABLE QVariantMap loadActivityConfiguration(const QString &activity); signals: void showLockedActivitiesChanged(); void audioVoicesEnabledChanged(); void audioEffectsEnabledChanged(); void fullscreenChanged(); void virtualKeyboardChanged(); void localeChanged(); void fontChanged(); void fontCapitalizationChanged(); void embeddedFontChanged(); void automaticDownloadsEnabledChanged(); void filterLevelMinChanged(); void filterLevelMaxChanged(); void demoModeChanged(); void kioskModeChanged(); void sectionVisibleChanged(); void baseFontSizeChanged(); void downloadServerUrlChanged(); void exeCountChanged(); void barHiddenChanged(); private: // Update in configuration the couple {key, value} in the group. template void updateValueInConfig(const QString& group, const QString& key, const T& value); static ApplicationSettings *m_instance; bool m_showLockedActivities; bool m_isAudioVoicesEnabled; bool m_isAudioEffectsEnabled; bool m_isFullscreen; bool m_isVirtualKeyboard; bool m_isAutomaticDownloadsEnabled; bool m_isEmbeddedFont; quint32 m_fontCapitalization; quint32 m_filterLevelMin; quint32 m_filterLevelMax; bool m_defaultCursor; bool m_noCursor; QString m_locale; QString m_font; bool m_isDemoMode; bool m_isKioskMode; bool m_sectionVisible; int m_baseFontSize; const int m_baseFontSizeMin; const int m_baseFontSizeMax; QString m_downloadServerUrl; quint32 m_exeCount; bool m_isBarHidden; QSettings m_config; }; #endif // APPLICATIONSETTINGS_H diff --git a/src/core/DownloadManager.cpp b/src/core/DownloadManager.cpp index 1dcb02658..f1830d9e7 100644 --- a/src/core/DownloadManager.cpp +++ b/src/core/DownloadManager.cpp @@ -1,642 +1,645 @@ /* GCompris - DownloadManager.cpp * * 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 . */ #include "DownloadManager.h" #include "ApplicationSettings.h" #include "ApplicationInfo.h" #include #include #include #include #include #include #include const QString DownloadManager::contentsFilename = QString("Contents"); DownloadManager* DownloadManager::_instance = 0; /* Public interface: */ DownloadManager::DownloadManager() : accessManager(this), serverUrl(ApplicationSettings::getInstance()->downloadServerUrl()) { // Cleanup of previous data directory no more used QDir previousDataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/data"; if(previousDataLocation.exists()) { qDebug() << "Remove previous directory data: " << previousDataLocation; previousDataLocation.removeRecursively(); } } DownloadManager::~DownloadManager() { shutdown(); _instance = 0; } void DownloadManager::shutdown() { qDebug() << "DownloadManager: shutting down," << activeJobs.size() << "active jobs"; abortDownloads(); } // It is not recommended to create a singleton of Qml Singleton registered // object but we could not found a better way to let us access DownloadManager // on the C++ side. All our test shows that it works. // Using the singleton after the QmlEngine has been destroyed is forbidden! DownloadManager* DownloadManager::getInstance() { if (!_instance) _instance = new DownloadManager; return _instance; } QObject *DownloadManager::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void DownloadManager::init() { qmlRegisterSingletonType("GCompris", 1, 0, "DownloadManager", systeminfoProvider); } bool DownloadManager::downloadIsRunning() const { return (activeJobs.size() > 0); } void DownloadManager::abortDownloads() { if (activeJobs.size() > 0) { QMutexLocker locker(&jobsMutex); QMutableListIterator iter(activeJobs); while (iter.hasNext()) { DownloadJob *job = iter.next(); if (job->reply) { disconnect(job->reply, SIGNAL(finished()), this, SLOT(downloadFinished())); disconnect(job->reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); if (job->reply->isRunning()) { qDebug() << "Aborting download job:" << job->url; job->reply->abort(); job->file.close(); job->file.remove(); } delete job->reply; } iter.remove(); } locker.unlock(); emit error(QNetworkReply::OperationCanceledError, "Download cancelled by user"); } } QString DownloadManager::getVoicesResourceForLocale(const QString& locale) const { return QString("data2/voices-" COMPRESSED_AUDIO "/voices-%1.rcc") .arg(ApplicationInfo::getInstance()->getVoicesLocale(locale)); } inline QString DownloadManager::getAbsoluteResourcePath(const QString& path) const { foreach (const QString &base, getSystemResourcePaths()) { if (QFile::exists(base + '/' + path)) return QString(base + '/' + path); } return QString(); } // @FIXME should support a variable subpath length like data2/full.rcc" inline QString DownloadManager::getRelativeResourcePath(const QString& path) const { QStringList parts = path.split('/', QString::SkipEmptyParts); if (parts.size() < 3) return QString(); return QString(parts[parts.size()-3] + '/' + parts[parts.size()-2] + '/' + parts[parts.size()-1]); } bool DownloadManager::haveLocalResource(const QString& path) const { return (!getAbsoluteResourcePath(path).isEmpty()); } bool DownloadManager::updateResource(const QString& path) { if (checkDownloadRestriction()) return downloadResource(path); // check for updates and register else { QString absPath = getAbsoluteResourcePath(path); // automatic download prohibited -> register if available if (!absPath.isEmpty()) return registerResource(absPath); else { qDebug() << "No such local resource and download prohibited:" << path; return false; } } } bool DownloadManager::downloadResource(const QString& path) { qDebug() << "Downloading resource file" << path; DownloadJob* job = new DownloadJob(QUrl(serverUrl.toString() + '/' + path)); { QMutexLocker locker(&jobsMutex); activeJobs.append(job); } if (!download(job)) { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); return false; } return true; } #if 0 // vvv might be helpful later with other use-cases: void DownloadManager::registerLocalResources() { QStringList filenames = getLocalResources(); if (filenames == QStringList()) { qDebug() << "No local resources found"; return; } QList::const_iterator iter; for (iter = filenames.constBegin(); iter != filenames.constEnd(); iter++) registerResource(*iter); } bool DownloadManager::checkForUpdates() { QStringList filenames = getLocalResources(); if (filenames == QStringList()) { qDebug() << "No local resources found"; return true; } if (!checkDownloadRestriction()) { qDebug() << "Can't download with current network connection (" << networkConfiguration.bearerTypeName() << ")!"; return false; } QList::const_iterator iter; DownloadJob *job = new DownloadJob(); for (iter = filenames.constBegin(); iter != filenames.constEnd(); iter++) { QUrl url = getUrlForFilename(*iter); qDebug() << "Scheduling resource for update: " << url; job->queue.append(url); } job->url = job->queue.takeFirst(); { QMutexLocker locker(&jobsMutex); activeJobs.append(job); } if (!download(job)) { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); return false; } return true; } #endif /* Private: */ inline QString DownloadManager::tempFilenameForFilename(const QString &filename) const { return QString(filename).append("_"); } inline QString DownloadManager::filenameForTempFilename(const QString &tempFilename) const { if (tempFilename.endsWith(QLatin1String("_"))) return tempFilename.left(tempFilename.length() - 1); return tempFilename; } bool DownloadManager::download(DownloadJob* job) { QNetworkRequest request; // First download Contents file for verification if not yet done: if (!job->contents.contains(job->url.fileName())) { int len = job->url.fileName().length(); QUrl contentsUrl = QUrl(job->url.toString().remove(job->url.toString().length() - len, len) + contentsFilename); if (!job->knownContentsUrls.contains(contentsUrl)) { // Note: need to track already tried Contents files or we can end // up in an infinite loop if corresponding Contents file does not // exist upstream job->knownContentsUrls.append(contentsUrl); //qDebug() << "Postponing rcc download, first fetching Contents" << contentsUrl; job->queue.prepend(job->url); job->url = contentsUrl; } } QFileInfo fi(getFilenameForUrl(job->url)); // make sure target path exists: QDir dir; if (!dir.exists(fi.path()) && !dir.mkpath(fi.path())) { qDebug() << "Could not create resource path " << fi.path(); emit error(QNetworkReply::ProtocolUnknownError, "Could not create resource path"); return false; } job->file.setFileName(tempFilenameForFilename(fi.filePath())); if (!job->file.open(QIODevice::WriteOnly)) { emit error(QNetworkReply::ProtocolUnknownError, QString("Could not open target file %1").arg(job->file.fileName())); return false; } // start download: request.setUrl(job->url); //qDebug() << "Now downloading" << job->url << "to" << fi.filePath() << "..."; QNetworkReply *reply = accessManager.get(request); job->reply = reply; connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); connect(reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); if (job->url.fileName() != contentsFilename) { connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64))); emit downloadStarted(job->url.toString().remove(0, serverUrl.toString().length())); } return true; } inline DownloadManager::DownloadJob* DownloadManager::getJobByReply(QNetworkReply *r) { QMutexLocker locker(&jobsMutex); for (int i = 0; i < activeJobs.size(); i++) if (activeJobs[i]->reply == r) return activeJobs[i]; return NULL; // should never happen! } void DownloadManager::downloadReadyRead() { QNetworkReply *reply = dynamic_cast(sender()); DownloadJob *job = getJobByReply(reply); job->file.write(reply->readAll()); } inline QString DownloadManager::getFilenameForUrl(const QUrl& url) const { QString relPart = url.toString().remove(0, serverUrl.toString().length()); return QString(getSystemDownloadPath() + relPart); } inline QUrl DownloadManager::getUrlForFilename(const QString& filename) const { return QUrl(serverUrl.toString() + '/' + getRelativeResourcePath(filename)); } inline QString DownloadManager::getSystemDownloadPath() const { return QStandardPaths::writableLocation(QStandardPaths::DataLocation); } inline QStringList DownloadManager::getSystemResourcePaths() const { QStringList results({ getSystemDownloadPath(), #if defined(Q_OS_ANDROID) "assets:", #endif QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + '/' + GCOMPRIS_APPLICATION_NAME }); // Append standard application directories (like /usr/share/applications/) foreach(const QString &path, QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { results.append(path + '/' + GCOMPRIS_APPLICATION_NAME); } return results; } bool DownloadManager::checkDownloadRestriction() const { #if 0 // note: Something like the following can be used once bearer mgmt // has been implemented for android (cf. Qt bug #30394) QNetworkConfiguration::BearerType conn = networkConfiguration.bearerType(); qDebug() << "Bearer type: "<< conn << ": "<< networkConfiguration.bearerTypeName(); if (!ApplicationSettings::getInstance()->isMobileNetworkDownloadsEnabled() && conn != QNetworkConfiguration::BearerEthernet && conn != QNetworkConfiguration::QNetworkConfiguration::BearerWLAN) return false; return true; #endif return ApplicationSettings::getInstance()->isAutomaticDownloadsEnabled() && ApplicationInfo::getInstance()->isDownloadAllowed(); } void DownloadManager::handleError(QNetworkReply::NetworkError code) { Q_UNUSED(code); QNetworkReply *reply = dynamic_cast(sender()); emit error(reply->error(), reply->errorString()); } bool DownloadManager::parseContents(DownloadJob *job) { if (job->file.isOpen()) job->file.close(); if (!job->file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Could not open file " << job->file.fileName(); return false; } /* * We expect the line-syntax, that md5sum and colleagues creates: * * 53f0a3eb206b3028500ca039615c5f84 voices-en.rcc */ QTextStream in(&job->file); while (!in.atEnd()) { QString line = in.readLine(); QStringList parts = line.split(' ', QString::SkipEmptyParts); if (parts.size() != 2) { qWarning() << "Invalid format of Contents file!"; return false; } job->contents[parts[1]] = parts[0]; //qDebug() << "Contents: " << parts[1] << " -> " << parts[0]; } job->file.close(); return true; } bool DownloadManager::checksumMatches(DownloadJob *job, const QString& filename) const { Q_ASSERT(job->contents != (QMap())); if (!QFile::exists(filename)) return false; QString basename = QFileInfo(filename).fileName(); if (!job->contents.contains(basename)) return false; QFile file(filename); file.open(QIODevice::ReadOnly); QCryptographicHash fileHasher(hashMethod); if (!fileHasher.addData(&file)) { qWarning() << "Could not read file for hashing: " << filename; return false; } file.close(); QByteArray fileHash = fileHasher.result().toHex(); //qDebug() << "Checking file-hash ~ contents-hash: " << fileHash << " ~ " << job->contents[basename]; return (fileHash == job->contents[basename]); } void DownloadManager::unregisterResource_locked(const QString& filename) { if (!QResource::unregisterResource(filename)) qDebug() << "Error unregistering resource file" << filename; else { qDebug() << "Successfully unregistered resource file" << filename; registeredResources.removeOne(filename); } } inline bool DownloadManager::isRegistered(const QString& filename) const { return (registeredResources.indexOf(filename) != -1); } bool DownloadManager::registerResource(const QString& filename) { QMutexLocker locker(&rcMutex); if (isRegistered(filename)) unregisterResource_locked(filename); if (!QResource::registerResource(filename)) { qDebug() << "Error registering resource file" << filename; return false; } else { qDebug() << "Successfully registered resource" << filename; registeredResources.append(filename); + locker.unlock(); /* note: we unlock before emitting to prevent + * potential deadlocks */ + emit resourceRegistered(getRelativeResourcePath(filename)); QString v = getVoicesResourceForLocale( ApplicationSettings::getInstance()->locale()); if (v == getRelativeResourcePath(filename)) emit voicesRegistered(); return false; } } bool DownloadManager::isDataRegistered(const QString& data) const { QString res = QString(":/gcompris/data/%1").arg(data); return !QDir(res).entryList().empty(); } bool DownloadManager::areVoicesRegistered() const { QString resource = QString("voices-" COMPRESSED_AUDIO "/%1"). arg(ApplicationInfo::getInstance()->getVoicesLocale(ApplicationSettings::getInstance()->locale())); return isDataRegistered(resource); } void DownloadManager::downloadFinished() { QNetworkReply* reply = dynamic_cast(sender()); DownloadFinishedCode code = Success; DownloadJob *job = getJobByReply(reply); if (job->file.isOpen()) { job->file.flush(); // note: important, or checksums might be wrong! job->file.close(); } if (reply->error() && job->file.exists()) job->file.remove(); else { // active temp file QString tFilename = filenameForTempFilename(job->file.fileName()); if (QFile::exists(tFilename)) QFile::remove(tFilename); if (!job->file.rename(tFilename)) qWarning() << "Could not rename temporary file to" << tFilename; } QString targetFilename = getFilenameForUrl(job->url); if (job->url.fileName() == contentsFilename) { // Contents if (reply->error()) { qWarning() << "Error downloading Contents from" << job->url << ":" << reply->error() << ":" << reply->errorString(); // note: errorHandler() emit's error! code = Error; goto outError; } //qDebug() << "Download of Contents finished successfully: " << job->url; if (!parseContents(job)) { qWarning() << "Invalid format of Contents file" << job->url; emit error(QNetworkReply::UnknownContentError, "Invalid format of Contents file"); code = Error; goto outError; } } else { // RCC file if (reply->error()) { qWarning() << "Error downloading RCC file from " << job->url << ":" << reply->error() << ":" << reply->errorString(); // note: errorHandler() emit's error! code = Error; // register already existing files: if (QFile::exists(targetFilename)) registerResource(targetFilename); } else { qDebug() << "Download of RCC file finished successfully: " << job->url; if (!checksumMatches(job, targetFilename)) { qWarning() << "Checksum of downloaded file does not match: " << targetFilename; emit error(QNetworkReply::UnknownContentError, QString("Checksum of downloaded file does not match: %1") .arg(targetFilename)); code = Error; } else registerResource(targetFilename); } } // try next: while (!job->queue.isEmpty()) { job->url = job->queue.takeFirst(); QString relPath = getRelativeResourcePath(getFilenameForUrl(job->url)); // check in each resource-path for an up2date rcc file: foreach (const QString &base, getSystemResourcePaths()) { QString filename = base + '/' + relPath; if (QFile::exists(filename) && checksumMatches(job, filename)) { // file is up2date, register! necessary: qDebug() << "Local resource is up-to-date:" << QFileInfo(filename).fileName(); registerResource(filename); code = NoChange; break; } } if (code != NoChange) // nothing is up2date locally -> download it if (download(job)) goto outNext; } // none left, DownloadJob finished if (job->file.isOpen()) job->file.close(); { // note: must remove before signalling downloadFinished(), otherwise race condition for the Qt.quit() case QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); } emit downloadFinished(code); delete reply; delete job; return; outError: if (job->url.fileName() == contentsFilename) { // if we could not download the contents file register local existing // files for outstanding jobs: QUrl nUrl; while (!job->queue.isEmpty()) { nUrl = job->queue.takeFirst(); QString relPath = getRelativeResourcePath(getFilenameForUrl(nUrl)); foreach (const QString &base, getSystemResourcePaths()) { QString filename = base + '/' + relPath; if (QFile::exists(filename)) registerResource(filename); } } } if (job->file.isOpen()) job->file.close(); { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); } delete reply; delete job; return; outNext: // next sub-job started delete reply; return; } QStringList DownloadManager::getLocalResources() { QStringList result; foreach (const QString &path, getSystemResourcePaths()) { QDir dir(path); if (!dir.exists(path) && !dir.mkpath(path)) { qWarning() << "Could not create resource path " << path; continue; } QDirIterator it(dir, QDirIterator::Subdirectories); while (it.hasNext()) { QString filename = it.next(); QFileInfo fi = it.fileInfo(); if (fi.isFile() && (filename.endsWith(QLatin1String(".rcc")))) result.append(filename); } } return result; } diff --git a/src/core/GCComboBox.qml b/src/core/GCComboBox.qml index 341cf36a7..88fbe5da0 100644 --- a/src/core/GCComboBox.qml +++ b/src/core/GCComboBox.qml @@ -1,304 +1,309 @@ /* 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.2 import QtQuick.Controls 1.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: "black" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: mouseArea.pressed ? "#87ff5c" : "#ffe85c" } GradientStop { position: 1 ; color: mouseArea.pressed ? "#44ff00" : "#f8d600" } } // Current value of combobox GCText { id: currentTextBox anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: currentText fontSize: mediumSize } MouseArea { id: mouseArea anchors.fill: parent onReleased: { popup.visible = true } } } - GCText { - id: labelText - text: label - fontSize: mediumSize - wrapMode: Text.WordWrap + Item { + width: button.width + height: parent.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 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: background.currentActivity.Keys.onPressed(event) 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)) 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.right: textValue.left anchors.verticalCenter: parent.verticalCenter anchors.rightMargin: 10 sourceSize.width: (gridview.elementHeight*0.8) * ApplicationInfo.ratio } 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/core.js b/src/core/core.js index c106e1db7..4dc5247fb 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -1,239 +1,244 @@ /* GCompris - core.js * * Copyright (C) 2014 * Authors: * Bruno Coudoin * Holger Kaelberer * 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 . */ /** * @file * Contains commonly used javascript methods. * @ingroup components * * FIXME: how to include this file in kgenapidox's output? */ .pragma library .import QtQml 2.2 as Qml .import GCompris 1.0 as GCompris /** * Shuffle the array @p o and returns it. * * @param o Array to shuffle. * @returns A shuffled array. */ function shuffle(o) { for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); return o; } /** * Return the filename of the audio voices file for the passed letters * (or a number) @p c * * A letter maybe a digraph or more characters (a pair of characters used * to write one phoneme). In this case the result have the form U0066U0068.ogg * * The returned audio file. has the suffix .ogg * * @param c Letter or number character. * @return A filename for the audio file for the passed letter @p c of the * form U0033.ogg */ function getSoundFilenamForChar(c) { var results = '' for(var i = 0; i < c.length; ++i) { var result = "U"; var codeHex = c.toLowerCase().charCodeAt(i).toString(16).toUpperCase(); while (codeHex.length < 4) { codeHex = "0" + codeHex; } results += "U" + codeHex } results += ".$CA"; return results; } /** * Create and present a GCDialog with the given parameters * * Instantiates a GCDialog object dynamically as child of the passed * parent object. After one of the buttons passed in the buttonHandler parameter * has been pressed, the dialog will be closed and destroyed automatically. * * @param parent QML parent object * @param informativeText Informative text * @param button1Text the Label of the first button * @param button1Callback Callback handler for the first button * @param button2Text Label of the second button * @param button2Callback Callback handler for the second button * @param closeCallback Callback handler for the close button * @returns The GCDialog object upon success, null otherwise * * @sa GCDialog */ function showMessageDialog(parent, informativeText, button1Text, button1Callback, button2Text, button2Callback, closeCallback) { var qmlStr = 'import QtQuick 2.0\n' + 'GCDialog {\n' + ' message: "' + informativeText + '"\n' + ' button1Text: "' + button1Text + '"\n' + ' button2Text: "' + button2Text + '"\n' + ' }\n'; var dialog = null; try { dialog = Qt.createQmlObject(qmlStr, parent); if(button1Callback) dialog.button1Hit.connect(button1Callback); if(button2Callback) dialog.button2Hit.connect(button2Callback); if(closeCallback) dialog.close.connect(closeCallback); dialog.start(); } catch (e) { console.error("core.js: Error creating a MessageDialog: " + e); if (dialog) dialog.destroy(); return null; } return dialog; } /** * Destroy dialog @p dialog * * @param dialog A dynamically created GCDialog or DownloadDialog */ function destroyDialog(dialog) { if (dialog) { dialog.stop(); dialog.destroy(); } } var downloadDialogComponent = null; /** * Create and present a DownloadDialog with the given parameters. * * Instantiates a DownloadDialog object dynamically as child of the passed * parent object. The DownloadDialog.dynamic property will be set, and the * dialog will be destroyed dynamically after closing. * * @param parent QML parent object * @param properties Object with property-value pairs used to parametrize the new * object. Used directly as properties parameter of * Component.createObject() * @returns A newly created DownloadDialog object upon success, null * otherwise. */ function showDownloadDialog(parent, properties) { var dialog = null; try { if (!downloadDialogComponent) { downloadDialogComponent = Qt.createComponent("qrc:/gcompris/src/core/DownloadDialog.qml"); if (downloadDialogComponent.status != Qml.Component.Ready) { throw new Error("Error creating DownloadDialog component: " + downloadDialogComponent.errorString()); downloadDialogComponent = null; } } properties.dynamic = true; dialog = downloadDialogComponent.createObject( parent, properties); dialog.main = parent dialog.start(); } catch (e) { console.error("core.js: Error creating a DownloadDialog: " + e); if (dialog) dialog.destroy(); return null; } //console.log("created DownloadDialog " + dialog); return dialog; } /** * Helper checking for availability of audio voices for the current locale and * informing the user in case they're missing. * * Can be used by acitivities that depend on audio voices to inform the user * of missing resources during startup. * * @param parent Parent QML object. */ function checkForVoices(parent) { if (!GCompris.DownloadManager.areVoicesRegistered()) { showMessageDialog(parent, qsTr("Missing sound files!") + '\n' + qsTr("This activity uses language sound files, that are not yet installed on your system.") + '\n' + qsTr("For downloading the needed sound files go to the preferences dialog."), "", null, "", null, null); } } var aboutToQuit = false; /** * Central function for quitting GCompris. * * Should be used everywhere instead of Qt.quit(), warning in case of running * downloadloads and showing a confirmation dialog on mobile devices. * Call Qt.quit() itself upon confirmation. * * @param parent QML parent object used for the dynamic dialog. */ function quit(parent) { if (aboutToQuit) // don't execute concurrently return; aboutToQuit = true; GCompris.ApplicationInfo.abandonAudioFocus() if (GCompris.DownloadManager.downloadIsRunning()) { var dialog = showDownloadDialog(parent, { text: qsTr("Download in progress") + '\n' + qsTr("Download in progress.
'Abort' it to quit immediately."), autohide: true, reportError: false, reportSuccess: false, backgroundButtonVisible: false }); dialog.finished.connect(function() {Qt.quit();}); } else if (GCompris.ApplicationInfo.isMobile) { // prevent the user from quitting accidentially by clicking back too often: showMessageDialog(parent, qsTr("Quit?") + '\n' + qsTr("Do you really want to quit GCompris?"), qsTr("Yes"), function() { Qt.quit(); }, qsTr("No"), function() { aboutToQuit = false; }, function() { aboutToQuit = false; } ); } else Qt.quit(); } + +function isLeftToRightLocale(locale) { + var localeShort = GCompris.ApplicationInfo.getVoicesLocale(locale) + return (localeShort != "ar" && localeShort != "he"); +} diff --git a/tools/menus/resource/chess_movelearn.svg b/tools/menus/resource/chess_movelearn.svg deleted file mode 100644 index ad19f740e..000000000 --- a/tools/menus/resource/chess_movelearn.svg +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/menus/resource/chess_partyend.svg b/tools/menus/resource/chess_partyend.svg deleted file mode 100644 index fce5dfeaf..000000000 --- a/tools/menus/resource/chess_partyend.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/wordlist-xml2json.pl b/tools/wordlist-xml2json.pl index d8fd5dee4..588086d19 100644 --- a/tools/wordlist-xml2json.pl +++ b/tools/wordlist-xml2json.pl @@ -1,81 +1,95 @@ #!/usr/bin/perl # # wordlist_xml2json.pl # -# Copyright (C) 2014 Holger Kaelberer +# Copyright (C) 2014 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 . +# # Transforms GCompris' wordlist xml syntax in a corresponding json # document. Reads from stdin, writes to stdout (UTF8 encoded). # # It produces the following json format: # # { "name":"default-gd", # "description":"Gàidhlig", # "locale":"gd", # "levels":[ { "level":1, # "speed":150, <-- optional # "fallspeed":7000, <-- optional # "sublevels":10, <-- optional # "words":["a","jim", "beam", ... ]}, <-- mandatory # { "level":2, ... } # ] # } # # Example usage: # ./wordlist_xml2json.php default-en.json # use XML::Simple; use Data::Dumper; use JSON; use utf8; use Encode; binmode STDIN, ":utf8"; binmode STDOUT, ":utf8"; binmode STDERR, ":utf8"; my $xml_str; while () { $xml_str .= encode('utf8', $_); } my $xml = XMLin($xml_str); my $obj = {}; # Wordlist attributes: $obj->{name} = $xml->{Wordlist}->{name} if (exists($xml->{Wordlist}->{name})); $obj->{locale} = $xml->{Wordlist}->{locale}; $obj->{description} = $xml->{Wordlist}->{description}; $obj->{name} = $xml->{Wordlist}->{name}; # levels $obj->{levels} = (); my $levels; if (ref($xml->{Wordlist}->{level}) eq "ARRAY") { $levels = $xml->{Wordlist}->{level}; } else { # assume scalar for single level files: $levels = [$xml->{Wordlist}->{level}]; } foreach $l (@{$levels}) { $l->{level} = $l->{value}; delete $l->{value}; my $wordarr = []; foreach $word (split('\n', $l->{content})) { $word =~ s/^\s+//; $word =~ s/\s+$//; push(@$wordarr, $word) if (length($word) > 0); } $l->{words} = $wordarr; delete $l->{content}; push (@{$obj->{levels}}, $l); } my $json = JSON->new->allow_nonref->pretty; my $json_text = $json->encode( $obj ); print $json_text; #EOF