[gnome-games/applygsoc2009: 48/76] Move the SudokuView class out



commit e4e66e04784b7b31e709764ce7a14f1a80c7d0a6
Author: Pablo Castellano <pablog src gnome org>
Date:   Thu Aug 26 16:43:04 2010 +0200

    Move the SudokuView class out

 gnome-sudoku/src/lib/dancer.py   |    7 +-
 gnome-sudoku/src/lib/gsudoku.py  |  698 --------------------------------------
 gnome-sudoku/src/lib/main.py     |   19 +-
 gnome-sudoku/src/lib/model.py    |  455 +++++++++++++++++++++++++
 gnome-sudoku/src/lib/printing.py |    4 +-
 gnome-sudoku/src/lib/view.py     |  244 +++++++++++++
 6 files changed, 715 insertions(+), 712 deletions(-)
---
diff --git a/gnome-sudoku/src/lib/dancer.py b/gnome-sudoku/src/lib/dancer.py
index 5da8377..9aa8689 100644
--- a/gnome-sudoku/src/lib/dancer.py
+++ b/gnome-sudoku/src/lib/dancer.py
@@ -102,7 +102,8 @@ class GridDancer:
 
 if __name__ == '__main__':
     def test_dance_grid ():
-        import gsudoku
+        import model
+        import view
         window = gtk.Window()
         game = '''9 1 6 3 2 8 4 5 7
                   5 7 4 6 1 9 2 8 3
@@ -113,8 +114,8 @@ if __name__ == '__main__':
                   4 6 9 1 8 7 5 3 2
                   1 2 8 9 5 3 6 7 4
                   7 5 3 4 6 2 8 1 9'''
-        view = gsudoku.SudokuView(9)
-        model = gsudoku.SudokuModel(game, 9)
+        view = view.SudokuView(9)
+        model = model.SudokuModel(game, 9)
         view.connect_to_model(model)
         dancer = GridDancer(view, model)
 
diff --git a/gnome-sudoku/src/lib/main.py b/gnome-sudoku/src/lib/main.py
index cb95593..0a7af43 100644
--- a/gnome-sudoku/src/lib/main.py
+++ b/gnome-sudoku/src/lib/main.py
@@ -16,7 +16,8 @@ from gettext import gettext as _
 from gettext import ngettext
 
 import game_selector
-import gsudoku
+import model
+import view
 import printing
 import saver
 import sudoku_maker
@@ -112,8 +113,8 @@ class UI (gconf_wrapper.GConfWrapper):
         """Finally enter the puzzle"""
 		self._puzzle = puzzle
         if game_type == game_selector.NewOrSavedGameSelector.NEW_GAME:
-            self._main_model = gsudoku.SudokuModel(puzzle, 9)
-            self._notes_model = gsudoku.NotesModel(self._main_model, group_size=9)
+            self._main_model = model.SudokuModel(puzzle, 9)
+            self._notes_model = model.NotesModel(self._main_model, group_size=9)
 
 #            self._main_grid_vew.connect('puzzle-finished', self.you_win_callback)
         elif game_type == game_selector.NewOrSavedGameSelector.SAVED_GAME:
@@ -127,10 +128,10 @@ class UI (gconf_wrapper.GConfWrapper):
 
     def _open_saved_game(self, jar):
         virgin, in_prog = jar["game"].split('\n')
-        self._main_model = gsudoku.SudokuModel(virgin, 9, in_prog)
+        self._main_model = model.SudokuModel(virgin, 9, in_prog)
         self._main_model.set_autofill_count(jar["gsd.auto_fills"])
         
-        self._notes_model = gsudoku.NotesModel(self._main_model, group_size=9)
+        self._notes_model = model.NotesModel(self._main_model, group_size=9)
         self._notes_model.set_hints_count(jar["gsd.hints"])
         for (x, y, top, bottom) in jar["notes"]:
              self._notes_model.set_notes(x, y, top, bottom)
