[gnome-games/applygsoc2009: 33/76] XXX: Towards split view and model



commit a2480e430936ec05db1e30410f4a3369b1bf1bdf
Author: Pablo Castellano <pablog src gnome org>
Date:   Thu Aug 26 15:12:03 2010 +0200

    XXX: Towards split view and model
    
    * SudokuGameDisplay is splitted into SudokuView and SudokuModel.
    * Much work is still needed, but the rough shape should be seen. Now the game
      can be launched.

 gnome-sudoku/src/lib/gsudoku.py |  303 ++++++++++++++++++++++-----------------
 gnome-sudoku/src/lib/main.py    |   33 +++--
 2 files changed, 187 insertions(+), 149 deletions(-)
---
diff --git a/gnome-sudoku/src/lib/gsudoku.py b/gnome-sudoku/src/lib/gsudoku.py
index 391cc6c..040c445 100644
--- a/gnome-sudoku/src/lib/gsudoku.py
+++ b/gnome-sudoku/src/lib/gsudoku.py
@@ -71,29 +71,18 @@ class SudokuNumberGrid (gtk.AspectFrame):
 	    return self.table.get_focus_child()
 
 
-class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
+class SudokuView (SudokuNumberGrid, gobject.GObject):
 
-    __gsignals__ = {
-        'focus-changed':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        'puzzle-finished':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
-        }
-
-    do_highlight_cells = False
-
-    def __init__ (self, grid = None, group_size = 9,
-                  show_impossible_implications = False):
-        group_size = int(group_size)
-        self.hints = 0
-        self.hint_square = None
-        self.always_show_hints = False
-        self.show_impossible_implications = show_impossible_implications
-        self.impossible_hints = 0
-        self.impossibilities = []
-        self.trackers = {}
-        self.tinfo = tracker_info.TrackerInfo()
+    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._setup_grid(grid, group_size)
+
+        self._model = None
+        self._do_highlight_cells = False
+
         for e in self.__entries__.values():
             e.show()
             e.connect('number-changed', self._number_changed_cb)
@@ -102,8 +91,49 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             e.connect('key-press-event', self._key_press_cb)
         self.connect('focus-changed', self._highlight_cells)
 
-    def _notes_changed_cb(self, box, top_note, bottom_note):
-        box.set_notes((top_note, bottom_note))
+    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.grid._get_(x, y)
+                if val:
+                    self.set_readonly_appearance(x, y, True)
+                    self.set_value(x, y, val)
+
+    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)
+            if box.top_note is not None or box.bottom_note is not None:
+                self._set_notes(x, y, box.top_note, box.bottom_note)
+            if box.conflict is not None:
+                self._show_conflict(x, y, box.conflict)
+
+    def _show_conflict(self, x, y, conflict):
+        self.__entries__[(x, y)].set_error_highlight(conflict)
+
+    def _set_notes(self, x, y, top_note, bottom_note):
+        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)
+
+    def _notes_changed_cb(self, widget, top_note, bottom_note):
+        print "user input: notes", (widget.x, widget.y, top_note, bottom_note)
+        self._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)
@@ -111,47 +141,8 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if dest:
             self.table.set_focus_child(self.__entries__[dest])
 
-    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)
-        if   (y, direction) == (limit_min, 'Up'):
-            dest = (x, limit_max)
-        elif (y, direction) == (limit_max, 'Down'):
-            dest = (x, limit_min)
-        elif (x, direction) == (limit_min, 'Left'):
-            dest = (limit_max, y)
-        elif (x, direction) == (limit_max, 'Right'):
-            dest = (limit_min, y)
-        else:
-            return None
-        return dest
-
-    def focus_callback (self, e, event):
-        self.emit('focus-changed')
-
-    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 toggle_highlight (self, val):
-        self.do_highlight_cells = val
+        self._do_highlight_cells = val
         if val:
             self._unhighlight_cells()
         else:
