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 def gtk_main_quit (self
, *args
):
23 # callback for menu item quit activated event
24 def on_quit_activate (self
, menuitem
):
26 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
27 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
28 "Puzzle is open. Are you sure you wish to quit?")
29 if dlg
.run () <> gtk
.RESPONSE_YES
:
36 # callback for menu item reveal word activated event
37 def on_revealword_activate (self
, menuitem
):
39 # reveal across/down word if any the position
40 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True:
41 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
42 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
43 "Are you sure you wish to reveal across word at current cell?")
44 # confirm that the user wants to reveal
45 if dlg
.run () == gtk
.RESPONSE_YES
:
46 self
.puzzle
.reveal_word_across (self
.selected_row
, self
.selected_col
)
47 # redraw the grid to reveal the word
48 puzgrid
= self
.ui
.get_object ("puzzlegrid")
51 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True:
52 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
53 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
54 "Are you sure wish to reveal down word at current cell?")
55 if dlg
.run () == gtk
.RESPONSE_YES
:
56 self
.puzzle
.reveal_word_down (self
.selected_row
, self
.selected_col
)
57 # redraw the grid to reveal the word
58 puzgrid
= self
.ui
.get_object ("puzzlegrid")
63 # function to set the selected row/col based on the number clicked
64 # on the clues list and also set the typing mode
65 def set_selection_of_num (self
, num
, across
= True):
66 # get the row, col of the word
67 row
, col
= self
.puzzle
.get_position_of_num (num
)
69 # set the selected row and column
70 self
.selected_row
= row
71 self
.selected_col
= col
72 # set typing mode to across
74 self
.typing_mode
= self
.ACROSS
76 self
.typing_mode
= self
.DOWN
78 # update the puzzle grid
79 puzgrid
= self
.ui
.get_object ("puzzlegrid")
81 # set focus to the puzzle grid
82 self
.window
.set_focus (puzgrid
)
86 # callback for tree view "across" being activated
87 # activated - when double clicked or enter pressed
88 def on_tree_clues_across_row_activated (self
, view
, path
, column
):
89 # get the across list object
90 across_list
= self
.ui
.get_object ("clues_across")
91 # get the number of the across word
92 anum
= int (across_list
.get_value (across_list
.get_iter (path
), 0))
94 self
.set_selection_of_num (anum
)
98 # callback for tree view "down" being activated
99 # activated - when double clicked or enter pressed
100 def on_tree_clues_down_row_activated (self
, view
, path
, column
):
101 # get the down list object
102 down_list
= self
.ui
.get_object ("clues_down")
103 # get the number of the down word
104 dnum
= int (down_list
.get_value (down_list
.get_iter (path
), 0))
106 self
.set_selection_of_num (dnum
, False)
108 # moving the current selection in grid by one up or down
109 def move_selection_updown (self
, step
):
110 # increase or reduce the row by step until an occupied grid is found
113 last_occupied_row
= self
.selected_row
115 self
.selected_row
+= step
116 if self
.selected_row
< 0 or self
.selected_row
>= self
.puzzle
.rows
:
117 self
.selected_row
= last_occupied_row
119 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
120 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
123 # moving the current selection in grid by one across either way
124 def move_selection_across (self
, step
):
125 # increase or reduce the row by step until an occupied grid is found
128 last_occupied_col
= self
.selected_col
130 self
.selected_col
+= step
131 if self
.selected_col
< 0 or self
.selected_col
>= self
.puzzle
.cols
:
132 self
.selected_col
= last_occupied_col
134 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
135 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
138 # set the guessed character in the grid at selected location and move the
139 # selection across or down as the case may be
140 def set_guess (self
, guess_char
):
142 # set a guess only if not revealed
143 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].revealed
is False:
144 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= guess_char
.upper ()
146 if self
.typing_mode
== self
.ACROSS
:
147 # move by one character across but only if there is no block
149 old_col
= self
.selected_col
150 self
.move_selection_across (1)
151 if abs (self
.selected_col
- old_col
) > 1:
152 self
.selected_col
= old_col
155 # move by one character down but only if there is no block
157 old_row
= self
.selected_row
158 self
.move_selection_updown (1)
159 if abs (self
.selected_row
- old_row
) > 1:
160 self
.selected_row
= old_row
162 # delete the guessed char in the previous row/col depending on the input mode
163 # If input mode is ACROSS then delete guessed char at previous column else
165 def delete_prev_guess (self
):
167 if self
.typing_mode
== self
.ACROSS
:
168 # prevent deleting characters when there is a gap
169 old_sel_col
= self
.selected_col
170 self
.move_selection_across (-1)
171 # only if there is no block inbetween delete
172 if abs (self
.selected_col
- old_sel_col
) <= 1:
173 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
176 self
.selected_col
= old_sel_col
177 elif self
.typing_mode
== self
.DOWN
:
178 # prevent deleting characters when there is a gap
179 old_sel_row
= self
.selected_row
180 self
.move_selection_updown (-1)
181 # only if there is no block inbetween delete
182 if abs (self
.selected_row
- old_sel_row
) <= 1:
183 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
186 self
.selected_row
= old_sel_row
188 # callback for puzzle grid mouse button release event
189 def on_puzzlegrid_button_press_event (self
, drawarea
, event
):
190 # set the focus on the puzzle grid
192 self
.window
.set_focus (drawarea
)
194 col
= int (event
.x
/ 30)
195 row
= int (event
.y
/ 30)
197 if col
< self
.puzzle
.cols
and row
< self
.puzzle
.rows
:
198 if (self
.puzzle
.data
[row
][col
].occupied_across
is True or
199 self
.puzzle
.data
[row
][col
].occupied_down
is True):
200 self
.selected_col
= col
201 self
.selected_row
= row
202 drawarea
.queue_draw ()
206 # callback for puzzle grid key release event
207 def on_puzzlegrid_key_press_event (self
, drawarea
, event
):
209 key
= gtk
.gdk
.keyval_name (event
.keyval
).lower ()
211 if event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "up":
212 # reduce the row by 1 until you find an occupied grid and not a
214 self
.move_selection_updown (-1)
215 self
.typing_mode
= self
.DOWN
216 drawarea
.queue_draw ()
217 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "down":
218 # increase the row by 1 until you find an occupied grid and not a
220 self
.move_selection_updown (1)
221 self
.typing_mode
= self
.DOWN
222 drawarea
.queue_draw ()
223 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "right":
224 # increase the column by 1 until you find an occupied grid and not
226 self
.move_selection_across (1)
227 self
.typing_mode
= self
.ACROSS
228 drawarea
.queue_draw ()
229 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "left":
230 # decrease the column by 1 until you find an occupied grid and not
232 self
.move_selection_across (-1)
233 self
.typing_mode
= self
.ACROSS
234 drawarea
.queue_draw ()
235 # if it is A-Z or a-z then
236 elif len (key
) == 1 and key
.isalpha ():
238 drawarea
.queue_draw ()
239 # if it is the delete key then delete character at selected row/col
240 elif key
== "delete" or key
== "space":
241 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
242 drawarea
.queue_draw ()
243 # if it is backspace key then delete character at previous row/col
244 # depending on the input mode. If across editing mode, then delete
245 # at previous column else at previous row
246 elif key
== "backspace":
247 self
.delete_prev_guess ()
248 drawarea
.queue_draw ()
252 # puzzle grid focus in event
253 def on_puzzlegrid_focus_out_event (self
, drawarea
, event
):
255 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("gray"))
256 drawarea
.window
.set_background (col
)
260 # puzzle grid focus out event
261 def on_puzzlegrid_focus_in_event (self
, drawarea
, event
):
263 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("white"))
264 drawarea
.window
.set_background (col
)
267 # callback for drawing the puzzle grid
268 def on_puzzlegrid_expose_event (self
, drawarea
, event
):
269 # if puzzle is loaded
272 drawarea
.set_size_request (self
.puzzle
.cols
*30+2, self
.puzzle
.rows
*30+2)
274 ctx
= drawarea
.window
.cairo_create ()
275 ctx
.set_line_width (1.5)
277 # run through the grid
278 for row
in range (self
.puzzle
.rows
):
279 for col
in range (self
.puzzle
.cols
):
280 # (re)set foreground color
281 ctx
.set_source_rgb (0, 0, 0)
282 # if the area is not occupied
283 if (self
.puzzle
.data
[row
][col
].occupied_across
is False and
284 self
.puzzle
.data
[row
][col
].occupied_down
is False):
285 ctx
.rectangle (col
*30, row
*30, 30, 30)
288 # if selected row/column
289 if row
== self
.selected_row
and col
== self
.selected_col
:
290 ctx
.set_source_rgb (1, 1, 0)
291 ctx
.rectangle (col
*30,row
*30, 30, 30)
294 ctx
.set_source_rgb (1, 1, 1)
295 ctx
.rectangle (col
*30, row
*30, 30, 30)
297 ctx
.set_source_rgb (0, 0, 0)
298 ctx
.rectangle (col
*30, row
*30, 30, 30)
302 if self
.puzzle
.data
[row
][col
].numbered
<> 0:
303 ctx
.set_source_rgb (0, 0, 0)
304 ctx
.select_font_face ("Serif")
305 ctx
.set_font_size (10)
306 ctx
.move_to (col
*30+2, row
*30+10)
307 ctx
.show_text (str(self
.puzzle
.data
[row
][col
].numbered
))
309 # if there is a guessed character at the location
310 # and it is not revealed as a solution
311 if (self
.puzzle
.data
[row
][col
].guess
and
312 self
.puzzle
.data
[row
][col
].revealed
is False and
313 (self
.puzzle
.data
[row
][col
].occupied_across
is True
314 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
315 ctx
.set_source_rgb (0, 0, 0)
316 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
317 cairo
.FONT_WEIGHT_BOLD
)
318 ctx
.set_font_size (16)
319 ctx
.move_to (col
*30+10, row
*30+20)
320 ctx
.show_text (self
.puzzle
.data
[row
][col
].guess
)
322 # if there is a revealed solution character at the location
323 if (self
.puzzle
.data
[row
][col
].revealed
is True and
324 (self
.puzzle
.data
[row
][col
].occupied_across
is True
325 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
326 ctx
.set_source_rgb (0, 0, 0.8)
327 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
328 cairo
.FONT_WEIGHT_BOLD
)
329 ctx
.set_font_size (16)
330 ctx
.move_to (col
*30+10, row
*30+20)
331 ctx
.show_text (self
.puzzle
.data
[row
][col
].char
)
335 # load clues to the list
336 def load_clues (self
):
337 # get the clues list store objects
338 across
= self
.ui
.get_object ("clues_across")
339 down
= self
.ui
.get_object ("clues_down")
343 # if puzzle is loaded
345 clues_across
= self
.puzzle
.get_clues_across ()
346 clues_down
= self
.puzzle
.get_clues_down ()
347 # insert the numbers and the clues for across
348 for word
, clue
in clues_across
:
349 across
.append ([str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
351 # insert the numbers and the clues for down
352 for word
, clue
in clues_down
:
353 down
.append ([ str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
356 def open_file (self
, file):
357 # try to open the file
360 self
.puzzle
= cPickle
.load (open (file, "rb"))
361 # assert that it is unfrozen otherwise raise frozen grid exception
362 self
.puzzle
.assert_frozen_grid ()
364 # set selected initial row and column to 0
365 self
.selected_row
= 0
366 self
.selected_col
= 0
367 # set the typing mode to default - across
368 self
.typing_mode
= self
.ACROSS
370 self
.window
.set_title ("GetAClue player - " + file)
373 # handle unpickling, and file errors
374 except (cPickle
.UnpicklingError
, IOError, OSError):
375 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
376 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
377 "Invalid file. Cannot be loaded")
380 # if the puzzle has no words, then it cannot be played obviously
381 except crosswordpuzzle
.NoWordsException
:
383 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
384 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
385 "Word grid has no words. Cannot play")
388 # if the puzzle is not frozen then it cannot be played
389 except crosswordpuzzle
.FrozenGridException
:
391 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
392 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
393 "Word grid is not finalized/frozen. Cannot play")
397 def __init__ (self
, file_to_play
= None):
398 # load the user interface
399 self
.ui
= gtk
.Builder ()
400 self
.ui
.add_from_file ("playerwindow.glade")
403 self
.window
= self
.ui
.get_object ("mainwindow")
406 # set the cell renderer for tree views
407 cell
= gtk
.CellRendererText ()
408 tree_acol1
= self
.ui
.get_object ("tree_clues_across").get_column (0)
409 tree_acol2
= self
.ui
.get_object ("tree_clues_across").get_column (1)
410 tree_acol1
.pack_start (cell
)
411 tree_acol1
.add_attribute (cell
, "text", 0)
412 tree_acol2
.pack_start (cell
)
413 tree_acol2
.add_attribute (cell
, "text", 1)
415 tree_down
= self
.ui
.get_object ("tree_clues_down")
416 tree_dcol1
= self
.ui
.get_object ("tree_clues_down").get_column (0)
417 tree_dcol2
= self
.ui
.get_object ("tree_clues_down").get_column (1)
418 tree_dcol1
.pack_start (cell
)
419 tree_dcol1
.add_attribute (cell
, "text", 0)
420 tree_dcol2
.pack_start (cell
)
421 tree_dcol2
.add_attribute (cell
, "text", 1)
423 # connect the signals
424 self
.ui
.connect_signals (self
)
426 # set the puzzle to None
429 # open the file if it is set
431 self
.open_file (file_to_play
)