X-Git-Url: https://harishankar.org/repos/?p=getaclue.git;a=blobdiff_plain;f=player_mainwindow.py;h=e167f039571f99f1196bbdf1b21dbd67a8b4cdf4;hp=366cbfa886280aa8afa8b4147970aba784cf0f37;hb=ccfc49515acfe83e5cab9878d095de25024dab97;hpb=f07b2f0668053c6bf2c786376f3602509e2efe0a diff --git a/player_mainwindow.py b/player_mainwindow.py index 366cbfa..e167f03 100644 --- a/player_mainwindow.py +++ b/player_mainwindow.py @@ -4,6 +4,8 @@ # Main window class for GetAClue player +import sys +import os.path import cPickle import pygtk pygtk.require20 () @@ -16,41 +18,211 @@ 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 .""" + + # 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") + + 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 @@ -85,23 +257,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 + # 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 @@ -118,31 +292,60 @@ class MainWindow: # 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 key release event - def on_puzzlegrid_key_press_event (self, drawarea, event): + # 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: - key = gtk.gdk.keyval_name (event.keyval) + self.window.set_focus (drawarea) + + col = int (event.x / 30) + row = int (event.y / 30) - if event.state == gtk.gdk.SHIFT_MASK and key == "Up": + 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 main window key release event + def on_mainwindow_key_press_event (self, window, event): + if self.puzzle: + drawarea = self.ui.get_object ("puzzlegrid") + 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": + 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) @@ -150,16 +353,22 @@ class MainWindow: 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 () @@ -187,8 +396,6 @@ class MainWindow: # 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) @@ -237,6 +444,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 @@ -260,6 +478,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: @@ -301,10 +531,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")