1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
5 # Main window class for GetAClue player
13 import crosswordpuzzle
16 # typing mode constants
20 # callback for menu item open activated event
21 def on_open_activate (self
, menuitem
):
22 dlg
= gtk
.FileChooserDialog ("Open a GetAClue puzzle", self
.window
,
23 gtk
.FILE_CHOOSER_ACTION_OPEN
,
24 (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
, gtk
.STOCK_OPEN
, gtk
.RESPONSE_OK
))
26 if dlg
.run () == gtk
.RESPONSE_OK
:
27 puzzlefile
= dlg
.get_filename ()
28 self
.open_file (puzzlefile
)
33 def verify_quit (self
):
35 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
36 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
37 "Puzzle is open. Are you sure you wish to quit?")
38 if dlg
.run () <> gtk
.RESPONSE_YES
:
45 # callback for main window destroy
46 def on_mainwindow_destroy (self
, args
):
50 # callback for window closing dialog
51 def on_mainwindow_delete_event (self
, window
, event
):
52 # verify whether really to quit or not if a puzzle is open
53 v
= self
.verify_quit ()
54 # return False for deleting and True for not deleting
57 # callback for menu item quit activated event
58 def on_quit_activate (self
, menuitem
):
59 # verify whether really to quit or not if a puzzle is open
60 v
= self
.verify_quit ()
61 # if verified, then quit
63 self
.window
.destroy ()
65 # callback for menu item clear grid activated event
66 def on_cleargrid_activate (self
, menuitem
):
68 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
69 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
70 "Are you sure you wish to clear your entries?")
71 if dlg
.run () == gtk
.RESPONSE_YES
:
73 self
.puzzle
.clear_guesses ()
75 puzgrid
= self
.ui
.get_object ("puzzlegrid")
79 # callback for menu item verify board activated event
80 def on_verify_activate (self
, menuitem
):
83 ans
= self
.puzzle
.is_solution_correct ()
84 # if the solution is correct
86 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
87 gtk
.MESSAGE_INFO
, gtk
.BUTTONS_CLOSE
,
88 "Success! Your entries are correct.")
91 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
92 gtk
.MESSAGE_INFO
, gtk
.BUTTONS_CLOSE
,
93 "Your solution has some errors. Fix them and try again")
95 except crosswordpuzzle
.IncompleteSolutionException
:
96 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
97 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
98 "You've not completed the board yet. Cannot verify")
102 # callback for menu item hide solution activated event
103 def on_hidesolution_activate (self
, menuitem
):
106 self
.puzzle
.reveal_solution (False)
107 puzgrid
= self
.ui
.get_object ("puzzlegrid")
109 puzgrid
.queue_draw ()
111 # callback for menu item reveal solution activated event
112 def on_revealsolution_activate (self
, menuitem
):
115 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
116 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
117 "This will reveal all words in the puzzle! Are you sure?")
118 if dlg
.run () == gtk
.RESPONSE_YES
:
119 # reveal the solution
120 self
.puzzle
.reveal_solution ()
122 puzgrid
= self
.ui
.get_object ("puzzlegrid")
123 puzgrid
.queue_draw ()
126 # callback for menu item reveal word activated event
127 def on_revealword_activate (self
, menuitem
):
129 # reveal across/down word if any the position
130 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True:
131 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
132 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
133 "Are you sure you wish to reveal across word at current cell?")
134 # confirm that the user wants to reveal
135 if dlg
.run () == gtk
.RESPONSE_YES
:
136 self
.puzzle
.reveal_word_across (self
.selected_row
, self
.selected_col
)
137 # redraw the grid to reveal the word
138 puzgrid
= self
.ui
.get_object ("puzzlegrid")
139 puzgrid
.queue_draw ()
141 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True:
142 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
143 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
144 "Are you sure wish to reveal down word at current cell?")
145 if dlg
.run () == gtk
.RESPONSE_YES
:
146 self
.puzzle
.reveal_word_down (self
.selected_row
, self
.selected_col
)
147 # redraw the grid to reveal the word
148 puzgrid
= self
.ui
.get_object ("puzzlegrid")
149 puzgrid
.queue_draw ()
153 # function to set the selected row/col based on the number clicked
154 # on the clues list and also set the typing mode
155 def set_selection_of_num (self
, num
, across
= True):
156 # get the row, col of the word
157 row
, col
= self
.puzzle
.get_position_of_num (num
)
159 # set the selected row and column
160 self
.selected_row
= row
161 self
.selected_col
= col
162 # set typing mode to across
164 self
.typing_mode
= self
.ACROSS
166 self
.typing_mode
= self
.DOWN
168 # update the puzzle grid
169 puzgrid
= self
.ui
.get_object ("puzzlegrid")
171 # set focus to the puzzle grid
172 self
.window
.set_focus (puzgrid
)
174 puzgrid
.queue_draw ()
176 # callback for tree view "across" being activated
177 # activated - when double clicked or enter pressed
178 def on_tree_clues_across_row_activated (self
, view
, path
, column
):
179 # get the across list object
180 across_list
= self
.ui
.get_object ("clues_across")
181 # get the number of the across word
182 anum
= int (across_list
.get_value (across_list
.get_iter (path
), 0))
184 self
.set_selection_of_num (anum
)
188 # callback for tree view "down" being activated
189 # activated - when double clicked or enter pressed
190 def on_tree_clues_down_row_activated (self
, view
, path
, column
):
191 # get the down list object
192 down_list
= self
.ui
.get_object ("clues_down")
193 # get the number of the down word
194 dnum
= int (down_list
.get_value (down_list
.get_iter (path
), 0))
196 self
.set_selection_of_num (dnum
, False)
198 # moving the current selection in grid by one up or down
199 def move_selection_updown (self
, step
):
200 # increase or reduce the row by step until an occupied grid is found
203 last_occupied_row
= self
.selected_row
205 self
.selected_row
+= step
206 if self
.selected_row
< 0 or self
.selected_row
>= self
.puzzle
.rows
:
207 self
.selected_row
= last_occupied_row
209 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
210 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
213 # moving the current selection in grid by one across either way
214 def move_selection_across (self
, step
):
215 # increase or reduce the row by step until an occupied grid is found
218 last_occupied_col
= self
.selected_col
220 self
.selected_col
+= step
221 if self
.selected_col
< 0 or self
.selected_col
>= self
.puzzle
.cols
:
222 self
.selected_col
= last_occupied_col
224 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
225 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
228 # set the guessed character in the grid at selected location and move the
229 # selection across or down as the case may be
230 def set_guess (self
, guess_char
):
232 # set a guess only if not revealed
233 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].revealed
is False:
234 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= guess_char
.upper ()
236 if self
.typing_mode
== self
.ACROSS
:
237 # move by one character across but only if there is no block
239 old_col
= self
.selected_col
240 self
.move_selection_across (1)
241 if abs (self
.selected_col
- old_col
) > 1:
242 self
.selected_col
= old_col
245 # move by one character down but only if there is no block
247 old_row
= self
.selected_row
248 self
.move_selection_updown (1)
249 if abs (self
.selected_row
- old_row
) > 1:
250 self
.selected_row
= old_row
252 # delete the guessed char in the previous row/col depending on the input mode
253 # If input mode is ACROSS then delete guessed char at previous column else
255 def delete_prev_guess (self
):
257 if self
.typing_mode
== self
.ACROSS
:
258 # prevent deleting characters when there is a gap
259 old_sel_col
= self
.selected_col
260 self
.move_selection_across (-1)
261 # only if there is no block inbetween delete
262 if abs (self
.selected_col
- old_sel_col
) <= 1:
263 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
266 self
.selected_col
= old_sel_col
267 elif self
.typing_mode
== self
.DOWN
:
268 # prevent deleting characters when there is a gap
269 old_sel_row
= self
.selected_row
270 self
.move_selection_updown (-1)
271 # only if there is no block inbetween delete
272 if abs (self
.selected_row
- old_sel_row
) <= 1:
273 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
276 self
.selected_row
= old_sel_row
278 # callback for puzzle grid mouse button release event
279 def on_puzzlegrid_button_press_event (self
, drawarea
, event
):
280 # set the focus on the puzzle grid
282 self
.window
.set_focus (drawarea
)
284 col
= int (event
.x
/ 30)
285 row
= int (event
.y
/ 30)
287 if col
< self
.puzzle
.cols
and row
< self
.puzzle
.rows
:
288 if (self
.puzzle
.data
[row
][col
].occupied_across
is True or
289 self
.puzzle
.data
[row
][col
].occupied_down
is True):
290 self
.selected_col
= col
291 self
.selected_row
= row
292 drawarea
.queue_draw ()
296 # callback for puzzle grid key release event
297 def on_puzzlegrid_key_press_event (self
, drawarea
, event
):
299 key
= gtk
.gdk
.keyval_name (event
.keyval
).lower ()
301 if event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "up":
302 # reduce the row by 1 until you find an occupied grid and not a
304 self
.move_selection_updown (-1)
305 self
.typing_mode
= self
.DOWN
306 drawarea
.queue_draw ()
307 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "down":
308 # increase the row by 1 until you find an occupied grid and not a
310 self
.move_selection_updown (1)
311 self
.typing_mode
= self
.DOWN
312 drawarea
.queue_draw ()
313 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "right":
314 # increase the column by 1 until you find an occupied grid and not
316 self
.move_selection_across (1)
317 self
.typing_mode
= self
.ACROSS
318 drawarea
.queue_draw ()
319 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "left":
320 # decrease the column by 1 until you find an occupied grid and not
322 self
.move_selection_across (-1)
323 self
.typing_mode
= self
.ACROSS
324 drawarea
.queue_draw ()
325 # if it is A-Z or a-z then
326 elif len (key
) == 1 and key
.isalpha ():
328 drawarea
.queue_draw ()
329 # if it is the delete key then delete character at selected row/col
330 elif key
== "delete" or key
== "space":
331 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
332 drawarea
.queue_draw ()
333 # if it is backspace key then delete character at previous row/col
334 # depending on the input mode. If across editing mode, then delete
335 # at previous column else at previous row
336 elif key
== "backspace":
337 self
.delete_prev_guess ()
338 drawarea
.queue_draw ()
342 # puzzle grid focus in event
343 def on_puzzlegrid_focus_out_event (self
, drawarea
, event
):
345 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("gray"))
346 drawarea
.window
.set_background (col
)
350 # puzzle grid focus out event
351 def on_puzzlegrid_focus_in_event (self
, drawarea
, event
):
353 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("white"))
354 drawarea
.window
.set_background (col
)
357 # callback for drawing the puzzle grid
358 def on_puzzlegrid_expose_event (self
, drawarea
, event
):
359 # if puzzle is loaded
362 drawarea
.set_size_request (self
.puzzle
.cols
*30+2, self
.puzzle
.rows
*30+2)
364 ctx
= drawarea
.window
.cairo_create ()
365 ctx
.set_line_width (1.5)
367 # run through the grid
368 for row
in range (self
.puzzle
.rows
):
369 for col
in range (self
.puzzle
.cols
):
370 # (re)set foreground color
371 ctx
.set_source_rgb (0, 0, 0)
372 # if the area is not occupied
373 if (self
.puzzle
.data
[row
][col
].occupied_across
is False and
374 self
.puzzle
.data
[row
][col
].occupied_down
is False):
375 ctx
.rectangle (col
*30, row
*30, 30, 30)
378 # if selected row/column
379 if row
== self
.selected_row
and col
== self
.selected_col
:
380 ctx
.set_source_rgb (1, 1, 0)
381 ctx
.rectangle (col
*30,row
*30, 30, 30)
384 ctx
.set_source_rgb (1, 1, 1)
385 ctx
.rectangle (col
*30, row
*30, 30, 30)
387 ctx
.set_source_rgb (0, 0, 0)
388 ctx
.rectangle (col
*30, row
*30, 30, 30)
392 if self
.puzzle
.data
[row
][col
].numbered
<> 0:
393 ctx
.set_source_rgb (0, 0, 0)
394 ctx
.select_font_face ("Serif")
395 ctx
.set_font_size (10)
396 ctx
.move_to (col
*30+2, row
*30+10)
397 ctx
.show_text (str(self
.puzzle
.data
[row
][col
].numbered
))
399 # if there is a guessed character at the location
400 # and it is not revealed as a solution
401 if (self
.puzzle
.data
[row
][col
].guess
and
402 self
.puzzle
.data
[row
][col
].revealed
is False and
403 (self
.puzzle
.data
[row
][col
].occupied_across
is True
404 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
405 ctx
.set_source_rgb (0, 0, 0)
406 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
407 cairo
.FONT_WEIGHT_BOLD
)
408 ctx
.set_font_size (16)
409 ctx
.move_to (col
*30+10, row
*30+20)
410 ctx
.show_text (self
.puzzle
.data
[row
][col
].guess
)
412 # if there is a revealed solution character at the location
413 if (self
.puzzle
.data
[row
][col
].revealed
is True and
414 (self
.puzzle
.data
[row
][col
].occupied_across
is True
415 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
416 ctx
.set_source_rgb (0, 0, 0.8)
417 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
418 cairo
.FONT_WEIGHT_BOLD
)
419 ctx
.set_font_size (16)
420 ctx
.move_to (col
*30+10, row
*30+20)
421 ctx
.show_text (self
.puzzle
.data
[row
][col
].char
)
425 # load clues to the list
426 def load_clues (self
):
427 # get the clues list store objects
428 across
= self
.ui
.get_object ("clues_across")
429 down
= self
.ui
.get_object ("clues_down")
433 # if puzzle is loaded
435 clues_across
= self
.puzzle
.get_clues_across ()
436 clues_down
= self
.puzzle
.get_clues_down ()
437 # insert the numbers and the clues for across
438 for word
, clue
in clues_across
:
439 across
.append ([str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
441 # insert the numbers and the clues for down
442 for word
, clue
in clues_down
:
443 down
.append ([ str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
446 def open_file (self
, file):
447 # try to open the file
450 self
.puzzle
= cPickle
.load (open (file, "rb"))
451 # assert that it is unfrozen otherwise raise frozen grid exception
452 self
.puzzle
.assert_frozen_grid ()
454 # set selected initial row and column to 0
455 self
.selected_row
= 0
456 self
.selected_col
= 0
457 # set the typing mode to default - across
458 self
.typing_mode
= self
.ACROSS
460 self
.window
.set_title ("GetAClue player - " + file)
463 # handle unpickling, and file errors
464 except (cPickle
.UnpicklingError
, IOError, OSError):
465 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
466 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
467 "Invalid file. Cannot be loaded")
470 # if the puzzle has no words, then it cannot be played obviously
471 except crosswordpuzzle
.NoWordsException
:
473 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
474 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
475 "Word grid has no words. Cannot play")
478 # if the puzzle is not frozen then it cannot be played
479 except crosswordpuzzle
.FrozenGridException
:
481 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
482 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
483 "Word grid is not finalized/frozen. Cannot play")
487 def __init__ (self
, file_to_play
= None):
488 # load the user interface
489 self
.ui
= gtk
.Builder ()
490 self
.ui
.add_from_file ("playerwindow.glade")
493 self
.window
= self
.ui
.get_object ("mainwindow")
496 # set the cell renderer for tree views
497 cell
= gtk
.CellRendererText ()
498 tree_acol1
= self
.ui
.get_object ("tree_clues_across").get_column (0)
499 tree_acol2
= self
.ui
.get_object ("tree_clues_across").get_column (1)
500 tree_acol1
.pack_start (cell
)
501 tree_acol1
.add_attribute (cell
, "text", 0)
502 tree_acol2
.pack_start (cell
)
503 tree_acol2
.add_attribute (cell
, "text", 1)
505 tree_down
= self
.ui
.get_object ("tree_clues_down")
506 tree_dcol1
= self
.ui
.get_object ("tree_clues_down").get_column (0)
507 tree_dcol2
= self
.ui
.get_object ("tree_clues_down").get_column (1)
508 tree_dcol1
.pack_start (cell
)
509 tree_dcol1
.add_attribute (cell
, "text", 0)
510 tree_dcol2
.pack_start (cell
)
511 tree_dcol2
.add_attribute (cell
, "text", 1)
513 # connect the signals
514 self
.ui
.connect_signals (self
)
516 # set the puzzle to None
519 # open the file if it is set
521 self
.open_file (file_to_play
)