b384b542cd1aa51a01c4f39fc1b3bc9cc49b12bf
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 hide solution activated event
37 def on_hidesolution_activate (self
, menuitem
):
40 self
.puzzle
.reveal_solution (False)
41 puzgrid
= self
.ui
.get_object ("puzzlegrid")
45 # callback for menu item reveal solution activated event
46 def on_revealsolution_activate (self
, menuitem
):
49 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
50 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
51 "This will reveal all words in the puzzle! Are you sure?")
52 if dlg
.run () == gtk
.RESPONSE_YES
:
54 self
.puzzle
.reveal_solution ()
56 puzgrid
= self
.ui
.get_object ("puzzlegrid")
60 # callback for menu item reveal word activated event
61 def on_revealword_activate (self
, menuitem
):
63 # reveal across/down word if any the position
64 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True:
65 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
66 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
67 "Are you sure you wish to reveal across word at current cell?")
68 # confirm that the user wants to reveal
69 if dlg
.run () == gtk
.RESPONSE_YES
:
70 self
.puzzle
.reveal_word_across (self
.selected_row
, self
.selected_col
)
71 # redraw the grid to reveal the word
72 puzgrid
= self
.ui
.get_object ("puzzlegrid")
75 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True:
76 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
77 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
78 "Are you sure wish to reveal down word at current cell?")
79 if dlg
.run () == gtk
.RESPONSE_YES
:
80 self
.puzzle
.reveal_word_down (self
.selected_row
, self
.selected_col
)
81 # redraw the grid to reveal the word
82 puzgrid
= self
.ui
.get_object ("puzzlegrid")
87 # function to set the selected row/col based on the number clicked
88 # on the clues list and also set the typing mode
89 def set_selection_of_num (self
, num
, across
= True):
90 # get the row, col of the word
91 row
, col
= self
.puzzle
.get_position_of_num (num
)
93 # set the selected row and column
94 self
.selected_row
= row
95 self
.selected_col
= col
96 # set typing mode to across
98 self
.typing_mode
= self
.ACROSS
100 self
.typing_mode
= self
.DOWN
102 # update the puzzle grid
103 puzgrid
= self
.ui
.get_object ("puzzlegrid")
105 # set focus to the puzzle grid
106 self
.window
.set_focus (puzgrid
)
108 puzgrid
.queue_draw ()
110 # callback for tree view "across" being activated
111 # activated - when double clicked or enter pressed
112 def on_tree_clues_across_row_activated (self
, view
, path
, column
):
113 # get the across list object
114 across_list
= self
.ui
.get_object ("clues_across")
115 # get the number of the across word
116 anum
= int (across_list
.get_value (across_list
.get_iter (path
), 0))
118 self
.set_selection_of_num (anum
)
122 # callback for tree view "down" being activated
123 # activated - when double clicked or enter pressed
124 def on_tree_clues_down_row_activated (self
, view
, path
, column
):
125 # get the down list object
126 down_list
= self
.ui
.get_object ("clues_down")
127 # get the number of the down word
128 dnum
= int (down_list
.get_value (down_list
.get_iter (path
), 0))
130 self
.set_selection_of_num (dnum
, False)
132 # moving the current selection in grid by one up or down
133 def move_selection_updown (self
, step
):
134 # increase or reduce the row by step until an occupied grid is found
137 last_occupied_row
= self
.selected_row
139 self
.selected_row
+= step
140 if self
.selected_row
< 0 or self
.selected_row
>= self
.puzzle
.rows
:
141 self
.selected_row
= last_occupied_row
143 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
144 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
147 # moving the current selection in grid by one across either way
148 def move_selection_across (self
, step
):
149 # increase or reduce the row by step until an occupied grid is found
152 last_occupied_col
= self
.selected_col
154 self
.selected_col
+= step
155 if self
.selected_col
< 0 or self
.selected_col
>= self
.puzzle
.cols
:
156 self
.selected_col
= last_occupied_col
158 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
159 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
162 # set the guessed character in the grid at selected location and move the
163 # selection across or down as the case may be
164 def set_guess (self
, guess_char
):
166 # set a guess only if not revealed
167 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].revealed
is False:
168 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= guess_char
.upper ()
170 if self
.typing_mode
== self
.ACROSS
:
171 # move by one character across but only if there is no block
173 old_col
= self
.selected_col
174 self
.move_selection_across (1)
175 if abs (self
.selected_col
- old_col
) > 1:
176 self
.selected_col
= old_col
179 # move by one character down but only if there is no block
181 old_row
= self
.selected_row
182 self
.move_selection_updown (1)
183 if abs (self
.selected_row
- old_row
) > 1:
184 self
.selected_row
= old_row
186 # delete the guessed char in the previous row/col depending on the input mode
187 # If input mode is ACROSS then delete guessed char at previous column else
189 def delete_prev_guess (self
):
191 if self
.typing_mode
== self
.ACROSS
:
192 # prevent deleting characters when there is a gap
193 old_sel_col
= self
.selected_col
194 self
.move_selection_across (-1)
195 # only if there is no block inbetween delete
196 if abs (self
.selected_col
- old_sel_col
) <= 1:
197 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
200 self
.selected_col
= old_sel_col
201 elif self
.typing_mode
== self
.DOWN
:
202 # prevent deleting characters when there is a gap
203 old_sel_row
= self
.selected_row
204 self
.move_selection_updown (-1)
205 # only if there is no block inbetween delete
206 if abs (self
.selected_row
- old_sel_row
) <= 1:
207 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
210 self
.selected_row
= old_sel_row
212 # callback for puzzle grid mouse button release event
213 def on_puzzlegrid_button_press_event (self
, drawarea
, event
):
214 # set the focus on the puzzle grid
216 self
.window
.set_focus (drawarea
)
218 col
= int (event
.x
/ 30)
219 row
= int (event
.y
/ 30)
221 if col
< self
.puzzle
.cols
and row
< self
.puzzle
.rows
:
222 if (self
.puzzle
.data
[row
][col
].occupied_across
is True or
223 self
.puzzle
.data
[row
][col
].occupied_down
is True):
224 self
.selected_col
= col
225 self
.selected_row
= row
226 drawarea
.queue_draw ()
230 # callback for puzzle grid key release event
231 def on_puzzlegrid_key_press_event (self
, drawarea
, event
):
233 key
= gtk
.gdk
.keyval_name (event
.keyval
).lower ()
235 if event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "up":
236 # reduce the row by 1 until you find an occupied grid and not a
238 self
.move_selection_updown (-1)
239 self
.typing_mode
= self
.DOWN
240 drawarea
.queue_draw ()
241 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "down":
242 # increase the row by 1 until you find an occupied grid and not a
244 self
.move_selection_updown (1)
245 self
.typing_mode
= self
.DOWN
246 drawarea
.queue_draw ()
247 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "right":
248 # increase the column by 1 until you find an occupied grid and not
250 self
.move_selection_across (1)
251 self
.typing_mode
= self
.ACROSS
252 drawarea
.queue_draw ()
253 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "left":
254 # decrease the column by 1 until you find an occupied grid and not
256 self
.move_selection_across (-1)
257 self
.typing_mode
= self
.ACROSS
258 drawarea
.queue_draw ()
259 # if it is A-Z or a-z then
260 elif len (key
) == 1 and key
.isalpha ():
262 drawarea
.queue_draw ()
263 # if it is the delete key then delete character at selected row/col
264 elif key
== "delete" or key
== "space":
265 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
266 drawarea
.queue_draw ()
267 # if it is backspace key then delete character at previous row/col
268 # depending on the input mode. If across editing mode, then delete
269 # at previous column else at previous row
270 elif key
== "backspace":
271 self
.delete_prev_guess ()
272 drawarea
.queue_draw ()
276 # puzzle grid focus in event
277 def on_puzzlegrid_focus_out_event (self
, drawarea
, event
):
279 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("gray"))
280 drawarea
.window
.set_background (col
)
284 # puzzle grid focus out event
285 def on_puzzlegrid_focus_in_event (self
, drawarea
, event
):
287 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("white"))
288 drawarea
.window
.set_background (col
)
291 # callback for drawing the puzzle grid
292 def on_puzzlegrid_expose_event (self
, drawarea
, event
):
293 # if puzzle is loaded
296 drawarea
.set_size_request (self
.puzzle
.cols
*30+2, self
.puzzle
.rows
*30+2)
298 ctx
= drawarea
.window
.cairo_create ()
299 ctx
.set_line_width (1.5)
301 # run through the grid
302 for row
in range (self
.puzzle
.rows
):
303 for col
in range (self
.puzzle
.cols
):
304 # (re)set foreground color
305 ctx
.set_source_rgb (0, 0, 0)
306 # if the area is not occupied
307 if (self
.puzzle
.data
[row
][col
].occupied_across
is False and
308 self
.puzzle
.data
[row
][col
].occupied_down
is False):
309 ctx
.rectangle (col
*30, row
*30, 30, 30)
312 # if selected row/column
313 if row
== self
.selected_row
and col
== self
.selected_col
:
314 ctx
.set_source_rgb (1, 1, 0)
315 ctx
.rectangle (col
*30,row
*30, 30, 30)
318 ctx
.set_source_rgb (1, 1, 1)
319 ctx
.rectangle (col
*30, row
*30, 30, 30)
321 ctx
.set_source_rgb (0, 0, 0)
322 ctx
.rectangle (col
*30, row
*30, 30, 30)
326 if self
.puzzle
.data
[row
][col
].numbered
<> 0:
327 ctx
.set_source_rgb (0, 0, 0)
328 ctx
.select_font_face ("Serif")
329 ctx
.set_font_size (10)
330 ctx
.move_to (col
*30+2, row
*30+10)
331 ctx
.show_text (str(self
.puzzle
.data
[row
][col
].numbered
))
333 # if there is a guessed character at the location
334 # and it is not revealed as a solution
335 if (self
.puzzle
.data
[row
][col
].guess
and
336 self
.puzzle
.data
[row
][col
].revealed
is False and
337 (self
.puzzle
.data
[row
][col
].occupied_across
is True
338 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
339 ctx
.set_source_rgb (0, 0, 0)
340 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
341 cairo
.FONT_WEIGHT_BOLD
)
342 ctx
.set_font_size (16)
343 ctx
.move_to (col
*30+10, row
*30+20)
344 ctx
.show_text (self
.puzzle
.data
[row
][col
].guess
)
346 # if there is a revealed solution character at the location
347 if (self
.puzzle
.data
[row
][col
].revealed
is True and
348 (self
.puzzle
.data
[row
][col
].occupied_across
is True
349 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
350 ctx
.set_source_rgb (0, 0, 0.8)
351 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
352 cairo
.FONT_WEIGHT_BOLD
)
353 ctx
.set_font_size (16)
354 ctx
.move_to (col
*30+10, row
*30+20)
355 ctx
.show_text (self
.puzzle
.data
[row
][col
].char
)
359 # load clues to the list
360 def load_clues (self
):
361 # get the clues list store objects
362 across
= self
.ui
.get_object ("clues_across")
363 down
= self
.ui
.get_object ("clues_down")
367 # if puzzle is loaded
369 clues_across
= self
.puzzle
.get_clues_across ()
370 clues_down
= self
.puzzle
.get_clues_down ()
371 # insert the numbers and the clues for across
372 for word
, clue
in clues_across
:
373 across
.append ([str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
375 # insert the numbers and the clues for down
376 for word
, clue
in clues_down
:
377 down
.append ([ str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
380 def open_file (self
, file):
381 # try to open the file
384 self
.puzzle
= cPickle
.load (open (file, "rb"))
385 # assert that it is unfrozen otherwise raise frozen grid exception
386 self
.puzzle
.assert_frozen_grid ()
388 # set selected initial row and column to 0
389 self
.selected_row
= 0
390 self
.selected_col
= 0
391 # set the typing mode to default - across
392 self
.typing_mode
= self
.ACROSS
394 self
.window
.set_title ("GetAClue player - " + file)
397 # handle unpickling, and file errors
398 except (cPickle
.UnpicklingError
, IOError, OSError):
399 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
400 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
401 "Invalid file. Cannot be loaded")
404 # if the puzzle has no words, then it cannot be played obviously
405 except crosswordpuzzle
.NoWordsException
:
407 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
408 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
409 "Word grid has no words. Cannot play")
412 # if the puzzle is not frozen then it cannot be played
413 except crosswordpuzzle
.FrozenGridException
:
415 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
416 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
417 "Word grid is not finalized/frozen. Cannot play")
421 def __init__ (self
, file_to_play
= None):
422 # load the user interface
423 self
.ui
= gtk
.Builder ()
424 self
.ui
.add_from_file ("playerwindow.glade")
427 self
.window
= self
.ui
.get_object ("mainwindow")
430 # set the cell renderer for tree views
431 cell
= gtk
.CellRendererText ()
432 tree_acol1
= self
.ui
.get_object ("tree_clues_across").get_column (0)
433 tree_acol2
= self
.ui
.get_object ("tree_clues_across").get_column (1)
434 tree_acol1
.pack_start (cell
)
435 tree_acol1
.add_attribute (cell
, "text", 0)
436 tree_acol2
.pack_start (cell
)
437 tree_acol2
.add_attribute (cell
, "text", 1)
439 tree_down
= self
.ui
.get_object ("tree_clues_down")
440 tree_dcol1
= self
.ui
.get_object ("tree_clues_down").get_column (0)
441 tree_dcol2
= self
.ui
.get_object ("tree_clues_down").get_column (1)
442 tree_dcol1
.pack_start (cell
)
443 tree_dcol1
.add_attribute (cell
, "text", 0)
444 tree_dcol2
.pack_start (cell
)
445 tree_dcol2
.add_attribute (cell
, "text", 1)
447 # connect the signals
448 self
.ui
.connect_signals (self
)
450 # set the puzzle to None
453 # open the file if it is set
455 self
.open_file (file_to_play
)