@@ -192,9 +193,9 @@ class UI (gconf_wrapper.GConfWrapper):
     def setup_gui (self):
         self.initialize_prefs()
         self.setup_main_window()
-        self.gsd = gsudoku.SudokuGameDisplay()
-        self.gsd.set_parent_for(self.w)
-        self.gsd.connect('puzzle-finished', self.you_win_callback)
+        self.gsd = gsudoku.SudokuGameDisplay() #? 
+        self.gsd.set_parent_for(self.w) #?
+        self.gsd.connect('puzzle-finished', self.you_win_callback) #?
         self.setup_color()
         self._setup_actions()
         return
@@ -228,7 +229,7 @@ class UI (gconf_wrapper.GConfWrapper):
     def _setup_main_boxes(self):
         main_area = self.builder.get_object("main_area")
 
-        self._main_grid_vew = gsudoku.SudokuView(group_size=9)
+        self._main_grid_vew = view.SudokuView(group_size=9)
         main_grid_container.pack_start(self._main_grid_vew, padding=6)
 
         self.tracker_ui = tracker_box.TrackerBox(self._main_grid_vew)
diff --git a/gnome-sudoku/src/lib/model.py b/gnome-sudoku/src/lib/model.py
new file mode 100644
index 0000000..3eddf25
--- /dev/null
+++ b/gnome-sudoku/src/lib/model.py
@@ -0,0 +1,455 @@
+# -*- coding: utf-8 -*-
+import sudoku
+
+class NumberBoxModel:
+    def __init__(self, x, y, value=None, conflict=None):
+        self.x = x
+        self.y = y
+        self.value = value
+        self.conflict = conflict
+
+
+class NotesModel:
+    """Track notes for a SudokuModel
+
+    Act as a observer of SudokuModel, and a model/observable for SudokuView.
+    SudokuModel should know nothing about notes.
+    """
+
+    def __init__(self, sudoku_model, group_size):
+        self._notes = {}
+        self._observers = []
+        self._group_size = group_size
+        self._model = sudoku_model
+        self._model.add_observer(self)
+
+        self._always_show_hints = False
+
+    def toggle_auto_hint(self, flag):
+        if flag:
+            self._always_show_hints = True
+            self.update_all_hints()
+        else:
+            self._always_show_hints = False
+            self.clear_hints()
+
+    def add_observer(self, observer):
+        self._observers.append(observer)
+
+    def _signal_observers(self, changes):
+        for observer in self._observers:
+            observer.update_notes(changes)
+
+    def set_notes(self, x, y, top_note=None, bottom_note=None):
+        if (x, y) not in self._notes:
+            self._notes[(x, y)] = [None, None]
+
+        if top_note is not None:
+            self._notes[(x, y)][0] = top_note
+        if bottom_note is not None:
+            self._notes[(x, y)][0] = bottom_note
+
+        change = (x, y, top_note=top_note, bottom_note=bottom_note)
+        self._signal_observers([change])
+
+    def update(self, changes):
+        if not self._always_show_hints:
+            return
+
+        self.update_all_hints()
+
+    def update_all_hints(self):
+        for x in range(self._group_size):
+            for y in range(self._group_size):
+                self._update_hint_for_entry(x, y)
+
+    def show_hint(self, x, y):
+        self._update_hint_for_entry(x, y)
+
+    def _update_hint_for_entry(self, x, y):
+        # no need to show anything if it's already filled, being correct or not
+        if self._model.get_value(x, y):
+            self.set_notes(x, y, bottom_note="")
+            return
+
+        vals = self._model.grid.possible_values(x, y)
+        vals = list(vals)
+        vals.sort()
+        if vals:
+            txt = ''.join([str(v) for v in vals])
+            self.set_notes(x, y, bottom_note=txt)
+        else:
+            self.set_notes(x, y, bottom_note="X")
+
+
+class SudokuModel:
+
+	def __init__ (self, virgin_grid, group_size, initial_grid=None):
+        self.hints = 0
+        self.autofill_count = 0
+        self.show_impossible_implications = False
+        self.impossible_hints = 0
+        self.impossibilities = []
+        self.__trackers_tracking__ = {}
+        self.group_size = group_size
+
+        self.grid = None
+        self._virgin_grid = None
+        self._observers = []
+
+        self.__error_pairs__ = ParallelDict()
+        self._setup_virgin_grid(virgin_grid)
+        if initial_grid:
+            self._setup_initial_grid(initial_grid)
+
+    def add_observer(self, observer):
+        self._observers.append(observer)
+
+    def _signal_observers(self, values):
+        for observer in self._observers:
+            observer.update(values)
+
+    def animate_hint (self):
+        if self.hint_animate_count % 2 == 0:
+            color = (1.0, 0.0, 0.0)
+        else:
+            color = None
+        self.hint_square.set_border_color(color)
+        self.hint_animate_count += 1
+
+        if self.hint_animate_count == 4:
+            self.hint_square = None
+            return False
+
+        return True;
+
+    def get_value(self, x, y):
+        return self.grid._get_(x, y)
+
+    def set_hint_square (self, square):
+        if self.hint_square is not None:
+            self.hint_square.set_border_color(None)
+            gobject.source_remove(self.hint_timer)
+            self.hint_timer = None
+
+        if square is None:
+            self.hint_square = None
+        else:
+            self.hint_square = self.__entries__[square]
+            self.hint_animate_count = 0
+            self.animate_hint()
+            self.hint_timer = gobject.timeout_add(150, self.animate_hint)
+
+    def _signal_completeness(self):
+        for observer in self._observers:
+            observer.puzzle_finished_cb()
+
+    def reset_grid (self):
+        '''Remove all untracked values from the grid
+
+        This method is used to clear all untracked values from the grid for
+        the undo processing.  The tracked values and notes are handled higher
+        up by the caller.
+        '''
+        removed = []
+        for x in range(self.group_size):
+            for y in range(self.group_size):
+                if not self.grid.virgin._get_(x, y):
+                    e = self.__entries__[(x, y)]
+                    val = e.get_value()
+                    track = e.tracker_id
+                    if val and track == tracker_info.NO_TRACKER:
+                        removed.append((x, y, val))
+                    self.remove(x, y)
+        return removed
+
+    def apply_notelist(self, notelist, apply_tracker = False):
+        '''Re-apply notes
+
+        Re-apply notes that have been removed with the clear_notes() function.
+        The apply_tracker argument is used for the "Apply Tracker" button
+        functionality, which requires the history to be updated.
+        '''
+        for x, y, (side, notelist_index, tracker_id, note) in notelist:
+            cell = self.__entries__[x, y]
+            if apply_tracker:
+                use_tracker = tracker_info.NO_TRACKER
+                cell.emit('notes-about-to-change')
+            else:
+                use_tracker = tracker_id
+            if side == 'Top':
+                cell.top_note_list.insert(notelist_index, (use_tracker, note))
+            if side == 'Bottom':
+                cell.bottom_note_list.insert(notelist_index, (use_tracker, note))
+            if apply_tracker:
+                cell.emit('notes-changed')
+                # When applying a tracker - update the notes to remove
+                # duplicates from other trackers.
+                if side == 'Top':
+                    cell.trim_untracked_notes(cell.top_note_list)
+                else:
+                    cell.trim_untracked_notes(cell.bottom_note_list)
+        # Redraw the notes
+        self.update_all_notes()
+
+    def _setup_virgin_grid(self, virgin):
+        """both grid and initial_grid should be str"""
+        self._virgin_grid = [int(c) for c in virgin.split()]
+        self.grid = sudoku.InteractiveSudoku(virgin, self.group_size)
+
+    def _setup_initial_grid(self, initial_grid):
+        values = [int(c) for c in initial_grid.split()]
+        for row in range(self.group_size):
+            for col in range(self.group_size):
+                index = row * 9 + col
+                if values[index] and not self.grid._get_(col, row):
+                    self.set_value(col, row, values[index])
+
+    def update_all_notes (self):
+        '''Display the notes for all the cells
+
+        The notes are context sensitive to the trackers.  This method displays
+        all of the notes for the currently viewed selection.
+        '''
+        for x in range(self.group_size):
+            for y in range(self.group_size):
+                self.__entries__[(x, y)].show_note_text()
+
+    def highlight_conflicts (self, x, y):
+        '''highlight any squares that conflict with position x,y.
+
+        Conflict resolution is taken care of completely within
+        the InteractiveGrid class.  A list of conflicting cells
+        are stored in InteractiveGrid.conflicts
+        '''
+        # Return if there are no conflicts for this cell
+        if not self.grid.conflicts.has_key((x, y)):
+            return
+        # Highlight the current cell
+        self.__entries__[(x, y)].set_error_highlight(True)
+        # Then highlight any cells that conflict with it
+        for coord in self.grid.conflicts[(x, y)]:
+            self.__entries__[coord].set_error_highlight(True)
+
+    def set_value(self, x, y, value):
+        result = []
+        old_conflicts = self._remove_old_conficts (x, y)
+        new_conflicts = []
+
+        # Some value won't go into the grid, e.g. conflicts values?
+        # This makes things complicated.
+        if not value:
+            if self.grid._get_(x, y):
+                self.grid.remove(x, y)
+        else:
+            try:
+                self.grid.add(x, y, value, force=True)
+            except sudoku.ConflictError, err:
+				new_conflicts = self._find_new_conflicts(x, y, value)
+
+        flag = True if new_conflicts else False
+        result.append(NumberBoxModel(x, y, value, conflict=flag))
+        result.extend(old_conflicts)
+        result.extend(new_conflicts)
+
+        self._signal_observers(result)
+        self._check_for_completeness()
+
+    def get_virgin_value(self, x, y):
+        return self._virgin_grid[9 * y + x]
+
+    def _remove_old_conficts(self, x, y):
+        result = []
+        old_conflicts = self._remove_related_conflicts(x, y)
+        for entry in old_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=False)
+            result.append(change)
+        return result
+
+    def _find_new_conflicts(self, x, y, value):
+        result = []
+        new_conflicts = self.grid.find_conflicts(x, y, value)
+        if new_conflicts:
+            self.__error_pairs__[(x, y)] = new_conflicts
+        for entry in new_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=True)
+            result.append(change)
+        return result
+
+    def _remove_related_conflicts(self, x, y):
+        result = []
+        if self.__error_pairs__.has_key((x, y)):
+            errors_removed = self.__error_pairs__[(x, y)]
+            del self.__error_pairs__[(x, y)]
+            for coord in errors_removed:
+                # If we're not an error by some other pairing...
+                if not self.__error_pairs__.has_key(coord):
+                    result.append(coord)
+        return result
+
+    def _check_for_completeness(self):
+        if self.grid.check_for_completeness():
+            self._signal_completeness()
+
+    def set_autofill_count(self, count):
+        self.autofill_count = count
+
+    def get_autofill_count(self):
+        return self.autofill_count
+
+    def auto_fill (self):
+		self.autofill_count += 1
+        changed = set(self.grid.auto_fill()) # there are duplicate?
+        for coords, val in changed:
+		    self.set_value(coords[0], coords[1], val)
+        return changed
+
+    def auto_fill_current (self, x, y):
+        filled = self.grid.auto_fill_for_xy(x, y)
+        if filled and filled != -1:
+			self.set_value(x, y, filled[1])
+
+    def __set_impossible (self, coords, setting):
+        '''Call set_impossible() on a grid entry
+
+        This function is a helper for the 'Warn about unfillable squares'
+        feature.  It only calls set_impossible() if the option is on to prevent
+        a check against the show_impossible_implications flag elsewhere.  The
+        return from this function indicates whether or not the cell "may"
+        have been modified, which is basically the value of the option setting.
+        '''
+        if self.show_impossible_implications:
+            self.__entries__[coords].set_impossible(setting)
+        return self.show_impossible_implications
+
+    def display_impossible_implications (self):
+        '''Start X-marking cells that have no possible values
+        '''
+        self.show_impossible_implications = True
+        for imp_cell in self.impossibilities:
+            self.__set_impossible(imp_cell, True)
+            self.impossible_hints += 1
+        if self.always_show_hints:
+            self.update_all_hints()
+
+    def hide_impossible_implications (self):
+        '''Stop X-marking cells that have no possible values
+        '''
+        for imp_cell in self.impossibilities:
+            self.__set_impossible(imp_cell, False)
+        self.show_impossible_implications = False
+        if self.always_show_hints:
+            self.update_all_hints()
+
+    def _mark_impossible_implications (self, x, y, check_conflicts = True):
+        '''Mark cells with X if they have no possible values
+
+        The hint this method provides can be turned on and off from the
+        menu Tools->'Warn about unfillable squares' option.
+
+        The check_conflicts parameter is for internal use only.  It is used as
+        a one level recursion on conflicts that the original target is involved
+        with.
+
+        Impossibilities are tracked regardless of the user's option setting.
+        This was done to allow the user to toggle the option mid-game and still
+        behave properly.  Conditional X-marking of cells happens in the
+        __set_impossible() function.
+        '''
+        # Make sure we have a grid to work with
+        if not self.grid:
+            return
+        # Flag whether or not we need to update hints
+        grid_modified = False
+        # Find any new impossible cells based on calling cell
+        implications = self.grid.find_impossible_implications(x, y)
+        if implications:
+            for imp_cell in implications:
+                grid_modified = self.__set_impossible(imp_cell, True)
+                # Add them to the list if they aren't there already...
+                if not imp_cell in self.impossibilities:
+                    self.impossibilities.append(imp_cell)
+                    # But don't score it unless the option is on
+                    if self.show_impossible_implications:
+                        self.impossible_hints += 1
+        # Reset the list of impossible cells ignoring the called cell. Use a
+        # copy to iterate over, so items can be removed while looping.
+        if self.impossibilities:
+            for imp_cell in self.impossibilities[:]:
+                if imp_cell == (x, y):
+                    continue
+                if self.grid.possible_values(*imp_cell):
+                    self.impossibilities.remove(imp_cell)
+                    grid_modified = self.__set_impossible(imp_cell, False)
+                else:
+                    grid_modified = self.__set_impossible(imp_cell, True)
+        # If any conflicts have been cleared or created, mark any impossible
+        # cells they may have caused or removed
+        if check_conflicts:
+            for xx, yy in self.grid.cleared_conflicts:
+                self.mark_impossible_implications(xx, yy, False)
+            if self.grid.conflicts.has_key((x, y)):
+                for xx, yy in self.grid.conflicts[(x, y)]:
+                    self.mark_impossible_implications(xx, yy, False)
+        # Update the hints if we need to
+        if grid_modified and self.always_show_hints:
+            self.update_all_hints()
+
+    def delete_by_tracker (self):
+        '''Delete all cells tracked by the current tracker
+
+        The values are deleted from the tracker as well as the visible grid.
+        '''
+        ret = []
+        tracker = self.tinfo.get_tracker(self.tinfo.showing_tracker)
+        if not tracker:
+            return ret
+        for (x, y), value in tracker.items():
+            ret.append((x, y, value, self.tinfo.showing_tracker))
+            self.remove(x, y)
+            if self.grid and self.grid._get_(x, y):
+                self.grid.remove(x, y)
+        return ret
+
+    def cover_track(self, hide = False):
+        '''Hide the current tracker
+
+        All tracked values are deleted from the display, but kept by the
+        tracker.  Setting hide to True changes prevents anything but untracked
+        values to be shown after the call.
+        '''
+        track = self.tinfo.get_tracker(self.tinfo.showing_tracker)
+        if track:
+            for coord in track.keys():
+                self.__entries__[coord].set_value(0, tracker_info.NO_TRACKER)
+                self.grid.remove(*coord)
+                self._remove_error_highlight()
+                self.mark_impossible_implications(*coord)
+        if hide:
+            self.tinfo.hide_tracker()
+        # Update all hints if we need to
+        if self.always_show_hints and not self.doing_initial_setup:
+            self.update_all_hints()
+
+    def show_track(self):
+        '''Displays the current tracker items
+
+        The values and notes for the currently showing tracker will be
+        displayed
+        '''
+        track = self.tinfo.get_tracker(self.tinfo.showing_tracker)
+        if not track:
+            return
+        for (x, y), value in track.items():
+            self.__entries__[(x, y)].set_value(value, self.tinfo.showing_tracker)
+            self.__entries__[(x, y)].recolor(self.tinfo.showing_tracker)
+            # Add it to the underlying grid
+            self.grid.add(x, y, value, True)
+            # Highlight any conflicts that the new value creates
+            self.highlight_conflicts(x, y)
+            # Draw our entry
+            self.__entries__[(x, y)].queue_draw()
+            self.mark_impossible_implications(x, y)
+        # Update all hints if we need to
+        if self.always_show_hints and not self.doing_initial_setup:
+            self.update_all_hints()
diff --git a/gnome-sudoku/src/lib/printing.py b/gnome-sudoku/src/lib/printing.py
index e0f0bf0..806a512 100644
--- a/gnome-sudoku/src/lib/printing.py
+++ b/gnome-sudoku/src/lib/printing.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 import gtk, cairo, time
 import os.path
