X-Git-Url: https://harishankar.org/repos/?p=getaclue.git;a=blobdiff_plain;f=player_mainwindow.py;h=b384b542cd1aa51a01c4f39fc1b3bc9cc49b12bf;hp=58de825f56548c6af95a49f3edda2d231cf3adae;hb=d7084b7094c99003960b33d4bd4fb655248c6ab3;hpb=8178235fb21779eed3615e502f2c4fdebd42150e diff --git a/player_mainwindow.py b/player_mainwindow.py index 58de825..b384b54 100644 --- a/player_mainwindow.py +++ b/player_mainwindow.py @@ -4,14 +4,420 @@ # Main window class for GetAClue player +import cPickle import pygtk pygtk.require20 () import gtk +import cairo + +import crosswordpuzzle class MainWindow: + # typing mode constants + ACROSS = 1 + DOWN = 2 + def gtk_main_quit (self, *args): gtk.main_quit () + # 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 () + + # 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 () + + + # 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: + # 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 + # 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) + + 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: + # 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.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) + + # 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 + def load_clues (self): + # get the clues list store objects + across = self.ui.get_object ("clues_across") + down = self.ui.get_object ("clues_down") + across.clear () + down.clear () + + # if puzzle is loaded + if self.puzzle: + clues_across = self.puzzle.get_clues_across () + clues_down = self.puzzle.get_clues_down () + # insert the numbers and the clues for across + for word, clue in clues_across: + across.append ([str(self.puzzle.data[word[1]][word[2]].numbered), + clue]) + # insert the numbers and the clues for down + for word, clue in clues_down: + down.append ([ str(self.puzzle.data[word[1]][word[2]].numbered), + clue]) + + def open_file (self, file): + # 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.ui = gtk.Builder () @@ -21,7 +427,7 @@ class MainWindow: 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) @@ -41,9 +447,12 @@ class MainWindow: # connect the signals self.ui.connect_signals (self) - # set the window title + # set the puzzle to None + self.puzzle = None + + # open the file if it is set if file_to_play: - self.window.set_title ("GetAClue player - " + file_to_play) + self.open_file (file_to_play) gtk.main ()