8e7299b43a95db948733674045d345d14dc285b2
[wordblah.git] / wordblox_player.c
1 #include <gtk/gtk.h>
2
3 #include "constantstrings.h"
4 #include "wordblox_resource.c"
5 #include "wordblox.h"
6
7 GtkWidget *window;
8 GtkListStore *across_store;
9 GtkListStore *down_store;
10
11 MainPlayerData app_data;
12
13 /* update the clue items store */
14 void update_clue_items ()
15 {
16 gtk_list_store_clear (across_store);
17 gtk_list_store_clear (down_store);
18
19 /* if the puzzle is loaded */
20 if (app_data.is_loaded == true)
21 {
22 /* iterate through the puzzle grid and gte the clues */
23 for (int i = 0; i < app_data.puzzle.grid_size; i ++)
24 {
25 for (int j = 0; j < app_data.puzzle.grid_size; j ++)
26 {
27 /* if it is the start of an across word */
28 if (app_data.puzzle.start_across_word[i][j] != -1)
29 {
30 GtkTreeIter iter;
31
32 gtk_list_store_append (across_store, &iter);
33 gtk_list_store_set (across_store, &iter, 0,
34 app_data.puzzle.start_across_word[i][j],
35 1, app_data.puzzle.clue_across[i][j],
36 -1);
37 }
38 /* if it is the start of a down word */
39 if (app_data.puzzle.start_down_word[i][j] != -1)
40 {
41 GtkTreeIter iter;
42 gtk_list_store_append (down_store, &iter);
43 gtk_list_store_set (down_store, &iter, 0,
44 app_data.puzzle.start_down_word[i][j],
45 1, app_data.puzzle.clue_down[i][j],
46 -1);
47 }
48 }
49 }
50 }
51 }
52
53 /* slot for handling list down selection changed */
54 gboolean on_down_list_selection_changed (GtkTreeSelection *sel,
55 GtkDrawingArea *area)
56 {
57 GtkTreeIter iter;
58 GtkTreeModel* mod;
59
60 if (gtk_tree_selection_get_selected (sel, &mod, &iter))
61 {
62 guint sel_word_index;
63 gtk_tree_model_get (mod, &iter, 0, &sel_word_index, -1);
64 set_selection_to_word_start (&app_data, DOWN, sel_word_index);
65 }
66
67 gtk_widget_queue_draw_area (GTK_WIDGET(area), 0, 0,
68 app_data.puzzle.grid_size*GRID_PIXELS+10,
69 app_data.puzzle.grid_size*GRID_PIXELS+10);
70
71 return FALSE;
72
73 }
74 /* slot for handling list across selection changed */
75 gboolean on_across_list_selection_changed (GtkTreeSelection *sel,
76 GtkDrawingArea *area)
77 {
78 GtkTreeIter iter;
79 GtkTreeModel* mod;
80
81 if (gtk_tree_selection_get_selected (sel, &mod, &iter))
82 {
83 guint sel_word_index;
84 gtk_tree_model_get (mod, &iter, 0, &sel_word_index, -1);
85 set_selection_to_word_start (&app_data, ACROSS, sel_word_index);
86 }
87
88 gtk_widget_queue_draw_area (GTK_WIDGET(area), 0, 0,
89 app_data.puzzle.grid_size*GRID_PIXELS+10,
90 app_data.puzzle.grid_size*GRID_PIXELS+10);
91
92 return FALSE;
93
94 }
95
96 /* slot for handling mouse button event for puzzle drawing area */
97 gboolean on_puzzle_area_button_press_event (GtkWidget *widget,
98 GdkEventButton *event, gpointer data)
99 {
100 if (event->type == GDK_BUTTON_PRESS && event->button == 1)
101 {
102 int col = (event->x - 5) / GRID_PIXELS;
103 int row = (event->y - 5) / GRID_PIXELS;
104
105 if (app_data.puzzle.chars[row][col] != '#')
106 {
107 if (row < app_data.puzzle.grid_size &&
108 col < app_data.puzzle.grid_size)
109 {
110 app_data.cur_row = row;
111 app_data.cur_col = col;
112
113 /* if it is a start of both across and down word, then
114 toggle the movement on clicking it */
115 if (app_data.puzzle.start_across_word[row][col] != -1 &&
116 app_data.puzzle.start_down_word[row][col] != -1)
117 {
118 if (app_data.current_movement == ACROSS)
119 app_data.current_movement = DOWN;
120 else
121 app_data.current_movement = ACROSS;
122 }
123 /* if it is only start of across word, make the current
124 movement across */
125 else if (app_data.puzzle.start_across_word[row][col] != -1)
126 app_data.current_movement = ACROSS;
127 /* else down movement */
128 else if (app_data.puzzle.start_down_word[row][col] != -1)
129 app_data.current_movement = DOWN;
130 }
131 }
132 }
133
134 gtk_widget_queue_draw_area (widget, 0, 0,
135 app_data.puzzle.grid_size*GRID_PIXELS+10,
136 app_data.puzzle.grid_size*GRID_PIXELS+10);
137
138 return FALSE;
139 }
140
141 /* slot for handling key press event for puzzle drawing area */
142 gboolean on_puzzle_area_key_press_event (GtkWidget *widget,
143 GdkEventKey *event, gpointer data)
144 {
145 /* respond to key events only if the puzzle is loaded */
146 if (app_data.is_loaded == true)
147 {
148 switch (event->keyval)
149 {
150 case GDK_KEY_Down : move_current_row (&app_data, DIR_FORWARD);
151 app_data.current_movement = DOWN;
152 break;
153 case GDK_KEY_Up : move_current_row (&app_data, DIR_BACK);
154 app_data.current_movement = DOWN;
155 break;
156 case GDK_KEY_Right: move_current_col (&app_data, DIR_FORWARD);
157 app_data.current_movement = ACROSS;
158 break;
159 case GDK_KEY_Left : move_current_col (&app_data, DIR_BACK);
160 app_data.current_movement = ACROSS;
161 break;
162 case GDK_KEY_BackSpace:
163 case GDK_KEY_Delete:
164 case GDK_KEY_space:
165 app_data.char_ans[app_data.cur_row][app_data.cur_col] = ' ';
166 break;
167 case GDK_KEY_a :
168 case GDK_KEY_A :
169 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'A';
170 break;
171 case GDK_KEY_b :
172 case GDK_KEY_B :
173 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'B';
174 break;
175 case GDK_KEY_c :
176 case GDK_KEY_C :
177 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'C';
178 break;
179 case GDK_KEY_d :
180 case GDK_KEY_D :
181 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'D';
182 break;
183 case GDK_KEY_e :
184 case GDK_KEY_E :
185 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'E';
186 break;
187 case GDK_KEY_f :
188 case GDK_KEY_F :
189 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'F';
190 break;
191 case GDK_KEY_g :
192 case GDK_KEY_G :
193 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'G';
194 break;
195 case GDK_KEY_h :
196 case GDK_KEY_H :
197 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'H';
198 break;
199 case GDK_KEY_i :
200 case GDK_KEY_I :
201 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'I';
202 break;
203 case GDK_KEY_j :
204 case GDK_KEY_J :
205 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'J';
206 break;
207 case GDK_KEY_k :
208 case GDK_KEY_K :
209 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'K';
210 break;
211 case GDK_KEY_l :
212 case GDK_KEY_L :
213 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'L';
214 break;
215 case GDK_KEY_m :
216 case GDK_KEY_M :
217 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'M';
218 break;
219 case GDK_KEY_n :
220 case GDK_KEY_N :
221 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'N';
222 break;
223 case GDK_KEY_o :
224 case GDK_KEY_O :
225 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'O';
226 break;
227 case GDK_KEY_p :
228 case GDK_KEY_P :
229 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'P';
230 break;
231 case GDK_KEY_q :
232 case GDK_KEY_Q :
233 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'Q';
234 break;
235 case GDK_KEY_r :
236 case GDK_KEY_R :
237 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'R';
238 break;
239 case GDK_KEY_s :
240 case GDK_KEY_S :
241 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'S';
242 break;
243 case GDK_KEY_t :
244 case GDK_KEY_T :
245 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'T';
246 break;
247 case GDK_KEY_u :
248 case GDK_KEY_U :
249 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'U';
250 break;
251 case GDK_KEY_v :
252 case GDK_KEY_V :
253 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'V';
254 break;
255 case GDK_KEY_w :
256 case GDK_KEY_W :
257 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'W';
258 break;
259 case GDK_KEY_x :
260 case GDK_KEY_X :
261 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'X';
262 break;
263 case GDK_KEY_y :
264 case GDK_KEY_Y :
265 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'Y';
266 break;
267 case GDK_KEY_z :
268 case GDK_KEY_Z :
269 app_data.char_ans[app_data.cur_row][app_data.cur_col] = 'Z';
270 break;
271 default : return FALSE;
272
273 }
274 }
275
276 /* move the selection pointer to next row/col as per the current
277 movement orientation if it is not a key left right up or down */
278 if (event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up &&
279 event->keyval != GDK_KEY_Left && event->keyval != GDK_KEY_Right
280 && event->keyval != GDK_KEY_Delete)
281 {
282 /* if the current movement is an across movement */
283 if (app_data.current_movement == ACROSS)
284 {
285 /* if the next column is not blocking move the col */
286 if (event->keyval != GDK_KEY_BackSpace &&
287 next_col_block (&app_data.puzzle, app_data.cur_row,
288 app_data.cur_col) == false)
289 move_current_col (&app_data, DIR_FORWARD);
290 else if (event->keyval == GDK_KEY_BackSpace &&
291 prev_col_block (&app_data.puzzle, app_data.cur_row,
292 app_data.cur_col) == false)
293 move_current_col (&app_data, DIR_BACK);
294 }
295 /* current movement is a up/down movement */
296 else
297 {
298 if (event->keyval != GDK_KEY_BackSpace &&
299 next_row_block (&app_data.puzzle,
300 app_data.cur_row, app_data.cur_col) == false)
301 move_current_row (&app_data, DIR_FORWARD);
302 else if (event->keyval == GDK_KEY_BackSpace &&
303 prev_row_block (&app_data.puzzle,
304 app_data.cur_row, app_data.cur_col) == false)
305 move_current_row (&app_data, DIR_BACK);
306 }
307 }
308
309
310 gtk_widget_queue_draw_area (widget, 0, 0,
311 app_data.puzzle.grid_size*GRID_PIXELS+10,
312 app_data.puzzle.grid_size*GRID_PIXELS+10);
313
314 return FALSE;
315 }
316
317 /* slot for drawing the puzzle */
318 gboolean on_puzzle_area_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
319 {
320 /* if a puzzle is loaded */
321 if (app_data.is_loaded == true)
322 {
323 GdkRGBA colorfore, colorback, colorblue, colorbacksel1, colorbacksel2;
324 gdk_rgba_parse (&colorfore, "#000000");
325 gdk_rgba_parse (&colorback, "#ffffff");
326 gdk_rgba_parse (&colorblue, "#0000dd");
327 gdk_rgba_parse (&colorbacksel1, "#ffffaa");
328 gdk_rgba_parse (&colorbacksel2, "#aaffff");
329 cairo_set_line_width (cr, 3);
330
331 /* set the size of the drawing area */
332 gtk_widget_set_size_request (widget,
333 app_data.puzzle.grid_size*GRID_PIXELS+10,
334 app_data.puzzle.grid_size*GRID_PIXELS+10);
335
336 /* Draw the grid */
337 for (int i = 0; i < app_data.puzzle.grid_size; i ++)
338 {
339 for (int j = 0; j < app_data.puzzle.grid_size; j ++)
340 {
341 /* if it is the current selection or if -1 is the current
342 selection then let the current selection be the first word */
343 if (app_data.cur_col == -1 && app_data.cur_row == -1)
344 {
345 if (app_data.puzzle.start_across_word[i][j] == 1)
346 {
347 app_data.cur_row = i;
348 app_data.cur_col = j;
349 }
350 else if (app_data.puzzle.start_down_word[i][j] == 1)
351 {
352 app_data.cur_row = i;
353 app_data.cur_col = j;
354 }
355 }
356
357 gdk_cairo_set_source_rgba (cr, &colorfore);
358 cairo_rectangle (cr, j*GRID_PIXELS+5, i*GRID_PIXELS+5,
359 GRID_PIXELS, GRID_PIXELS);
360
361 cairo_stroke (cr);
362
363 /* if it is not a blank grid then set the background color
364 to black */
365 if (app_data.puzzle.chars[i][j] != '#')
366 gdk_cairo_set_source_rgba (cr, &colorback);
367
368 /* if it is a current selection then set the background
369 to yellow */
370 if (app_data.cur_row == i && app_data.cur_col == j)
371 {
372 if (app_data.current_movement == ACROSS)
373 gdk_cairo_set_source_rgba (cr, &colorbacksel1);
374 else
375 gdk_cairo_set_source_rgba (cr, &colorbacksel2);
376 }
377
378 cairo_rectangle (cr, j*GRID_PIXELS+5, i*GRID_PIXELS+5,
379 GRID_PIXELS, GRID_PIXELS);
380 cairo_fill (cr);
381
382 /* draw the word number if it is the start of a word */
383 if (app_data.puzzle.start_across_word[i][j] != -1 ||
384 app_data.puzzle.start_down_word[i][j] != -1)
385 {
386 int num;
387 if (app_data.puzzle.start_across_word[i][j] != -1)
388 num = app_data.puzzle.start_across_word[i][j];
389 else
390 num = app_data.puzzle.start_down_word[i][j];
391
392 gdk_cairo_set_source_rgba (cr, &colorblue);
393 cairo_select_font_face (cr, "sans serif",
394 CAIRO_FONT_SLANT_NORMAL,
395 CAIRO_FONT_WEIGHT_NORMAL);
396 cairo_set_font_size (cr, 11);
397 cairo_move_to (cr, j*GRID_PIXELS+7, i*GRID_PIXELS+15);
398 char cnum[3];
399 sprintf (cnum, "%d", num);
400 cairo_show_text (cr, (const char*)cnum);
401 }
402
403 /* draw the answer if it is there */
404 gdk_cairo_set_source_rgba (cr, &colorfore);
405 cairo_select_font_face (cr, "sans serif",
406 CAIRO_FONT_SLANT_NORMAL,
407 CAIRO_FONT_WEIGHT_BOLD);
408
409 cairo_set_font_size (cr, 16);
410 cairo_move_to (cr, j*GRID_PIXELS+GRID_PIXELS/2,
411 i*GRID_PIXELS+GRID_PIXELS-10);
412 char ctxt[3];
413 sprintf (ctxt, "%c", app_data.char_ans[i][j]);
414 cairo_show_text (cr, (const char*) ctxt);
415
416 }
417 }
418 }
419
420 return FALSE;
421
422 }
423
424 /* slot for reveal solution menu */
425 void on_menu_reveal_solution_activate (GtkMenuItem *item, gpointer *data)
426 {
427 /* TODO */
428 }
429
430 /* slot for save grid state menu */
431 void on_menu_save_grid_state_activate (GtkMenuItem *item, gpointer *data)
432 {
433 /* TODO */
434 }
435
436 /* slot for exit menu */
437 void on_menu_exit_activate (GtkMenuItem *item, gpointer data)
438 {
439 gtk_main_quit ();
440 }
441
442 /* slot for open menu */
443 void on_menu_open_activate (GtkMenuItem *item, GtkDrawingArea* area)
444 {
445 GtkWidget *dialog;
446 GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
447 gint res;
448 dialog = gtk_file_chooser_dialog_new (OPEN_FILE, GTK_WINDOW(window), action,
449 "_Cancel",
450 GTK_RESPONSE_CANCEL,
451 "_Open",
452 GTK_RESPONSE_ACCEPT,
453 NULL);
454 res = gtk_dialog_run (GTK_DIALOG (dialog));
455 if (res == GTK_RESPONSE_ACCEPT)
456 {
457 char *filename;
458 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
459 MainPlayerData temp;
460 reset_player_data (&temp, filename);
461
462 /* if the grid is not frozen then the game cannot be played */
463 if (temp.is_loaded == false)
464 {
465 GtkWidget *errordlg ;
466 errordlg = gtk_message_dialog_new (GTK_WINDOW(window),
467 GTK_DIALOG_DESTROY_WITH_PARENT,
468 GTK_MESSAGE_ERROR,
469 GTK_BUTTONS_CLOSE,
470 UNFROZEN_GRID_PLAYER);
471 gtk_dialog_run (GTK_DIALOG(errordlg));
472 gtk_widget_destroy (errordlg);
473 }
474 else
475 {
476 app_data = temp;
477 gtk_widget_queue_draw_area (GTK_WIDGET (area), 0, 0,
478 app_data.puzzle.grid_size*30+10,
479 app_data.puzzle.grid_size*30+10);
480
481 }
482 update_clue_items ();
483 g_free (filename);
484 }
485
486 gtk_widget_destroy (dialog);
487 }
488
489 /* slot for about menu */
490 void on_menu_about_activate (GtkMenuItem *item, gpointer data)
491 {
492 const char *AUTHOR[] = {"V.Harishankar", NULL};
493 gtk_show_about_dialog (GTK_WINDOW(window), "authors",AUTHOR,
494 "program-name", PROGRAM_NAME,
495 "copyright", COPYRIGHT,
496 "comments", COMMENTS,
497 "website", WEBSITE,
498 "website-label", WEBSITE_LABEL,
499 "license-type", GTK_LICENSE_GPL_2_0,
500 "version", VERSION,
501 (char*)NULL);
502 }
503
504 int main (int argc, char *argv [])
505 {
506 gtk_init (&argc, &argv);
507 GdkPixbuf *icon;
508 icon = gdk_pixbuf_new_from_resource
509 ("/org/harishankar/wordblox/wordblox.svg", NULL);
510 if (icon == NULL)
511 fprintf (stderr, ERROR_ICON);
512
513 GtkBuilder *builder;
514 builder = gtk_builder_new ();
515 guint ret = gtk_builder_add_from_resource (builder,
516 "/org/harishankar/wordblox/wordblox_player.glade", NULL);
517
518 app_data.is_loaded = false;
519
520 if (ret == 0)
521 {
522 fprintf (stderr, ERROR_WINDOW);
523 g_object_unref (builder);
524 return 1;
525 }
526 else
527 {
528 window = GTK_WIDGET (gtk_builder_get_object (builder, "main_window") );
529 across_store = GTK_LIST_STORE (gtk_builder_get_object
530 (builder, "store_across_clues"));
531 down_store = GTK_LIST_STORE (gtk_builder_get_object
532 (builder, "store_down_clues"));
533
534 if (window != NULL)
535 {
536 gtk_window_set_default_icon (icon);
537
538 GtkWidget *draw_area = GTK_WIDGET (
539 gtk_builder_get_object(builder, "puzzle_area"));
540
541 /* make drawing area respond to mouse event */
542 gtk_widget_set_events(draw_area,
543 gtk_widget_get_events(draw_area)
544 | GDK_BUTTON_PRESS_MASK);
545
546 gtk_builder_connect_signals (builder, NULL);
547 g_object_unref (builder);
548 gtk_widget_show (window);
549 gtk_main ();
550 return 0;
551 }
552 else
553 {
554 g_object_unref (builder);
555 fprintf (stderr, ERROR_WINDOW);
556 return 1;
557 }
558 }
559 }