import pygtk
pygtk.require20 ()
import gtk
-import pango
+import cairo
+
import crosswordpuzzle
class MainWindow:
+ # typing mode constants
+ ACROSS = 1
+ DOWN = 2
+
def gtk_main_quit (self, *args):
gtk.main_quit ()
- # drawing for puzzle grid
+ # callback for menu item quit activated event
+ def on_quit_activate (self, menuitem):
+ if self.puzzle:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
+ "Puzzle is open. Are you sure you wish to quit?")
+ if dlg.run () <> gtk.RESPONSE_YES:
+ dlg.destroy ()
+ return False
+ dlg.destroy ()
+
+ self.gtk_main_quit ()
+
+ # function to set the selected row/col based on the number clicked
+ # on the clues list and also set the typing mode
+ def set_selection_of_num (self, num, across = True):
+ # get the row, col of the word
+ row, col = self.puzzle.get_position_of_num (num)
+
+ # set the selected row and column
+ self.selected_row = row
+ self.selected_col = col
+ # set typing mode to across
+ if across is True:
+ self.typing_mode = self.ACROSS
+ else:
+ self.typing_mode = self.DOWN
+
+ # update the puzzle grid
+ puzgrid = self.ui.get_object ("puzzlegrid")
+
+ # set focus to the puzzle grid
+ self.window.set_focus (puzgrid)
+
+ puzgrid.queue_draw ()
+
+ # callback for tree view "across" being activated
+ # activated - when double clicked or enter pressed
+ def on_tree_clues_across_row_activated (self, view, path, column):
+ # get the across list object
+ across_list = self.ui.get_object ("clues_across")
+ # get the number of the across word
+ anum = int (across_list.get_value (across_list.get_iter (path), 0))
+
+ self.set_selection_of_num (anum)
+
+ return False
+
+ # callback for tree view "down" being activated
+ # activated - when double clicked or enter pressed
+ def on_tree_clues_down_row_activated (self, view, path, column):
+ # get the down list object
+ down_list = self.ui.get_object ("clues_down")
+ # get the number of the down word
+ dnum = int (down_list.get_value (down_list.get_iter (path), 0))
+
+ self.set_selection_of_num (dnum, False)
+
+ # moving the current selection in grid by one up or down
+ def move_selection_updown (self, step):
+ # increase or reduce the row by step until an occupied grid is found
+ # black block
+ if self.puzzle:
+ last_occupied_row = self.selected_row
+ while True:
+ self.selected_row += step
+ if self.selected_row < 0 or self.selected_row >= self.puzzle.rows:
+ self.selected_row = last_occupied_row
+ break
+ if (self.puzzle.data[self.selected_row][self.selected_col].occupied_across is True
+ or self.puzzle.data[self.selected_row][self.selected_col].occupied_down is True):
+ break
+
+ # moving the current selection in grid by one across either way
+ def move_selection_across (self, step):
+ # increase or reduce the row by step until an occupied grid is found
+ # black block
+ if self.puzzle:
+ last_occupied_col = self.selected_col
+ while True:
+ self.selected_col += step
+ if self.selected_col < 0 or self.selected_col >= self.puzzle.cols:
+ self.selected_col = last_occupied_col
+ break
+ if (self.puzzle.data[self.selected_row][self.selected_col].occupied_across is True
+ or self.puzzle.data[self.selected_row][self.selected_col].occupied_down is True):
+ break
+
+ # set the guessed character in the grid at selected location and move the
+ # selection across or down as the case may be
+ def set_guess (self, guess_char):
+ if self.puzzle:
+ self.puzzle.data[self.selected_row][self.selected_col].guess = guess_char.upper ()
+ # across mode typing
+ if self.typing_mode == self.ACROSS:
+ # move by one character across but only if there is no block
+ # in between
+ old_col = self.selected_col
+ self.move_selection_across (1)
+ if abs (self.selected_col - old_col) > 1:
+ self.selected_col = old_col
+ # down mode typing
+ else:
+ # move by one character down but only if there is no block
+ # in between
+ old_row = self.selected_row
+ self.move_selection_updown (1)
+ if abs (self.selected_row - old_row) > 1:
+ self.selected_row = old_row
+
+ # delete the guessed char in the previous row/col depending on the input mode
+ # If input mode is ACROSS then delete guessed char at previous column else
+ # at previous row
+ def delete_prev_guess (self):
+ if self.puzzle:
+ if self.typing_mode == self.ACROSS:
+ # prevent deleting characters when there is a gap
+ old_sel_col = self.selected_col
+ self.move_selection_across (-1)
+ # only if there is no block inbetween delete
+ if abs (self.selected_col - old_sel_col) <= 1:
+ self.puzzle.data[self.selected_row][self.selected_col].guess = None
+ # reset selection
+ else:
+ self.selected_col = old_sel_col
+ elif self.typing_mode == self.DOWN:
+ # prevent deleting characters when there is a gap
+ old_sel_row = self.selected_row
+ self.move_selection_updown (-1)
+ # only if there is no block inbetween delete
+ if abs (self.selected_row - old_sel_row) <= 1:
+ self.puzzle.data[self.selected_row][self.selected_col].guess = None
+ # reset selection
+ else:
+ self.selected_row = old_sel_row
+
+ # callback for puzzle grid mouse button release event
+ def on_puzzlegrid_button_press_event (self, drawarea, event):
+ # set the focus on the puzzle grid
+ if self.puzzle:
+ self.window.set_focus (drawarea)
+
+ col = int (event.x / 30)
+ row = int (event.y / 30)
+
+ if col < self.puzzle.cols and row < self.puzzle.rows:
+ if (self.puzzle.data[row][col].occupied_across is True or
+ self.puzzle.data[row][col].occupied_down is True):
+ self.selected_col = col
+ self.selected_row = row
+ drawarea.queue_draw ()
+
+ return False
+
+ # callback for puzzle grid key release event
+ def on_puzzlegrid_key_press_event (self, drawarea, event):
+ if self.puzzle:
+ key = gtk.gdk.keyval_name (event.keyval).lower ()
+
+ if event.state == gtk.gdk.SHIFT_MASK and key == "up":
+ # reduce the row by 1 until you find an occupied grid and not a
+ # black block
+ self.move_selection_updown (-1)
+ self.typing_mode = self.DOWN
+ drawarea.queue_draw ()
+ elif event.state == gtk.gdk.SHIFT_MASK and key == "down":
+ # increase the row by 1 until you find an occupied grid and not a
+ # black block
+ self.move_selection_updown (1)
+ self.typing_mode = self.DOWN
+ drawarea.queue_draw ()
+ elif event.state == gtk.gdk.SHIFT_MASK and key == "right":
+ # increase the column by 1 until you find an occupied grid and not
+ # a black block
+ self.move_selection_across (1)
+ self.typing_mode = self.ACROSS
+ drawarea.queue_draw ()
+ elif event.state == gtk.gdk.SHIFT_MASK and key == "left":
+ # decrease the column by 1 until you find an occupied grid and not
+ # a black block
+ self.move_selection_across (-1)
+ self.typing_mode = self.ACROSS
+ drawarea.queue_draw ()
+ # if it is A-Z or a-z then
+ elif len (key) == 1 and key.isalpha ():
+ self.set_guess (key)
+ drawarea.queue_draw ()
+ # if it is the delete key then delete character at selected row/col
+ elif key == "delete" or key == "space":
+ self.puzzle.data[self.selected_row][self.selected_col].guess = None
+ drawarea.queue_draw ()
+ # if it is backspace key then delete character at previous row/col
+ # depending on the input mode. If across editing mode, then delete
+ # at previous column else at previous row
+ elif key == "BackSpace":
+ self.delete_prev_guess ()
+ drawarea.queue_draw ()
+
+ return False
+
+ # puzzle grid focus in event
+ def on_puzzlegrid_focus_out_event (self, drawarea, event):
+ if self.puzzle:
+ col = drawarea.window.get_colormap ().alloc_color (gtk.gdk.Color ("gray"))
+ drawarea.window.set_background (col)
+
+ return False
+
+ # puzzle grid focus out event
+ def on_puzzlegrid_focus_in_event (self, drawarea, event):
+ if self.puzzle:
+ col = drawarea.window.get_colormap ().alloc_color (gtk.gdk.Color ("white"))
+ drawarea.window.set_background (col)
+ return False
+
+ # callback for drawing the puzzle grid
def on_puzzlegrid_expose_event (self, drawarea, event):
# if puzzle is loaded
if self.puzzle:
# size the area
drawarea.set_size_request (self.puzzle.cols*30+2, self.puzzle.rows*30+2)
- # set background color to white
- col = drawarea.window.get_colormap ().alloc_color (gtk.gdk.Color ("white"))
- drawarea.window.set_background (col)
-
- #numlayout = gtk.PrintContext().create_pango_layout ()
- #numlayout.set_font_description (pango.FontDescription ("Sans 8"))
ctx = drawarea.window.cairo_create ()
ctx.set_line_width (1.5)
+ # run through the grid
for row in range (self.puzzle.rows):
for col in range (self.puzzle.cols):
+ # (re)set foreground color
+ ctx.set_source_rgb (0, 0, 0)
# if the area is not occupied
if (self.puzzle.data[row][col].occupied_across is False and
self.puzzle.data[row][col].occupied_down is False):
ctx.rectangle (col*30, row*30, 30, 30)
ctx.fill ()
else:
- ctx.rectangle (col*30, row*30, 30, 30)
- ctx.stroke ()
+ # if selected row/column
+ if row == self.selected_row and col == self.selected_col:
+ ctx.set_source_rgb (1, 1, 0)
+ ctx.rectangle (col*30,row*30, 30, 30)
+ ctx.fill ()
+ else:
+ ctx.set_source_rgb (1, 1, 1)
+ ctx.rectangle (col*30, row*30, 30, 30)
+ ctx.fill ()
+ ctx.set_source_rgb (0, 0, 0)
+ ctx.rectangle (col*30, row*30, 30, 30)
+ ctx.stroke ()
+
# if numbered
if self.puzzle.data[row][col].numbered <> 0:
- ctx.select_font_face ("Sans 7")
+ ctx.set_source_rgb (0, 0, 0)
+ ctx.select_font_face ("Serif")
+ ctx.set_font_size (10)
ctx.move_to (col*30+2, row*30+10)
ctx.show_text (str(self.puzzle.data[row][col].numbered))
+ # if there is a guessed character at the location
+ # and it is not revealed as a solution
+ if (self.puzzle.data[row][col].guess and
+ self.puzzle.data[row][col].revealed is False and
+ (self.puzzle.data[row][col].occupied_across is True
+ or self.puzzle.data[row][col].occupied_down is True)):
+ ctx.set_source_rgb (0, 0, 0)
+ ctx.select_font_face ("Serif", cairo.FONT_SLANT_NORMAL,
+ cairo.FONT_WEIGHT_BOLD)
+ ctx.set_font_size (16)
+ ctx.move_to (col*30+10, row*30+20)
+ ctx.show_text (self.puzzle.data[row][col].guess)
+
return False
+ # load clues to the list
def load_clues (self):
# get the clues list store objects
across = self.ui.get_object ("clues_across")
clue])
def open_file (self, file):
- self.puzzle = cPickle.load (open (file, "rb"))
- self.window.set_title ("GetAClue player - " + file)
- self.load_clues ()
+ # try to open the file
+ try:
+ # load the puzzle
+ self.puzzle = cPickle.load (open (file, "rb"))
+ # assert that it is unfrozen otherwise raise frozen grid exception
+ self.puzzle.assert_frozen_grid ()
+
+ # set selected initial row and column to 0
+ self.selected_row = 0
+ self.selected_col = 0
+ # set the typing mode to default - across
+ self.typing_mode = self.ACROSS
+
+ self.window.set_title ("GetAClue player - " + file)
+ # load the clues
+ self.load_clues ()
+ # handle unpickling, and file errors
+ except (cPickle.UnpicklingError, IOError, OSError):
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ "Invalid file. Cannot be loaded")
+ dlg.run ()
+ dlg.destroy ()
+ # if the puzzle has no words, then it cannot be played obviously
+ except crosswordpuzzle.NoWordsException:
+ self.puzzle = None
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ "Word grid has no words. Cannot play")
+ dlg.run ()
+ dlg.destroy ()
+ # if the puzzle is not frozen then it cannot be played
+ except crosswordpuzzle.FrozenGridException:
+ self.puzzle = None
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ "Word grid is not finalized/frozen. Cannot play")
+ dlg.run ()
+ dlg.destroy ()
def __init__ (self, file_to_play = None):
# load the user interface
self.window = self.ui.get_object ("mainwindow")
self.window.show ()
- # set the cell renderer
+ # set the cell renderer for tree views
cell = gtk.CellRendererText ()
tree_acol1 = self.ui.get_object ("tree_clues_across").get_column (0)
tree_acol2 = self.ui.get_object ("tree_clues_across").get_column (1)