-import sudoku, gsudoku, saver, defaults
+import sudoku, model, saver, defaults
 from gtk_goodies import gconf_wrapper
 
 def fit_squares_in_rectangle (width, height, n, margin = 0):
@@ -85,7 +85,7 @@ class SudokuPrinter:
             cr.move_to(left, top - height / 2)
             cr.show_text(label)
 
-            if isinstance(sudoku, gsudoku.SudokuGameDisplay):
+            if isinstance(sudoku, model.SudokuGameDisplay):
                 sudoku = sudoku.grid
 
             sudoku_thumber.draw_sudoku (cr, sudoku.grid, None, best_square_size, left, top, for_printing = True)
diff --git a/gnome-sudoku/src/lib/view.py b/gnome-sudoku/src/lib/view.py
new file mode 100644
index 0000000..ec68c32
--- /dev/null
+++ b/gnome-sudoku/src/lib/view.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+import math
+
+import gtk
+import gobject
+
+import colors
+import number_box
+
+def gtkcolor_to_rgb (color):
+    return (color.red   / float(2**16),
+            color.green / float(2**16),
+            color.blue  / float(2**16))
+
+class SudokuNumberGrid (gtk.AspectFrame):
+
+    def __init__ (self, group_size = 9):
+        self.table = gtk.Table(rows = group_size, columns = group_size, homogeneous = True)
+        self.group_size = group_size
+        self.__entries__ = {}
+        for x in range(self.group_size):
+            for y in range(self.group_size):
+                e = number_box.SudokuNumberBox(upper = self.group_size)
+                e.x = x
+                e.y = y
+                self.table.attach(e, x, x+1, y, y+1,
+                                  )
+                self.__entries__[(x, y)] = e
+        gtk.AspectFrame.__init__(self, obey_child = False)
+        self.set_shadow_type(gtk.SHADOW_NONE)
+        self.eb = gtk.EventBox()
+        self.eb.add(self.table)
+        self.add(self.eb)
+        self.table.set_row_spacings(1)
+        self.table.set_col_spacings(1)
+        box_side = int(math.sqrt(self.group_size))
+        for n in range(1, box_side):
+            self.table.set_row_spacing(box_side*n-1, 2)
+            self.table.set_col_spacing(box_side*n-1, 2)
+        self.table.set_border_width(2)
+        self.show_all()
+
+    def set_color(self, x, y, color):
+        self.__entries__[(x, y)].set_color(color)
+
+    def set_parent_for(self, parent):
+        for entry in self.__entries__.values():
+            entry.set_parent_win(parent)
+
+    def set_timer(self, timer):
+        for entry in self.__entries__.values():
+            entry.set_timer(timer)
+
+    def set_bg_color (self, color):
+        if type(color) == str:
+            try:
+                color = gtk.gdk.color_parse(color)
+            except:
+                print 'set_bg_color handed Bad color', color
+                return
+        self.eb.modify_bg(gtk.STATE_NORMAL, color)
+        self.eb.modify_base(gtk.STATE_NORMAL, color)
+        self.eb.modify_fg(gtk.STATE_NORMAL, color)
+        self.table.modify_bg(gtk.STATE_NORMAL, color)
+        self.table.modify_base(gtk.STATE_NORMAL, color)
+        self.table.modify_fg(gtk.STATE_NORMAL, color)
+        for e in self.__entries__.values():
+            e.modify_bg(gtk.STATE_NORMAL, color)
+
+    def get_focus(self):
+	    return self.table.get_focus_child()
+
+
+class SudokuView (SudokuNumberGrid, gobject.GObject):
+
+    # some signals to give notice about change of the View
+    __gsignals__ = {
+            # atm. only used by dancer
+            "puzzle-finished": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+            # atm. only used by tracker
+            "view-updated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+                (int, int, int))}
+
+    def __init__(self, group_size):
+        SudokuNumberGrid.__init__(self, group_size)
+#        self.hint_square = None
+#        self.tinfo = tracker_info.TrackerInfo()
+        gobject.GObject.__init__(self)
+        SudokuNumberGrid.__init__(self, group_size = group_size)
+
+        self._model = None
+        self._do_highlight_cells = False
+		self._notes_model = None
+
+        for e in self.__entries__.values():
+            e.show()
+            e.connect('number-changed', self._number_changed_cb)
+            e.connect('notes-changed', self._notes_changed_cb)
+            e.connect('focus-in-event', self._focus_callback)
+            e.connect('key-press-event', self._key_press_cb)
+        self.connect('focus-changed', self._highlight_cells)
+
+    def connect_to_model(self, model):
+        if not model:
+            return
+        self._model = model
+        model.add_observer(self)
+        for x in range(self.group_size):
+            for y in range(self.group_size):
+                val = model.get_value(x, y)
+                if val:
+                    self.set_value(x, y, val)
+                    if model.get_virgin_value(x, y):
+                        self.set_readonly_appearance(x, y, True)
+
+    def connect_to_notes_model(self, model):
+        self._notes_model = model
+        self._notes_model.add_observer(self)
+
+    def update(self, values):
+        """Update the GUI, called by a Model
+
+        values should be a list of NumberBoxModel"""
+        for box in values:
+            x, y = box.x, box.y,
+            if box.value is not None:
+                self.set_value(x, y, box.value)
+				self.emit("view-updated", x, y, box.value)
+            if box.conflict is not None:
+                self._show_conflict(x, y, box.conflict)
+
+    def puzzle_finished_cb(self):
+        self.emit("puzzle-finished")
+
+    def _show_conflict(self, x, y, conflict):
+        self.__entries__[(x, y)].set_error_highlight(conflict)
+        
+    def update_notes(self, notes_list):
+        for notes in notes_list:
+            x, y, top_note, bottom_note = notes
+            self.__entries__[(x, y)].set_notes((top_note, bottom_note))
+
+    def _number_changed_cb(self, widget, new_number):
+        print "user input: number", (widget.x, widget.y, new_number)
+        self._model.set_value(widget.x, widget.y, new_number)
+		self.update_model(widget.x, widget.y, new_number)
+
+    def _number_changed_cb(self, widget, new_number):
+        print "user input: number", (widget.x, widget.y, new_number)
+        self._model.set_value(widget.x, widget.y, new_number)
+		self.update_model(widget.x, widget.y, new_number)
+
+    def update_model(self, x, y, value):
+        self._model.set_value(x, y, value)
+
+    def _notes_changed_cb(self, widget, top_note, bottom_note):
+		self._notes_model.set_notes(widget.x, widget.y, top_note, bottom_note)
+
+    def _focus_callback(self, widget, event):
+        if self._do_highlight_cells:
+            self._highlight_cells()
+#        self.emit('focus-changed')
+
+    def _key_press_cb (self, widget, event):
+        key = gtk.gdk.keyval_name(event.keyval)
+        dest = self._go_around(widget.x, widget.y, key)
+        if dest:
+            self.table.set_focus_child(self.__entries__[dest])
+
+    def toggle_highlight (self, val):
+        self._do_highlight_cells = val
+        if val:
+            self._unhighlight_cells()
+        else:
+            self._highlight_cells()
+
+    def _unhighlight_cells (self):
+        for e in self.__entries__.values():
+            e.set_background_color(None)
+
+    def _highlight_cells (self):
+        focused = self.get_focus()
+        if not focused: # no need to do anything if no box has focus
+            return
+        if not self.do_highlight_cells:
+            return
+        self._unhighlight_cells() # first clear all the cells
+        if focused:
+            self._get_highlight_colors()
+        my_x, my_y = focused.x, focused.y
+
+        grid = self._model.grid
+        for x, y in grid.col_coords[my_x]:
+            if (x, y) != (my_x, my_y):
+                self.__entries__[(x, y)].set_background_color(self.col_color)
+        for x, y in grid.row_coords[my_y]:
+            if (x, y) != (my_x, my_y):
+                self.__entries__[(x, y)].set_background_color(self.row_color)
+        for x, y in grid.box_coords[grid.box_by_coords[(my_x, my_y)]]:
+            if (x, y) != (my_x, my_y):
+                e = self.__entries__[(x, y)]
+                if x == my_x:
+                    e.set_background_color(self.box_and_col_color)
+                elif y == my_y:
+                    e.set_background_color(self.box_and_row_color)
+                else:
+                    e.set_background_color(self.box_color)
+
+    def _get_highlight_colors(self):
+        entry = self.__entries__.values()[0]
+        default_color = gtkcolor_to_rgb(entry.style.bg[gtk.STATE_SELECTED])
+        hsv = colors.rgb_to_hsv(*default_color)
+        box_s = hsv[1]
+        box_v = hsv[2]
+        if box_v < 0.5:
+            box_v = box_v * 2
+        if box_s > 0.75:
+            box_s = box_s * 0.5
+        else:
+            box_s = box_s * 1.5
+            if box_s > 1:
+                box_s = 1.0
+        self.box_color = colors.hsv_to_rgb(hsv[0], box_s, box_v)
+        self.box_and_row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33 / 2})
+        self.row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33})
+        self.col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.66})
+        self.box_and_col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 1.0 - (0.33 / 2)})
+
+    def _go_around(self, x, y, direction):
+        '''return the coordinate if we should go to the other side of the grid.
+        Or else return None.'''
+        limit_min, limit_max = 0, self.group_size - 1
+        y_edge = [(limit_min, 'Up'), (limit_max, 'Down')]
+        x_edge = [(limit_min, 'Left'), (limit_max, 'Right')]
+        opposite_edge = {limit_min: limit_max, limit_max: limit_min}
+
+        if (y, direction) in y_edge:
+            return x, opposite_edge[y]
+        elif (x, direction) in x_edge:
+            return opposite_edge[x], y
+        else:
+            return None, None
+
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]