# Main window class for GetAClue player
+import sys
+import os.path
import cPickle
import pygtk
pygtk.require20 ()
# typing mode constants
ACROSS = 1
DOWN = 2
+ # license text to be displayed in the about dialog
+ LICENSE_TEXT = """GetAClue is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GetAClue is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GetAClue. If not, see <http://www.gnu.org/licenses/>."""
+
+ # quit verification
+ def verify_quit (self):
+ 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 ()
+ return True
+
+ # callback for menu item open activated event
+ def on_open_activate (self, menuitem):
+ dlg = gtk.FileChooserDialog ("Open a GetAClue puzzle", self.window,
+ gtk.FILE_CHOOSER_ACTION_OPEN,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
- def gtk_main_quit (self, *args):
+ if dlg.run () == gtk.RESPONSE_OK:
+ puzzlefile = dlg.get_filename ()
+ self.open_file (puzzlefile)
+
+ dlg.destroy ()
+
+ # callback for menu item save as activated event
+ def on_save_as_activate (self, menuitem):
+ if self.puzzle:
+ dlg = gtk.FileChooserDialog ("Save GetAClue puzzle as", self.window,
+ gtk.FILE_CHOOSER_ACTION_SAVE,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ if dlg.run () == gtk.RESPONSE_OK:
+ puzzlefile = dlg.get_filename ()
+ self.save_file (puzzlefile)
+
+ dlg.destroy ()
+
+ # callback for main window destroy
+ def on_mainwindow_destroy (self, args):
gtk.main_quit ()
+
+ # callback for window closing dialog
+ def on_mainwindow_delete_event (self, window, event):
+ # verify whether really to quit or not if a puzzle is open
+ v = self.verify_quit ()
+ # return False for deleting and True for not deleting
+ return not v
+
# callback for menu item quit activated event
def on_quit_activate (self, menuitem):
+ # verify whether really to quit or not if a puzzle is open
+ v = self.verify_quit ()
+ # if verified, then quit
+ if v is True:
+ self.window.destroy ()
+
+ # callback for menu item clear grid activated event
+ def on_cleargrid_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
+ "Are you sure you wish to clear your entries?")
+ if dlg.run () == gtk.RESPONSE_YES:
+ # clear the guesses
+ self.puzzle.clear_guesses ()
+ # redraw the grid
+ puzgrid = self.ui.get_object ("puzzlegrid")
+ puzgrid.queue_draw ()
+ dlg.destroy()
+
+ # callback for menu item verify board activated event
+ def on_verify_activate (self, menuitem):
+ if self.puzzle:
+ try:
+ ans = self.puzzle.is_solution_correct ()
+ # if the solution is correct
+ if ans:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE,
+ "Success! Your entries are correct.")
+ dlg.run ()
+ else:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE,
+ "Your solution has some errors. Fix them and try again")
+ dlg.run ()
+ except crosswordpuzzle.IncompleteSolutionException:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ "You've not completed the board yet. Cannot verify")
+ dlg.run ()
dlg.destroy ()
- self.gtk_main_quit ()
+ # callback for menu item hide solution activated event
+ def on_hidesolution_activate (self, menuitem):
+ if self.puzzle:
+ # hide the solution
+ self.puzzle.reveal_solution (False)
+ puzgrid = self.ui.get_object ("puzzlegrid")
+ # redraw the grid
+ puzgrid.queue_draw ()
+
+ # callback for menu item reveal solution activated event
+ def on_revealsolution_activate (self, menuitem):
+ if self.puzzle:
+ # confirm first
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
+ "This will reveal all words in the puzzle! Are you sure?")
+ if dlg.run () == gtk.RESPONSE_YES:
+ # reveal the solution
+ self.puzzle.reveal_solution ()
+ # redraw the grid
+ puzgrid = self.ui.get_object ("puzzlegrid")
+ puzgrid.queue_draw ()
+ dlg.destroy ()
- # callback for puzzle grid mouse button release event
- def on_puzzlegrid_button_press_event (self, drawarea, event):
- # set the focus on the puzzle grid
+ # callback for menu item reveal word activated event
+ def on_revealword_activate (self, menuitem):
if self.puzzle:
- self.window.set_focus (drawarea)
+ # reveal across/down word if any the position
+ if self.puzzle.data[self.selected_row][self.selected_col].occupied_across is True:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
+ "Are you sure you wish to reveal across word at current cell?")
+ # confirm that the user wants to reveal
+ if dlg.run () == gtk.RESPONSE_YES:
+ self.puzzle.reveal_word_across (self.selected_row, self.selected_col)
+ # redraw the grid to reveal the word
+ puzgrid = self.ui.get_object ("puzzlegrid")
+ puzgrid.queue_draw ()
+ dlg.destroy ()
+ if self.puzzle.data[self.selected_row][self.selected_col].occupied_down is True:
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
+ "Are you sure wish to reveal down word at current cell?")
+ if dlg.run () == gtk.RESPONSE_YES:
+ self.puzzle.reveal_word_down (self.selected_row, self.selected_col)
+ # redraw the grid to reveal the word
+ puzgrid = self.ui.get_object ("puzzlegrid")
+ puzgrid.queue_draw ()
+ dlg.destroy ()
- col = int (event.x / 30)
- row = int (event.y / 30)
+ # callback for menu help about activated event
+ def on_about_activate (self, menu_item):
+ # display the about dialog
+ self.about ()
+
+ # 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
- 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 ()
+ # 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
# 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
+ # set a guess only if not revealed
+ if self.puzzle.data[self.selected_row][self.selected_col].revealed is False:
+ self.puzzle.data[self.selected_row][self.selected_col].guess = guess_char
+ # 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
# 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)
+ key = gtk.gdk.keyval_name (event.keyval).lower ()
- if event.state == gtk.gdk.SHIFT_MASK and key == "Up":
+ 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":
+ 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":
+ 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":
+ 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)
drawarea.queue_draw ()
# if it is A-Z or a-z then
elif len (key) == 1 and key.isalpha ():
- self.set_guess (key)
+ guess_char = key.upper ()
+ self.set_guess (guess_char)
drawarea.queue_draw ()
# if it is the delete key then delete character at selected row/col
- elif key == "Delete":
+ elif key == "delete":
self.puzzle.data[self.selected_row][self.selected_col].guess = None
drawarea.queue_draw ()
+ # if the key is space key then delete character and move across or
+ # down one step depending on the mode
+ elif key == "space":
+ self.set_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":
+ elif key == "backspace":
self.delete_prev_guess ()
drawarea.queue_draw ()
# size the area
drawarea.set_size_request (self.puzzle.cols*30+2, self.puzzle.rows*30+2)
- #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)
ctx.move_to (col*30+10, row*30+20)
ctx.show_text (self.puzzle.data[row][col].guess)
+ # if there is a revealed solution character at the location
+ if (self.puzzle.data[row][col].revealed is True 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.8)
+ 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].char)
+
return False
# load clues to the list
down.append ([ str(self.puzzle.data[word[1]][word[2]].numbered),
clue])
+ def save_file (self, file):
+ # try to save the file
+ try:
+ cPickle.dump (self.puzzle, open (file, "wb"), cPickle.HIGHEST_PROTOCOL)
+ except (IOError, OSError, cPickle.PicklingError):
+ dlg = gtk.MessageDialog (self.window, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ "Error in saving puzzle")
+ dlg.run ()
+ dlg.destroy ()
+
+ # open a file
def open_file (self, file):
# try to open the file
try:
dlg.run ()
dlg.destroy ()
+ # about dialog
+ def about (self):
+ dlg = gtk.AboutDialog ()
+ dlg.set_name ("GetAClue Player")
+ dlg.set_copyright ("Copyright 2010 V.Harishankar")
+ dlg.set_website ("http://harishankar.org/software")
+ dlg.set_authors (("Harishankar",))
+ dlg.set_logo (self.window.get_icon())
+ dlg.set_license (self.LICENSE_TEXT)
+ dlg.set_comments ("Create and play Crossword puzzles")
+ dlg.run ()
+ dlg.destroy ()
+
def __init__ (self, file_to_play = None):
# load the user interface
self.ui = gtk.Builder ()
- self.ui.add_from_file ("playerwindow.glade")
+
+ # Path for the interface file - change this if you are distributing
+ # the application and put the icon, interface file in a different
+ # location!!
+ gladepath = os.path.join (sys.path[0], "playerwindow.glade")
+ self.ui.add_from_file (gladepath)
# window object
self.window = self.ui.get_object ("mainwindow")