Added an icon and about dialog to player app
[getaclue.git] / player_mainwindow.py
index 3d528cc..24fabf6 100644 (file)
@@ -4,6 +4,8 @@
 
 # Main window class for GetAClue player
 
+import sys
+import os.path
 import cPickle
 import pygtk
 pygtk.require20 ()
@@ -16,22 +18,168 @@ class MainWindow:
        # 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))
+
+               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)
 
-       def gtk_main_quit (self, *args):
+                       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 menu item reveal word activated event
+       def on_revealword_activate (self, menuitem):
+               if self.puzzle:
+                       # 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 ()
+
+       # 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
@@ -112,23 +260,25 @@ class MainWindow:
        # 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.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
@@ -214,7 +364,7 @@ class MainWindow:
                        # 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 ()
 
@@ -290,6 +440,17 @@ class MainWindow:
                                                        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
@@ -313,6 +474,18 @@ class MainWindow:
                                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:
@@ -354,10 +527,28 @@ class MainWindow:
                        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")