@@ -172,17 +163,14 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             self._get_highlight_colors()
         my_x, my_y = focused.x, focused.y
 
-        # col_coords can sometimes be null.
-        if not hasattr(self.grid, 'col_coords'):
-            return
-
-        for x, y in self.grid.col_coords[my_x]:
+        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 self.grid.row_coords[my_y]:
+        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 self.grid.box_coords[self.grid.box_by_coords[(my_x, my_y)]]:
+        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:
@@ -192,6 +180,81 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
                 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
+
+
+class NumberBoxModel:
+    def __init__(self, x, y, value=None, top_note=None, bottom_note=None,
+            conflict=None):
+        self.x = x
+        self.y = y
+        self.value = value
+        self.top_note = top_note
+        self.bottom_note = bottom_note
+        self.conflict = conflict
+
+
+class SudokuModel(gobject.GObject):
+
+    __gsignals__ = {
+        'puzzle-finished':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
+        }
+
+    def __init__ (self, grid, group_size):
+        group_size = int(group_size)
+        self.hints = 0
+        self.always_show_hints = False
+        self.auto_fills = 0
+        self.show_impossible_implications = False
+        self.impossible_hints = 0
+        self.impossibilities = []
+        self.trackers = {}
+        self.__trackers_tracking__ = {}
+        gobject.GObject.__init__(self)
+        self._setup_grid(grid, group_size)
+
+        self._observers = []
+
+    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)
@@ -409,17 +472,10 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
                         self.add_value(col, row, values[index])
 
     def _setup_grid (self, grid, group_size):
-        self.doing_initial_setup = True
         if isinstance(grid, sudoku.SudokuGrid):
             self.grid = sudoku.InteractiveSudoku(grid.grid, group_size = grid.group_size)
         else:
             self.grid = sudoku.InteractiveSudoku(grid, group_size = group_size)
-        for x in range(group_size):
-            for y in range(group_size):
-                val = self.grid._get_(x, y)
-                if val:
-                    self.add_value(x, y, val)
-        self.doing_initial_setup = False
 
     def _number_changed_cb(self, widget, new_value):
         # TODO
@@ -475,12 +531,42 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         for coord in self.grid.conflicts[(x, y)]:
             self.__entries__[coord].set_error_highlight(True)
 
