[gnome-games/applygsoc2009: 48/76] Move the SudokuView class out
- From: Pablo Castellano <pablog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games/applygsoc2009: 48/76] Move the SudokuView class out
- Date: Mon, 6 Sep 2010 02:51:15 +0000 (UTC)
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]