1
/* gcompris - reading.c
3
* Copyright (C) 2000, 2008 Bruno Coudoin
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 3 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, see <http://www.gnu.org/licenses/>.
21
#include "gcompris/gcompris.h"
24
#define SOUNDLISTFILE PACKAGE
25
#define MAXWORDSLENGTH 50
29
static GcomprisWordlist *gc_wordlist = NULL;
31
static GcomprisBoard *gcomprisBoard = NULL;
33
static gint drop_items_id = 0;
34
static gint next_level_timer = 0;
36
static gchar *textToFind = NULL;
37
static gint textToFindIndex = 0;
38
#define NOT_THERE -1000
40
static GooCanvasItem *boardRootItem = NULL;
46
MODE_HORIZONTAL_RTL = 2
48
static Mode currentMode = MODE_VERTICAL;
50
/* Store the moving words */
52
GooCanvasItem *rootItem;
53
GooCanvasItem *overwriteItem;
57
static LettersItem previousFocus;
58
static LettersItem toDeleteFocus;
61
/* Define the page area where text can be displayed */
66
#define BASE_CX BASE_X1+(BASE_X2-BASE_X1)/2
68
static gint current_x;
69
static gint current_y;
70
static gint numberOfLine;
71
static gint font_size;
72
static gint interline;
75
static void start_board (GcomprisBoard *agcomprisBoard);
76
static void pause_board (gboolean pause);
77
static void end_board (void);
78
static gboolean is_our_board (GcomprisBoard *gcomprisBoard);
79
static void set_level (guint level);
80
static int wait_for_ready;
83
static gboolean reading_create_item(GooCanvasItem *parent);
84
static gint reading_drop_items (void);
85
static void reading_destroy_all_items(void);
86
static gint reading_next_level(void);
87
static void reading_config_start(GcomprisBoard *agcomprisBoard,
88
GcomprisProfile *aProfile);
89
static void reading_config_stop(void);
91
static void player_win(void);
92
static void player_loose(void);
93
static gchar *get_random_word(const gchar *except);
94
static GooCanvasItem *display_what_to_do(GooCanvasItem *parent);
95
static void ask_ready(gboolean status);
96
static void ask_yes_no(void);
97
static gboolean item_event_valid (GooCanvasItem *item,
98
GooCanvasItem *target,
99
GdkEventButton *event,
102
static guint32 fallSpeed = 0;
104
/* Description of this plugin */
105
static BoardPlugin menu_bp =
110
"Read a list of words and then work out if the given word is in it",
111
"Bruno Coudoin <bruno.coudoin@free.fr>",
125
reading_config_start,
130
* Main entry point mandatory for each Gcompris's game
131
* ---------------------------------------------------
135
GET_BPLUGIN_INFO(reading)
138
* in : boolean TRUE = PAUSE : FALSE = UNPAUSE
141
static void pause_board (gboolean pause)
143
// after the bonus is ended, the board is unpaused, but we must wait for
144
// the player to be ready (this board does not use the same framework as others)
148
if(gcomprisBoard==NULL)
154
gtk_timeout_remove (drop_items_id);
161
reading_drop_items();
168
static void start_board (GcomprisBoard *agcomprisBoard)
170
GHashTable *config = gc_db_get_board_conf();
172
gc_locale_set(g_hash_table_lookup( config, "locale"));
174
g_hash_table_destroy(config);
176
if(agcomprisBoard!=NULL)
178
gcomprisBoard=agcomprisBoard;
180
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
181
"readingh/reading-bg.svgz");
182
wait_for_ready = TRUE;
185
gcomprisBoard->level = 1;
186
gcomprisBoard->maxlevel = 9;
187
gc_bar_set(GC_BAR_CONFIG|GC_BAR_LEVEL);
188
gc_bar_location(BOARDWIDTH-240, -1, 0.7);
189
PangoFontDescription *font_medium = pango_font_description_from_string(gc_skin_font_board_medium);
190
font_size = PANGO_PIXELS(pango_font_description_get_size (font_medium));
191
interline = (int) (1.5*font_size);
193
PangoContext *pango_context = gtk_widget_get_pango_context (GTK_WIDGET(agcomprisBoard->canvas));
195
PangoFontMetrics* pango_metrics = pango_context_get_metrics (pango_context,
197
pango_language_from_string (gc_locale_get()));
198
pango_font_description_free(font_medium);
200
int ascent = PANGO_PIXELS(pango_font_metrics_get_ascent (pango_metrics));
201
int descent = PANGO_PIXELS(pango_font_metrics_get_descent (pango_metrics));
203
pango_font_metrics_unref(pango_metrics);
205
interline = ascent + descent;
207
g_warning ("Font to display words have size %d ascent : %d, descent : %d.\n Set inerline to %d",
208
font_size, ascent, descent, interline);
210
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-$LOCALE.xml");
214
/* Fallback to english */
215
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-en.xml");
219
gcomprisBoard = NULL;
220
gc_dialog(_("Error: We can't find\na list of words to play this game.\n"), gc_board_end);
226
currentMode=MODE_VERTICAL; // Default mode
227
if(gcomprisBoard->mode && g_strcasecmp(gcomprisBoard->mode, "horizontal")==0)
229
if (pango_unichar_direction(g_utf8_get_char(gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level))) == PANGO_DIRECTION_RTL)
230
currentMode=MODE_HORIZONTAL_RTL;
232
currentMode=MODE_HORIZONTAL;
235
reading_next_level();
243
if(gcomprisBoard!=NULL)
246
reading_destroy_all_items();
249
if (gc_wordlist != NULL){
250
gc_wordlist_free(gc_wordlist);
256
gcomprisBoard = NULL;
260
set_level (guint level)
263
if(gcomprisBoard!=NULL)
265
gcomprisBoard->level=level;
266
reading_next_level();
271
is_our_board (GcomprisBoard *gcomprisBoard)
275
if(g_strcasecmp(gcomprisBoard->type, "reading")==0)
277
/* Set the plugin entry */
278
gcomprisBoard->plugin=&menu_bp;
287
/*-------------------------------------------------------------------------------*/
288
/*-------------------------------------------------------------------------------*/
289
/*-------------------------------------------------------------------------------*/
290
/*-------------------------------------------------------------------------------*/
292
/* set initial values for the next level */
293
static gint reading_next_level()
296
gc_bar_set_level(gcomprisBoard);
300
reading_destroy_all_items();
302
boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
307
fallSpeed = 1400-gcomprisBoard->level*120;
309
if(currentMode==MODE_VERTICAL)
312
numberOfLine = 7 + gcomprisBoard->level;
317
numberOfLine = 2 + gcomprisBoard->level;
320
current_y = BASE_Y1 - 2 * interline;
322
gcomprisBoard->number_of_sublevel = 1;
323
gcomprisBoard->sublevel = 1;
325
display_what_to_do(boardRootItem);
330
/* Destroy all the items */
332
reading_destroy_all_items()
336
gtk_timeout_remove (drop_items_id);
340
if (next_level_timer) {
341
gtk_timeout_remove (next_level_timer);
345
if(boardRootItem!=NULL)
346
goo_canvas_item_remove(boardRootItem);
348
boardRootItem = NULL;
349
previousFocus.rootItem = NULL;
350
toDeleteFocus.rootItem = NULL;
352
if (textToFind!=NULL)
359
static GooCanvasItem *
360
display_what_to_do(GooCanvasItem *parent)
366
/* Load the text to find */
368
textToFind = get_random_word(NULL);
370
g_assert(textToFind != NULL);
372
/* Decide now if this time we will display the text to find */
373
/* Use this formula to have a better random number see 'man 3 rand' */
374
if(g_random_boolean())
375
textToFindIndex = g_random_int_range(0, numberOfLine);
377
textToFindIndex = NOT_THERE;
379
goo_canvas_text_new (parent,
380
_("Please, check if the word"),
385
"font", gc_skin_font_board_medium,
386
"fill-color", "black",
389
goo_canvas_text_new (parent,
392
(double) base_Y + 30,
395
"font", gc_skin_font_board_big,
396
"fill-color", "blue",
399
goo_canvas_text_new (parent,
400
_("is being displayed"),
402
(double) base_Y + 60,
405
"font", gc_skin_font_board_medium,
406
"fill-color", "black",
414
reading_create_item(GooCanvasItem *parent)
416
gint anchor = GTK_ANCHOR_CENTER;
419
g_assert(textToFind!=NULL);
421
if(toDeleteFocus.rootItem)
423
goo_canvas_item_remove(toDeleteFocus.rootItem);
424
toDeleteFocus.rootItem = NULL;
427
if(previousFocus.rootItem)
429
g_object_set (previousFocus.overwriteItem,
430
"visibility", GOO_CANVAS_ITEM_VISIBLE,
432
toDeleteFocus.rootItem = previousFocus.rootItem;
437
goo_canvas_item_remove(toDeleteFocus.rootItem);
438
toDeleteFocus.rootItem = NULL;
445
if(textToFindIndex!=0)
447
word = get_random_word(textToFind);
451
word = g_strdup(textToFind);
456
gc_dialog(_("We skip this level because there are not enough word in the list!"),
457
(DialogBoxCallBack)reading_next_level);
458
gcomprisBoard->level++;
459
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
460
gcomprisBoard->level = gcomprisBoard->maxlevel;
464
if(textToFindIndex>=0)
467
previousFocus.rootItem = \
468
goo_canvas_group_new (parent,
471
goo_canvas_item_translate(previousFocus.rootItem,
475
if(currentMode==MODE_HORIZONTAL)
476
anchor=GTK_ANCHOR_WEST;
477
else if (currentMode==MODE_HORIZONTAL_RTL)
478
anchor=GTK_ANCHOR_EAST;
480
previousFocus.item = \
481
goo_canvas_text_new (previousFocus.rootItem,
487
"font", gc_skin_font_board_medium,
488
"fill-color", "black",
492
gchar *oldword = g_strdup_printf("<span foreground=\"black\" background=\"black\">%s</span>", word);
496
previousFocus.overwriteItem = \
497
goo_canvas_text_new (previousFocus.rootItem,
503
"font", gc_skin_font_board_medium,
508
g_object_set (previousFocus.overwriteItem,
509
"visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
511
// Calculate the next spot
512
if(currentMode==MODE_VERTICAL)
514
current_y += interline;
517
else if (currentMode==MODE_HORIZONTAL_RTL)
519
GooCanvasBounds bounds;
520
goo_canvas_item_get_bounds(previousFocus.rootItem, &bounds);
522
// Are we out of bound
523
if(bounds.x1<BASE_X1)
525
// Do the line Wrapping
526
goo_canvas_item_translate(previousFocus.rootItem, BASE_X2-bounds.x2, interline);
527
current_y += interline;
531
current_x -= bounds.x2-bounds.x1 + font_size;
536
GooCanvasBounds bounds;
538
goo_canvas_item_get_bounds(previousFocus.rootItem, &bounds);
540
// Are we out of bound
541
if(bounds.x2 > BASE_X2)
543
// Do the line Wrapping
544
goo_canvas_item_translate(previousFocus.rootItem,
545
BASE_X1-bounds.x1, interline);
546
current_y += interline;
550
current_x += bounds.x2 - bounds.x1 + font_size;
557
* This is called on a low frequency and is used to display new items
561
reading_drop_items ()
564
if(reading_create_item(boardRootItem))
565
drop_items_id = gtk_timeout_add (fallSpeed,
566
(GtkFunction) reading_drop_items, NULL);
570
static GooCanvasItem *
571
addBackground(GooCanvasItem *parent,
574
GooCanvasBounds bounds;
577
goo_canvas_item_get_bounds (item, &bounds);
579
return(goo_canvas_rect_new (parent,
582
bounds.x2 - bounds.x1 + gap*2,
583
bounds.y2 - bounds.y1 + gap*2,
584
"stroke_color_rgba", 0xFFFFFFFFL,
585
"fill_color_rgba", 0XD5C393FFL,
586
"line-width", (double) 2,
587
"radius-x", (double) 10,
588
"radius-y", (double) 10,
593
ask_ready(gboolean status)
595
static GooCanvasItem *item1 = NULL;
596
static GooCanvasItem *item2 = NULL;
597
double x_offset = 560;
598
double y_offset = 260;
605
gc_item_focus_remove(item1, NULL);
606
gc_item_focus_remove(item2, item1);
609
goo_canvas_item_remove(item1);
612
goo_canvas_item_remove(item2);
619
/*----- READY -----*/
620
item2 = goo_canvas_text_new (boardRootItem,
626
"font", gc_skin_font_board_big,
627
"fill-color", "white",
630
g_signal_connect(item2, "button-press-event",
631
(GtkSignalFunc) item_event_valid,
634
item1 = addBackground(boardRootItem, item2);
636
g_signal_connect(item1, "button-press-event",
637
(GtkSignalFunc) item_event_valid,
639
gc_item_focus_init(item1, NULL);
640
gc_item_focus_init(item2, item1);
641
goo_canvas_item_raise(item2, NULL);
647
GooCanvasItem *item1;
648
GooCanvasItem *item2;
649
double x_offset = 560;
650
double y_offset = 260;
658
goo_canvas_text_new (boardRootItem,
664
"font", gc_skin_font_board_big,
665
"fill-color", "white",
668
item1 = addBackground(boardRootItem, item2);
670
g_signal_connect(item2, "button-press-event",
671
(GtkSignalFunc) item_event_valid,
673
g_signal_connect(item1, "button-press-event",
674
(GtkSignalFunc) item_event_valid,
677
gc_item_focus_init(item1, NULL);
678
gc_item_focus_init(item2, item1);
679
goo_canvas_item_raise(item2, NULL);
685
goo_canvas_text_new (boardRootItem,
686
_("No, it was not there"),
691
"font", gc_skin_font_board_big,
692
"fill-color", "white",
695
item1 = addBackground(boardRootItem, item2);
697
g_signal_connect(item2, "button-press-event",
698
(GtkSignalFunc) item_event_valid,
700
g_signal_connect(item1, "button-press-event",
701
(GtkSignalFunc) item_event_valid,
704
gc_item_focus_init(item1, NULL);
705
gc_item_focus_init(item2, item1);
706
goo_canvas_item_raise(item2, NULL);
714
wait_for_ready = TRUE;
715
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
716
/* Try the next level */
717
gcomprisBoard->level++;
718
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
719
gcomprisBoard->level = gcomprisBoard->maxlevel;
721
next_level_timer = g_timeout_add(3000, (GtkFunction)reading_next_level, NULL);
730
wait_for_ready = TRUE;
732
/* Report what was wrong in the log */
733
expected = g_strdup_printf(_("The word to find was '%s'"), textToFind);
735
if(textToFindIndex == NOT_THERE)
736
got = g_strdup_printf(_("But it was not displayed"));
738
got = g_strdup_printf(_("And it was displayed"));
740
gc_log_set_comment (gcomprisBoard, expected, got);
745
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
747
next_level_timer = g_timeout_add(3000, (GtkFunction)reading_next_level, NULL);
750
/* Callback for the yes and no buttons */
752
item_event_valid (GooCanvasItem *item,
753
GooCanvasItem *target,
754
GdkEventButton *event,
758
if (((char *)data)[0]=='R')
761
wait_for_ready = FALSE;
765
else if(!wait_for_ready) {
766
if ((((char *)data)[0]=='Y' && textToFindIndex == -1)
767
|| (((char *)data)[0]=='N' && textToFindIndex == NOT_THERE))
780
/** Return a random word from a set of text file depending on
781
* the current level and language
783
* \param except: if non NULL, never return this value
785
* \return a random word from. must be freed by the caller
788
get_random_word(const gchar* except)
793
word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
796
while(strcmp(except, word)==0)
805
word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
812
/* ************************************* */
813
/* * Configuration * */
814
/* ************************************* */
817
/* ======================= */
818
/* = config_start = */
819
/* ======================= */
821
static GcomprisProfile *profile_conf;
822
static GcomprisBoard *board_conf;
824
static void save_table (gpointer key,
828
gc_db_set_board_conf ( profile_conf,
834
static void conf_ok(GHashTable *table)
842
g_hash_table_foreach(table, (GHFunc) save_table, NULL);
850
config = gc_db_get_board_conf();
854
gc_locale_set(g_hash_table_lookup( config, "locale"));
857
g_hash_table_destroy(config);
859
reading_next_level();
870
reading_config_start(GcomprisBoard *agcomprisBoard,
871
GcomprisProfile *aProfile)
873
GcomprisBoardConf *conf;
874
board_conf = agcomprisBoard;
875
profile_conf = aProfile;
880
gchar *label = g_strdup_printf(_("<b>%s</b> configuration\n for profile <b>%s</b>"),
881
agcomprisBoard->name,
882
aProfile? aProfile->name: "");
884
conf = gc_board_config_window_display( label,
885
(GcomprisConfCallback )conf_ok);
889
/* init the combo to previously saved value */
890
GHashTable *config = gc_db_get_conf( profile_conf, board_conf);
892
gchar *locale = g_hash_table_lookup( config, "locale");
894
gc_board_config_combo_locales(conf, locale);
895
gc_board_config_wordlist(conf, "wordsgame/default-$LOCALE.xml");
899
/* ======================= */
900
/* = config_stop = */
901
/* ======================= */
903
reading_config_stop()