-    def set_value(self, x, y, val):
-        '''Sets value for position x, y to val.
-
-        Calls set_text_interactive so the history list is updated.
-        '''
-        self.__entries__[(x, y)].set_text_interactive(str(val))
+    def set_value(self, x, y, value):
+        result = []
+        # first remove conflicts; grid.add should return conflicts together
+        # with ConflictError
+        old_conflicts = self._remove_related_conflicts(x, y)
+        new_conflicts = []
+        for entry in old_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=False)
+            result.append(change)
+
+        try:
+            self.grid.add(x, y, value) # force=True???
+        except sudoku.ConflictError, err:
+            new_conflicts = self.grid.find_conflicts(x, y, value)
+        has_conflict = True if new_conflicts else False
+        result.append(NumberBoxModel(x, y, value, conflict=has_conflict))
+        if new_conflicts:
+            self._record_conflicts(x, y, new_conflicts)
+        for entry in new_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=True)
+            result.append(change)
+        self._signal_observers(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 _record_conflicts(self, x, y, new_conflicts):
+        self.__error_pairs__[(x, y)] = new_conflicts
 
     def add_value (self, x, y, val, tracker = None):
         """Add value val at position x, y.
@@ -548,19 +634,6 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if not self.doing_initial_setup:
             self._mark_impossible_implications(x, y)
 
-    def _remove_error_highlight (self):
-        '''remove error highlight from [x, y] and also all errors caused by it
-
-        Conflict resolution is now handled within the InteractiveSudoku class.
-        If any conflicts were cleared on the last remove() then they are
-        stored in grid.cleared_conflicts
-        '''
-        if not self.grid.cleared_conflicts:
-            return
-        for coord in self.grid.cleared_conflicts:
-            linked_entry = self.__entries__[coord]
-            linked_entry.set_error_highlight(False)
-
     def auto_fill (self):
         changed = self.grid.auto_fill()
         retval = []
@@ -725,43 +798,3 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if self.always_show_hints and not self.doing_initial_setup:
             self.update_all_hints()
 
-if __name__ == '__main__':
-    window = gtk.Window()
-    window.connect('delete-event', gtk.main_quit)
-
-    def test_number_grid ():
-        t = SudokuNumberGrid(4)
-        window.add(t)
-        t.__entries__[(0, 1)].set_color((0.0, 1.0, 0.0))
-        t.__entries__[(0, 1)].set_value(4)
-        t.__entries__[(1, 1)].set_error_highlight(True)
-        t.__entries__[(1, 1)].set_value(1)
-        t.__entries__[(2, 1)].set_color((0.0, 0.0, 1.0))
-        t.__entries__[(2, 1)].set_error_highlight(True)
-        t.__entries__[(2, 1)].set_value(2)
-        t.__entries__[(3, 1)].set_color((0.0, 0.0, 1.0))
-        t.__entries__[(3, 1)].set_error_highlight(True)
-        t.__entries__[(3, 1)].set_error_highlight(False)
-        t.__entries__[(3, 1)].set_value(3)
-        t.__entries__[(3, 1)].set_note_text('234', '12')
-
-    def test_sudoku_game ():
-        game = '''1 8 4 2 0 0 0 0 0
-                  0 6 0 0 0 9 1 2 0
-                  0 2 0 0 8 0 0 0 0
-                  0 1 8 0 5 0 0 0 0
-                  9 0 0 0 0 0 0 0 3
-                  0 0 0 0 1 0 6 5 0
-                  0 0 0 0 9 0 0 8 0
-                  0 5 7 1 0 0 0 9 0
-                  0 0 0 0 0 3 5 4 7'''
-        sgd = SudokuGameDisplay(game)
-        sgd.set_bg_color('black')
-        window.add(sgd)
-        window.show_all()
-
-#    test_number_grid()
-#    reproduce_foobared_rendering()
-    test_sudoku_game()
-    window.show_all()
-    gtk.main()
diff --git a/gnome-sudoku/src/lib/main.py b/gnome-sudoku/src/lib/main.py
index cf25f32..7aa7e7c 100644
--- a/gnome-sudoku/src/lib/main.py
+++ b/gnome-sudoku/src/lib/main.py
@@ -58,6 +58,8 @@ class UI (gconf_wrapper.GConfWrapper):
                                             )
         self.sudoku_maker = sudoku_maker.SudokuMaker()
         self.sudoku_tracker = saver.SudokuTracker()
+        self._main_model = None
+        self._main_grid_vew = None
         self.setup_gui()
         self.timer = timer.ActiveTimer(self.w)
         self.gsd.set_timer(self.timer)
@@ -106,10 +108,13 @@ class UI (gconf_wrapper.GConfWrapper):
             logger.warning("failed to genereate new puzzle")
             return None
 
-    def _open_game(self, type, puzzle):
+    def _open_game(self, game_type, puzzle):
         """Finally enter the puzzle"""
+		self._puzzle = puzzle
         if type == game_selector.NewOrSavedGameSelector.NEW_GAME:
-            self.gsd.change_grid(puzzle, 9)
+            self._main_model = gsudoku.SudokuModel(puzzle, 9)
+            self._main_grid_vew.connect_to_model(self._main_model)
+#            self._main_grid_vew.connect('puzzle-finished', self.you_win_callback)
         elif type == game_selector.NewOrSavedGameSelector.SAVED_GAME:
             saver.open_game(self, puzzle)
         self._post_open_setup()
@@ -155,6 +160,7 @@ class UI (gconf_wrapper.GConfWrapper):
         self.gsd.connect('puzzle-finished', self.you_win_callback)
         self.setup_color()
         self._setup_actions()
+        return
         self.setup_undo()
         self.setup_autosave()
         #TODO
@@ -185,9 +191,8 @@ class UI (gconf_wrapper.GConfWrapper):
     def _setup_main_boxes(self):
         main_area = self.builder.get_object("main_area")
 
-        self.gsd = gsudoku.SudokuGameDisplay()
-        self.gsd.connect('puzzle-finished', self.you_win_callback)
-        main_area.pack_start(self.gsd, padding = 6)
+        self._main_grid_vew = gsudoku.SudokuView(group_size=9)
+        main_grid_container.pack_start(self._main_grid_vew, padding=6)
 
         self.tracker_ui = tracker_box.TrackerBox(self)
         self.tracker_ui.show_all()
@@ -198,7 +203,7 @@ class UI (gconf_wrapper.GConfWrapper):
         # setup background colors
         bgcol = self.gconf['bg_color']
         if bgcol != '':
-            self.gsd.set_bg_color(bgcol)
+            self._main_grid_vew.set_bg_color(bgcol)
 
     def _setup_actions (self):
         toggle_actions = [
@@ -340,7 +345,7 @@ class UI (gconf_wrapper.GConfWrapper):
         """Close current running game
 
         Return True if game is closed, or else return False"""
-        if (self.gsd.grid and self.gsd.grid.is_changed() and not self.won):
+        if (self._main_model.grid and self._main_model.grid.is_changed() and not self.won):
             try:
                 if dialog_extras.getBoolean(
                     label = _("Save current game?"),
@@ -357,7 +362,7 @@ class UI (gconf_wrapper.GConfWrapper):
 
     def do_stop (self):
         self.stop_dancer()
-        self.gsd.grid = None
+        self._main_modelgsd.grid = None
         self.tracker_ui.reset()
         self.history.clear()
         self.won = False
@@ -369,8 +374,8 @@ class UI (gconf_wrapper.GConfWrapper):
 
     def quit_cb (self, *args):
         self.w.hide()
-        if (self.gsd.grid
-            and self.gsd.grid.is_changed()
+        if (self._main_model.grid
+            and self._main_model.grid.is_changed()
             and (not self.won)):
             self.save_game(self)
         # make sure we really go away before doing our saving --
@@ -468,7 +473,7 @@ class UI (gconf_wrapper.GConfWrapper):
         # Redraw the notes
         self.gsd.update_all_notes()
         # Make sure we're still dancing if we undo after win
-        if self.gsd.grid.check_for_completeness():
+        if self._main_model.grid.check_for_completeness():
             self.start_dancer()
 
     def show_hint_cb (self, *args):
@@ -521,13 +526,13 @@ class UI (gconf_wrapper.GConfWrapper):
             self.gsd.toggle_highlight(False)
 
     def show_info_cb (self, *args):
-        if not self.gsd.grid:
+        if not self._main_model.grid:
             dialog_extras.show_message(parent = self.w,
                                        title = _("Puzzle Information"),
                                        label = _("There is no current puzzle.")
                                        )
             return
-        puzzle = self.gsd.grid.virgin.to_string()
+        puzzle = self._main_model.grid.virgin.to_string()
         diff = self.sudoku_maker.get_difficulty(puzzle)
         information = _("Calculated difficulty: ")
         information += diff.value_string()
@@ -549,7 +554,7 @@ class UI (gconf_wrapper.GConfWrapper):
     def autosave (self):
         # this is called on a regular loop and will autosave if we
         # have reason to...
-        if self.gsd.grid and self.gsd.grid.is_changed() and not self.won:
+        if self._main_model.grid and self._main_model.grid.is_changed() and not self.won:
             self.sudoku_tracker.save_game(self)
         return True
 



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