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 # function to set the selected row/col based on the number clicked
37 # on the clues list and also set the typing mode
38 def set_selection_of_num (self
, num
, across
= True):
39 # get the row, col of the word
40 row
, col
= self
.puzzle
.get_position_of_num (num
)
42 # set the selected row and column
43 self
.selected_row
= row
44 self
.selected_col
= col
45 # set typing mode to across
47 self
.typing_mode
= self
.ACROSS
49 self
.typing_mode
= self
.DOWN
51 # update the puzzle grid
52 puzgrid
= self
.ui
.get_object ("puzzlegrid")
54 # set focus to the puzzle grid
55 self
.window
.set_focus (puzgrid
)
59 # callback for tree view "across" being activated
60 # activated - when double clicked or enter pressed
61 def on_tree_clues_across_row_activated (self
, view
, path
, column
):
62 # get the across list object
63 across_list
= self
.ui
.get_object ("clues_across")
64 # get the number of the across word
65 anum
= int (across_list
.get_value (across_list
.get_iter (path
), 0))
67 self
.set_selection_of_num (anum
)
71 # callback for tree view "down" being activated
72 # activated - when double clicked or enter pressed
73 def on_tree_clues_down_row_activated (self
, view
, path
, column
):
74 # get the down list object
75 down_list
= self
.ui
.get_object ("clues_down")
76 # get the number of the down word
77 dnum
= int (down_list
.get_value (down_list
.get_iter (path
), 0))
79 self
.set_selection_of_num (dnum
, False)
81 # moving the current selection in grid by one up or down
82 def move_selection_updown (self
, step
):
83 # increase or reduce the row by step until an occupied grid is found
86 last_occupied_row
= self
.selected_row
88 self
.selected_row
+= step
89 if self
.selected_row
< 0 or self
.selected_row
>= self
.puzzle
.rows
:
90 self
.selected_row
= last_occupied_row
92 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
93 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
96 # moving the current selection in grid by one across either way
97 def move_selection_across (self
, step
):
98 # increase or reduce the row by step until an occupied grid is found
101 last_occupied_col
= self
.selected_col
103 self
.selected_col
+= step
104 if self
.selected_col
< 0 or self
.selected_col
>= self
.puzzle
.cols
:
105 self
.selected_col
= last_occupied_col
107 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
108 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
111 # set the guessed character in the grid at selected location and move the
112 # selection across or down as the case may be
113 def set_guess (self
, guess_char
):
115 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= guess_char
.upper ()
117 if self
.typing_mode
== self
.ACROSS
:
118 # move by one character across but only if there is no block
120 old_col
= self
.selected_col
121 self
.move_selection_across (1)
122 if abs (self
.selected_col
- old_col
) > 1:
123 self
.selected_col
= old_col
126 # move by one character down but only if there is no block
128 old_row
= self
.selected_row
129 self
.move_selection_updown (1)
130 if abs (self
.selected_row
- old_row
) > 1:
131 self
.selected_row
= old_row
133 # delete the guessed char in the previous row/col depending on the input mode
134 # If input mode is ACROSS then delete guessed char at previous column else
136 def delete_prev_guess (self
):
138 if self
.typing_mode
== self
.ACROSS
:
139 # prevent deleting characters when there is a gap
140 old_sel_col
= self
.selected_col
141 self
.move_selection_across (-1)
142 # only if there is no block inbetween delete
143 if abs (self
.selected_col
- old_sel_col
) <= 1:
144 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
147 self
.selected_col
= old_sel_col
148 elif self
.typing_mode
== self
.DOWN
:
149 # prevent deleting characters when there is a gap
150 old_sel_row
= self
.selected_row
151 self
.move_selection_updown (-1)
152 # only if there is no block inbetween delete
153 if abs (self
.selected_row
- old_sel_row
) <= 1:
154 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
157 self
.selected_row
= old_sel_row
159 # callback for puzzle grid mouse button release event
160 def on_puzzlegrid_button_press_event (self
, drawarea
, event
):
161 # set the focus on the puzzle grid
163 self
.window
.set_focus (drawarea
)
165 col
= int (event
.x
/ 30)
166 row
= int (event
.y
/ 30)
168 if col
< self
.puzzle
.cols
and row
< self
.puzzle
.rows
:
169 if (self
.puzzle
.data
[row
][col
].occupied_across
is True or
170 self
.puzzle
.data
[row
][col
].occupied_down
is True):
171 self
.selected_col
= col
172 self
.selected_row
= row
173 drawarea
.queue_draw ()
177 # callback for puzzle grid key release event
178 def on_puzzlegrid_key_press_event (self
, drawarea
, event
):
180 key
= gtk
.gdk
.keyval_name (event
.keyval
).lower ()
182 if event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "up":
183 # reduce the row by 1 until you find an occupied grid and not a
185 self
.move_selection_updown (-1)
186 self
.typing_mode
= self
.DOWN
187 drawarea
.queue_draw ()
188 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "down":
189 # increase the row by 1 until you find an occupied grid and not a
191 self
.move_selection_updown (1)
192 self
.typing_mode
= self
.DOWN
193 drawarea
.queue_draw ()
194 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "right":
195 # increase the column by 1 until you find an occupied grid and not
197 self
.move_selection_across (1)
198 self
.typing_mode
= self
.ACROSS
199 drawarea
.queue_draw ()
200 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "left":
201 # decrease the column by 1 until you find an occupied grid and not
203 self
.move_selection_across (-1)
204 self
.typing_mode
= self
.ACROSS
205 drawarea
.queue_draw ()
206 # if it is A-Z or a-z then
207 elif len (key
) == 1 and key
.isalpha ():
209 drawarea
.queue_draw ()
210 # if it is the delete key then delete character at selected row/col
211 elif key
== "delete" or key
== "space":
212 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
213 drawarea
.queue_draw ()
214 # if it is backspace key then delete character at previous row/col
215 # depending on the input mode. If across editing mode, then delete
216 # at previous column else at previous row
217 elif key
== "BackSpace":
218 self
.delete_prev_guess ()
219 drawarea
.queue_draw ()
223 # puzzle grid focus in event
224 def on_puzzlegrid_focus_out_event (self
, drawarea
, event
):
226 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("gray"))
227 drawarea
.window
.set_background (col
)
231 # puzzle grid focus out event
232 def on_puzzlegrid_focus_in_event (self
, drawarea
, event
):
234 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("white"))
235 drawarea
.window
.set_background (col
)
238 # callback for drawing the puzzle grid
239 def on_puzzlegrid_expose_event (self
, drawarea
, event
):
240 # if puzzle is loaded
243 drawarea
.set_size_request (self
.puzzle
.cols
*30+2, self
.puzzle
.rows
*30+2)
245 ctx
= drawarea
.window
.cairo_create ()
246 ctx
.set_line_width (1.5)
248 # run through the grid
249 for row
in range (self
.puzzle
.rows
):
250 for col
in range (self
.puzzle
.cols
):
251 # (re)set foreground color
252 ctx
.set_source_rgb (0, 0, 0)
253 # if the area is not occupied
254 if (self
.puzzle
.data
[row
][col
].occupied_across
is False and
255 self
.puzzle
.data
[row
][col
].occupied_down
is False):
256 ctx
.rectangle (col
*30, row
*30, 30, 30)
259 # if selected row/column
260 if row
== self
.selected_row
and col
== self
.selected_col
:
261 ctx
.set_source_rgb (1, 1, 0)
262 ctx
.rectangle (col
*30,row
*30, 30, 30)
265 ctx
.set_source_rgb (1, 1, 1)
266 ctx
.rectangle (col
*30, row
*30, 30, 30)
268 ctx
.set_source_rgb (0, 0, 0)
269 ctx
.rectangle (col
*30, row
*30, 30, 30)
273 if self
.puzzle
.data
[row
][col
].numbered
<> 0:
274 ctx
.set_source_rgb (0, 0, 0)
275 ctx
.select_font_face ("Serif")
276 ctx
.set_font_size (10)
277 ctx
.move_to (col
*30+2, row
*30+10)
278 ctx
.show_text (str(self
.puzzle
.data
[row
][col
].numbered
))
280 # if there is a guessed character at the location
281 # and it is not revealed as a solution
282 if (self
.puzzle
.data
[row
][col
].guess
and
283 self
.puzzle
.data
[row
][col
].revealed
is False and
284 (self
.puzzle
.data
[row
][col
].occupied_across
is True
285 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
286 ctx
.set_source_rgb (0, 0, 0)
287 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
288 cairo
.FONT_WEIGHT_BOLD
)
289 ctx
.set_font_size (16)
290 ctx
.move_to (col
*30+10, row
*30+20)
291 ctx
.show_text (self
.puzzle
.data
[row
][col
].guess
)
295 # load clues to the list
296 def load_clues (self
):
297 # get the clues list store objects
298 across
= self
.ui
.get_object ("clues_across")
299 down
= self
.ui
.get_object ("clues_down")
303 # if puzzle is loaded
305 clues_across
= self
.puzzle
.get_clues_across ()
306 clues_down
= self
.puzzle
.get_clues_down ()
307 # insert the numbers and the clues for across
308 for word
, clue
in clues_across
:
309 across
.append ([str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
311 # insert the numbers and the clues for down
312 for word
, clue
in clues_down
:
313 down
.append ([ str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
316 def open_file (self
, file):
317 # try to open the file
320 self
.puzzle
= cPickle
.load (open (file, "rb"))
321 # assert that it is unfrozen otherwise raise frozen grid exception
322 self
.puzzle
.assert_frozen_grid ()
324 # set selected initial row and column to 0
325 self
.selected_row
= 0
326 self
.selected_col
= 0
327 # set the typing mode to default - across
328 self
.typing_mode
= self
.ACROSS
330 self
.window
.set_title ("GetAClue player - " + file)
333 # handle unpickling, and file errors
334 except (cPickle
.UnpicklingError
, IOError, OSError):
335 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
336 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
337 "Invalid file. Cannot be loaded")
340 # if the puzzle has no words, then it cannot be played obviously
341 except crosswordpuzzle
.NoWordsException
:
343 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
344 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
345 "Word grid has no words. Cannot play")
348 # if the puzzle is not frozen then it cannot be played
349 except crosswordpuzzle
.FrozenGridException
:
351 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
352 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
353 "Word grid is not finalized/frozen. Cannot play")
357 def __init__ (self
, file_to_play
= None):
358 # load the user interface
359 self
.ui
= gtk
.Builder ()
360 self
.ui
.add_from_file ("playerwindow.glade")
363 self
.window
= self
.ui
.get_object ("mainwindow")
366 # set the cell renderer for tree views
367 cell
= gtk
.CellRendererText ()
368 tree_acol1
= self
.ui
.get_object ("tree_clues_across").get_column (0)
369 tree_acol2
= self
.ui
.get_object ("tree_clues_across").get_column (1)
370 tree_acol1
.pack_start (cell
)
371 tree_acol1
.add_attribute (cell
, "text", 0)
372 tree_acol2
.pack_start (cell
)
373 tree_acol2
.add_attribute (cell
, "text", 1)
375 tree_down
= self
.ui
.get_object ("tree_clues_down")
376 tree_dcol1
= self
.ui
.get_object ("tree_clues_down").get_column (0)
377 tree_dcol2
= self
.ui
.get_object ("tree_clues_down").get_column (1)
378 tree_dcol1
.pack_start (cell
)
379 tree_dcol1
.add_attribute (cell
, "text", 0)
380 tree_dcol2
.pack_start (cell
)
381 tree_dcol2
.add_attribute (cell
, "text", 1)
383 # connect the signals
384 self
.ui
.connect_signals (self
)
386 # set the puzzle to None
389 # open the file if it is set
391 self
.open_file (file_to_play
)