[gnome-games] Tracker overhaul



commit 01329b9edb8c0f6d787b8fb7f6d7fb76698165b5
Author: Jim Ross <jimbo dimensia com>
Date:   Tue May 25 10:55:36 2010 +1000

    Tracker overhaul

 gnome-sudoku/data/tracker.ui         |  259 ++++++++++++++++-------
 gnome-sudoku/src/lib/gsudoku.py      |  376 +++++++++++++++++++---------------
 gnome-sudoku/src/lib/main.py         |  281 ++++++++++++++++++++------
 gnome-sudoku/src/lib/number_box.py   |  243 ++++++++++++++++++----
 gnome-sudoku/src/lib/saver.py        |   52 +++--
 gnome-sudoku/src/lib/tracker_info.py |  208 +++++++++++++++++++
 6 files changed, 1050 insertions(+), 369 deletions(-)
---
diff --git a/gnome-sudoku/data/tracker.ui b/gnome-sudoku/data/tracker.ui
index 2f6b876..61c738a 100644
--- a/gnome-sudoku/data/tracker.ui
+++ b/gnome-sudoku/data/tracker.ui
@@ -13,25 +13,11 @@
           <object class="GtkVBox" id="vbox1">
             <property name="visible">True</property>
             <child>
-              <object class="GtkLabel" id="label3">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Trackers</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">treeview1</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
               <object class="GtkAlignment" id="alignment3">
                 <property name="visible">True</property>
                 <property name="top_padding">12</property>
                 <property name="bottom_padding">12</property>
-                <property name="right_padding">12</property>
+                <property name="right_padding">0</property>
                 <child>
                   <object class="GtkVBox" id="vbox2">
                     <property name="visible">True</property>
@@ -58,7 +44,7 @@
                 </child>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="position">0</property>
               </packing>
             </child>
             <child>
@@ -67,109 +53,222 @@
                 <property name="xalign">0</property>
                 <property name="xscale">0</property>
                 <child>
-                  <object class="GtkVButtonBox" id="hbuttonbox1">
+                  <object class="GtkHBox" id="bhbox1">
                     <property name="visible">True</property>
-                    <property name="spacing">6</property>
-                    <property name="layout_style">end</property>
+                    <property name="spacing">2</property>
                     <child>
-                      <object class="GtkButton" id="AddTrackerButton">
+                      <object class="GtkVButtonBox" id="hbuttonbox1">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="can_default">True</property>
-                        <property name="receives_default">False</property>
+                        <property name="spacing">6</property>
+                        <property name="layout_style">end</property>
                         <child>
-                          <object class="GtkAlignment" id="alignment6">
+                          <object class="GtkButton" id="AddTrackerButton">
                             <property name="visible">True</property>
-                            <property name="xscale">0</property>
-                            <property name="yscale">0</property>
+                            <property name="can_focus">True</property>
+                            <property name="can_default">True</property>
+                            <property name="receives_default">False</property>
                             <child>
-                              <object class="GtkHBox" id="hbox4">
+                              <object class="GtkAlignment" id="alignment6">
                                 <property name="visible">True</property>
-                                <property name="spacing">2</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
                                 <child>
-                                  <object class="GtkImage" id="image4">
+                                  <object class="GtkHBox" id="hbox4">
                                     <property name="visible">True</property>
-                                    <property name="stock">gtk-add</property>
-                                    <property name="icon-size">4</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <object class="GtkImage" id="image4">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-add</property>
+                                        <property name="icon-size">4</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label6">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">_Add</property>
+                                        <property name="use_underline">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
                                   </object>
-                                  <packing>
-                                    <property name="expand">False</property>
-                                    <property name="fill">False</property>
-                                    <property name="position">0</property>
-                                  </packing>
                                 </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="ApplyTrackerButton">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="can_default">True</property>
+                            <property name="receives_default">False</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment8">
+                                <property name="visible">True</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
                                 <child>
-                                  <object class="GtkLabel" id="label6">
+                                  <object class="GtkHBox" id="hbox5">
                                     <property name="visible">True</property>
-                                    <property name="label" translatable="yes">_Add Tracker</property>
-                                    <property name="use_underline">True</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <object class="GtkImage" id="image5">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-apply</property>
+                                        <property name="icon-size">4</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label7">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">A_pply</property>
+                                        <property name="use_underline">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
                                   </object>
-                                  <packing>
-                                    <property name="expand">False</property>
-                                    <property name="fill">False</property>
-                                    <property name="position">1</property>
-                                  </packing>
                                 </child>
                               </object>
                             </child>
                           </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
                     <child>
-                      <object class="GtkButton" id="ClearTrackerButton">
+                      <object class="GtkVButtonBox" id="hbuttonbox2">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="can_default">True</property>
-                        <property name="receives_default">False</property>
+                        <property name="spacing">6</property>
+                        <property name="layout_style">end</property>
                         <child>
-                          <object class="GtkAlignment" id="alignment5">
+                          <object class="GtkButton" id="RemoveTrackerButton">
                             <property name="visible">True</property>
-                            <property name="xscale">0</property>
-                            <property name="yscale">0</property>
+                            <property name="can_focus">True</property>
+                            <property name="can_default">True</property>
+                            <property name="receives_default">False</property>
                             <child>
-                              <object class="GtkHBox" id="hbox3">
+                              <object class="GtkAlignment" id="alignment5">
                                 <property name="visible">True</property>
-                                <property name="spacing">2</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
                                 <child>
-                                  <object class="GtkImage" id="image3">
+                                  <object class="GtkHBox" id="hbox3">
                                     <property name="visible">True</property>
-                                    <property name="stock">gtk-clear</property>
-                                    <property name="icon-size">4</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <object class="GtkImage" id="image3">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-delete</property>
+                                        <property name="icon-size">4</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label5">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">_Remove</property>
+                                        <property name="use_underline">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
                                   </object>
-                                  <packing>
-                                    <property name="expand">False</property>
-                                    <property name="fill">False</property>
-                                    <property name="position">0</property>
-                                  </packing>
                                 </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="HideTrackerButton">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="can_default">True</property>
+                            <property name="receives_default">False</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment4">
+                                <property name="visible">True</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
                                 <child>
-                                  <object class="GtkLabel" id="label5">
+                                  <object class="GtkHBox" id="hbox2">
                                     <property name="visible">True</property>
-                                    <property name="label" translatable="yes">_Clear Tracker</property>
-                                    <property name="use_underline">True</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <object class="GtkImage" id="image2">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-close</property>
+                                        <property name="icon-size">4</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label4">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">H_ide</property>
+                                        <property name="use_underline">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
                                   </object>
-                                  <packing>
-                                    <property name="expand">False</property>
-                                    <property name="fill">False</property>
-                                    <property name="position">1</property>
-                                  </packing>
                                 </child>
                               </object>
                             </child>
                           </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
                   </object>
                 </child>
@@ -177,7 +276,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">2</property>
+                <property name="position">1</property>
               </packing>
             </child>
           </object>
diff --git a/gnome-sudoku/src/lib/gsudoku.py b/gnome-sudoku/src/lib/gsudoku.py
index d9a5bb0..ba8f1ec 100644
--- a/gnome-sudoku/src/lib/gsudoku.py
+++ b/gnome-sudoku/src/lib/gsudoku.py
@@ -2,24 +2,10 @@
 import gtk, gobject
 import colors
 import math
-import random
 from simple_debug import simple_debug
 import sudoku
 import number_box
-
-TRACKER_COLORS = [
-    # Use tango colors recommended here:
-    # http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines
-    tuple([x / 255.0 for x in cols]) for cols in
-    [(32, 74, 135), # Sky Blue 3
-     (78, 154, 6), # Chameleon 3
-     (206, 92, 0), # Orange 3
-     (143, 89, 2), # Chocolate 3
-     (92, 53, 102), # Plum 3
-     (85, 87, 83), # Aluminium 5
-     (196, 160, 0), # Butter 3
-     ]
-    ]
+import tracker_info
 
 def gtkcolor_to_rgb (color):
     return (color.red   / float(2**16),
@@ -100,13 +86,13 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         self.impossible_hints = 0
         self.impossibilities = []
         self.trackers = {}
-        self.__trackers_tracking__ = {}
+        self.tinfo = tracker_info.TrackerInfo()
         gobject.GObject.__init__(self)
         SudokuNumberGrid.__init__(self, group_size = group_size)
         self.setup_grid(grid, group_size)
         for e in self.__entries__.values():
             e.show()
-            e.connect('undo-change', self.entry_callback)
+            e.connect('undo-change', self.entry_callback, 'undo-change')
             e.connect('changed', self.entry_callback)
             e.connect('focus-in-event', self.focus_callback)
             e.connect('key-press-event', self.key_press_cb)
@@ -219,68 +205,127 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             ''.join([str(v) for v in vals])
             txt = ''.join([str(v) for v in vals])
             if txt != entry.get_text():
-                set_method(bottom_text = txt)
+                set_method(bottom_text = txt, for_hint = True)
                 self.hints += 1
         elif not entry.get_text():
             if entry.get_text() != 'X':
                 self.hints += 1
-                set_method(bottom_text = 'X')
+                set_method(bottom_text = 'X', for_hint = True)
         else:
-            set_method(bottom_text = "")
+            set_method(bottom_text = "", for_hint = True)
 
     @simple_debug
     def reset_grid (self):
-        """Reset grid to its original setup.
+        '''Remove all untracked values from the grid
 
-        Return a list of items we removed so that callers can handle
-        e.g. Undo properly"""
+        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):
-                    val = self.__entries__[(x, y)].get_value() # get the value from the user-visible grid,
-                    if val:
-                        removed.append((x, y, val, self.trackers_for_point(x, y, val)))
-                        self.remove(x, y, do_removal = True)
+                    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 clear_notes (self, side = 'Both'):
+    def clear_notes (self, side = 'Both', tracker = None):
         '''Remove notes
 
         The list of notes removed by this function are returned in a list.
+        The notes are returned in the format (x, y, (side, pos, tid, note)) where:
+        x and y are the cell's coordinates
+        side is either 'Top' or 'Bottom'
+        pos is the index of the note within the notelist
+        tid is the tracker id for the note
+        note is the value of the note
+
         The side argument determines what notes get cleared as well as what
         notes get returned.
-        'Both' - Clears both the top and bottom notes
+        'Both' - Clears both the top and bottom notes(default)
         'Top' - Clear only the top notes
         'Bottom' - Clear only the bottom notes
+        'AutoHint' - Clear all bottom notes for all trackers
+        'All' - Reset all notes
+
+        For 'Top', 'Bottom', and 'Both', the tracker argument can be supplied
+        to clear for a specific tracker.  Set tracker to None(default) to
+        operate on just what is currently displayed.
         '''
-        # Set the argument list for NumberBox.set_note_text()
-        if side == 'Both':
-            clear_args = {'top_text':'', 'bottom_text':''}
-        elif side == 'Top':
-            clear_args = {'top_text':''}
-        else:
-            clear_args = {'bottom_text':''}
         # Storage for removed notes
         removed = []
         for x in range(self.group_size):
             for y in range(self.group_size):
                 e = self.__entries__[(x, y)]
-                top, bottom = e.get_note_text()
-                # Don't return the bottom notes if we're only clearing the top
-                # or the top notes if we're only clearing the bottom.
-                if side == 'Top':
-                    bottom = ''
-                elif side == 'Bottom':
-                    top = ''
-                if top or bottom:
-                    removed.append((x, y, (top, bottom)))
-                    e.set_note_text(**clear_args)
-                    e.queue_draw()
+                if side in ['Top', 'Both']:
+                    if tracker == None:
+                        top_display_list = e.get_note_display(e.top_note_list)[0]
+                    else:
+                        top_display_list = e.get_note_display(e.top_note_list, tracker, False)[0]
+                    for offset, (notelist_index, tracker_id, note) in enumerate(top_display_list):
+                        removed.append((x, y, ('Top', notelist_index, tracker_id, note)))
+                        del e.top_note_list[notelist_index - offset]
+                if side in ['Bottom', 'Both']:
+                    if tracker == None:
+                        bottom_display_list = e.get_note_display(e.bottom_note_list)[0]
+                    else:
+                        bottom_display_list = e.get_note_display(e.bottom_note_list, tracker, False)[0]
+                    for offset, (notelist_index, tracker_id, note) in enumerate(bottom_display_list):
+                        removed.append((x, y, ('Bottom', notelist_index, tracker_id, note)))
+                        del e.bottom_note_list[notelist_index - offset]
+                if side == 'All':
+                    for notelist_index, (tracker_id, note) in enumerate(e.top_note_list):
+                        removed.append((x, y, ('Top', notelist_index, tracker_id, note)))
+                    e.top_note_list = []
+                if side in ['All', 'AutoHint']:
+                    for notelist_index, (tracker_id, note) in enumerate(e.bottom_note_list):
+                        removed.append((x, y, ('Bottom', notelist_index, tracker_id, note)))
+                    e.bottom_note_list = []
+        # Redraw the notes
+        self.update_all_notes()
         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()
+
     @simple_debug
     def blank_grid (self):
+        '''Wipe out everything on the grid.
+
+        This blanks all values, notes, tracked values, virgin values.  You end
+        up with a blank grid ready for a new puzzle.
+        '''
         for x in range(self.group_size):
             for y in range(self.group_size):
                 e = self.__entries__[(x, y)]
@@ -291,14 +336,13 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             self.__entries__[imp_cell].set_text('')
         self.impossibilities = []
         self.grid = None
-        self.clear_notes()
+        self.clear_notes('All')
+        self.tinfo.reset()
 
     @simple_debug
     def change_grid (self, grid, group_size):
         self.hints = 0
         self.impossible_hints = 0
-        self.trackers = {}
-        self.__trackers_tracking__ = {}
         self.blank_grid()
         self.setup_grid(grid, group_size)
 
@@ -342,11 +386,12 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
     @simple_debug
     def entry_callback (self, widget, *args):
         if not widget.get_text():
-            if self.grid and self.grid._get_(widget.x, widget.y):
-                self.grid.remove(widget.x, widget.y)
-            self.remove(widget.x, widget.y)
+            self.remove(widget.x, widget.y, *args)
+            # Trackers need to be redisplayed on an undo
+            if args and args[0] == 'undo-change':
+                self.show_track()
         else:
-            self.entry_validate(widget)
+            self.entry_validate(widget, *args)
 
     def update_all_hints (self):
         for x in range(self.group_size):
@@ -359,10 +404,26 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
                 else:
                     self.show_hint_for_entry(e)
 
+    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()
+
     @simple_debug
     def entry_validate (self, widget, *args):
         val = widget.get_value()
-        self.add_value(widget.x, widget.y, val)
+        if (args and args[0] == 'undo-change'):
+            # When undoing from one value to another - remove the errors from
+            # the previous value and add the new value to the proper tracker
+            self.remove_error_highlight()
+            self.add_value(widget.x, widget.y, val, widget.tracker_id)
+        else:
+            self.add_value(widget.x, widget.y, val)
         if self.grid.check_for_completeness():
             self.emit('puzzle-finished')
 
@@ -382,36 +443,45 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         for coord in self.grid.conflicts[(x, y)]:
             self.__entries__[coord].set_error_highlight(True)
 
-    @simple_debug
-    def add_value (self, x, y, val, trackers = []):
-        """Add value val at position x, y.
-
-        If tracker is True, we track it with tracker ID tracker.
+    def set_value(self, x, y, val):
+        '''Sets value for position x, y to val.
 
-        Otherwise, we use any currently tracking trackers to track our addition.
+        Calls set_text_interactive so the history list is updated.
+        '''
+        self.__entries__[(x, y)].set_text_interactive(str(val))
 
-        Providing the tracker arg is mostly useful for e.g. undo/redo
-        or removed items.
+    @simple_debug
+    def add_value (self, x, y, val, tracker = None):
+        """Add value val at position x, y.
 
-        To specify NO trackers, use trackers = [-1]
+        If tracker is set, we track the value with it.  Otherwise,
+        the current tracker is used(default).
         """
-        # Add the value to the UI to display
-        self.__entries__[(x, y)].set_value(val)
-        if self.doing_initial_setup:
-            self.__entries__[(x, y)].set_read_only(True)
-        # Handle any trackers.
-        if trackers:
-            # Explicitly specified tracker
-            for tracker in trackers:
-                if tracker == -1:
-                    pass
-                self.__entries__[(x, y)].set_color(self.get_tracker_color(tracker))
-                self.trackers[tracker].append((x, y, val))
-        elif True in self.__trackers_tracking__.values():
-            for k, v in self.__trackers_tracking__.items():
-                if v:
-                    self.__entries__[(x, y)].set_color(self.get_tracker_color(k))
-                    self.trackers[k].append((x, y, val))
+        # If the cell already has a value - remove it first.
+        e = self.__entries__[(x, y)]
+        if e.get_value():
+            self.remove(x, y)
+        # Explicitly specified tracker
+        if tracker:
+            # Only add it to the display when it's tracker is visible
+            if tracker == tracker_info.NO_TRACKER or tracker == self.tinfo.showing_tracker:
+                self.__entries__[(x, y)].set_value(val, tracker)
+            # If the tracker isn't showing at the moment - add it as a trace
+            if tracker != tracker_info.NO_TRACKER:
+                self.tinfo.add_trace(x, y, val, tracker)
+        else:
+            # Add a trace(tracked value) if a tracker is selected
+            if self.tinfo.current_tracker != tracker_info.NO_TRACKER:
+                self.tinfo.add_trace(x, y, val)
+            # Remove all tracked values(all traces) for the cell if the player
+            # adds an untracked value
+            else:
+                self.tinfo.remove_trace(x, y)
+            self.__entries__[(x, y)].set_value(val, self.tinfo.current_tracker)
+            if self.doing_initial_setup:
+                self.__entries__[(x, y)].set_read_only(True)
+            else:
+                self.__entries__[(x, y)].recolor(self.tinfo.current_tracker)
         # Add it to the underlying grid
         self.grid.add(x, y, val, True)
         # Highlight any conflicts that the new value creates
@@ -425,27 +495,23 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             self.mark_impossible_implications(x, y)
 
     @simple_debug
-    def remove (self, x, y, do_removal = False):
+    def remove (self, x, y, *args):
         """Remove x, y from our visible grid.
 
-        If do_removal, remove it from our underlying grid as well.
+        *args is passed from the undo mechanism
         """
         e = self.__entries__[(x, y)]
         # Always call the grid's remove() for proper conflict resolution
         if self.grid:
             self.grid.remove(x, y)
             self.remove_error_highlight()
-        # remove trackers
-        for t in self.trackers_for_point(x, y):
-            remove = []
-            for crumb in self.trackers[t]:
-                if crumb[0] == x and crumb[1] == y:
-                    remove.append(crumb)
-            for r in remove:
-                self.trackers[t].remove(r)
-        if e.get_text():
-            e.set_value(0)
-        e.unset_color()
+        # Remove it from the tracker.  When removing via undo, the trace
+        # manipulation is handled at a higher level
+        if not args or args[0] != 'undo-change':
+            if e.tracker_id != tracker_info.NO_TRACKER:
+                self.tinfo.remove_trace(x, y, e.tracker_id)
+        # Reset the value and tracker id
+        e.set_value(0, tracker_info.NO_TRACKER)
         # Update all hints if we need to
         if self.grid and self.always_show_hints and not self.doing_initial_setup:
             self.update_all_hints()
@@ -573,88 +639,64 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if grid_modified and self.always_show_hints:
             self.update_all_hints()
 
-    @simple_debug
-    def create_tracker (self, identifier = 0):
-        if not identifier:
-            identifier = 0
-        while self.trackers.has_key(identifier):
-            identifier += 1
-        self.trackers[identifier] = []
-        return identifier
-
-    def trackers_for_point (self, x, y, val = None):
-        if val:
-            # if we have a value we can do this a simpler way...
-            track_for_point = filter(
-                lambda t: (x, y, val) in t[1],
-                self.trackers.items()
-                )
-        else:
-            track_for_point = filter(
-                lambda tkr: True in [t[0] == x and t[1] == y for t in tkr[1]],
-                self.trackers.items())
-        return [t[0] for t in track_for_point]
-
-    def get_tracker_color (self, identifier):
-        if len(TRACKER_COLORS)>identifier:
-            return TRACKER_COLORS[identifier]
-        else:
-            random_color = TRACKER_COLORS[0]
-            while random_color in TRACKER_COLORS:
-                # If we have generated all possible colors, this will
-                # enter an infinite loop
-                random_color = (random.randint(0, 100)/100.0,
-                                random.randint(0, 100)/100.0,
-                                random.randint(0, 100)/100.0)
-            TRACKER_COLORS.append(random_color)
-            return self.get_tracker_color(identifier)
-
-    @simple_debug
-    def toggle_tracker (self, identifier, value):
-        """Toggle tracking for tracker identified by identifier."""
-        self.__trackers_tracking__[identifier] = value
+    def delete_by_tracker (self):
+        '''Delete all cells tracked by the current tracker
 
-    def delete_by_tracker (self, identifier):
-        """Delete all cells tracked by tracker ID identifer."""
+        The values are deleted from the tracker as well as the visible grid.
+        '''
         ret = []
-        while self.trackers[identifier]:
-            x, y, v = self.trackers[identifier][0]
-            ret.append((x, y, v, self.trackers_for_point(x, y, v)))
+        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 delete_except_for_tracker (self, identifier):
-        tracks = self.trackers[identifier]
-        removed = []
-        for x in range(self.group_size):
-            for y in range(self.group_size):
-                val = self.grid._get_(x, y)
-                if (val
-                    and (x, y, val) not in tracks
-                    and not self.grid.virgin._get_(x, y)
-                    ):
-                    removed.append((x, y, val, self.trackers_for_point(x, y, val)))
-                    self.remove(x, y)
-                    if self.grid and self.grid._get_(x, y):
-                        self.grid.remove(x, y)
+    def cover_track(self, hide = False):
+        '''Hide the current tracker
 
-        return removed
+        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 add_tracker (self, x, y, tracker, val = None):
-        self.__entries__[(x, y)].set_color(self.get_tracker_color(tracker))
-        # Highlight the conflicts when opening a saved game
-        if self.grid.conflicts.has_key((x, y)):
-            self.__entries__[(x, y)].set_error_highlight(True)
-        if not val:
-            val = self.grid._get_(x, y)
-        self.trackers[tracker].append((x, y, val))
-
-    def remove_tracker (self, x, y, tracker, val = None):
-        if not val:
-            val = self.grid._get_(x, y)
-        self.trackers[tracker].remove((x, y, val))
+    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()
 
 if __name__ == '__main__':
     window = gtk.Window()
diff --git a/gnome-sudoku/src/lib/main.py b/gnome-sudoku/src/lib/main.py
index bd89d89..1b19b97 100644
--- a/gnome-sudoku/src/lib/main.py
+++ b/gnome-sudoku/src/lib/main.py
@@ -10,6 +10,7 @@ import threading
 
 import gobject
 import gtk
+import pango
 from gettext import gettext as _
 from gettext import ngettext
 
@@ -20,6 +21,7 @@ import printing
 import saver
 import sudoku_maker
 import timer
+import tracker_info
 from defaults import (APPNAME, APPNAME_SHORT, AUTHORS, COPYRIGHT, DESCRIPTION, DOMAIN, 
         IMAGE_DIR, LICENSE, MIN_NEW_PUZZLES, UI_DIR, VERSION, WEBSITE, WEBSITE_LABEL)
 from gtk_goodies import gconf_wrapper, Undo, dialog_extras
@@ -292,14 +294,15 @@ class UI (gconf_wrapper.GConfWrapper):
         self.history = Undo.UndoHistoryList(undo_widg, redo_widg)
         for entry in self.gsd.__entries__.values():
             Undo.UndoableGenericWidget(entry, self.history,
-                                       set_method = 'set_value_from_undo',
+                                       set_method = 'set_value_for_undo',
+                                       get_method = 'get_value_for_undo',
                                        pre_change_signal = 'value-about-to-change'
                                        )
             Undo.UndoableGenericWidget(entry, self.history,
-                                       set_method = 'set_notes',
-                                       get_method = 'get_note_text',
+                                       set_method = 'set_notes_for_undo',
+                                       get_method = 'get_notes_for_undo',
                                        signal = 'notes-changed',
-                                       pre_change_signal = 'value-about-to-change',
+                                       pre_change_signal = 'notes-about-to-change',
                                        )
 
     def setup_color (self):
@@ -454,6 +457,7 @@ class UI (gconf_wrapper.GConfWrapper):
         self.tracker_ui.reset()
         self.history.clear()
         self.won = False
+        self.old_tracker_view = None
 
     @simple_debug
     def resize_cb (self, widget, event):
@@ -513,13 +517,20 @@ class UI (gconf_wrapper.GConfWrapper):
         clearer.perform()
 
     def do_game_reset (self, *args):
+        self.gsd.cover_track()
+        self.cleared.append(self.tinfo.save())
         self.cleared.append(self.gsd.reset_grid())
+        self.cleared_notes.append((tracker_info.NO_TRACKER, self.gsd.clear_notes('All')))
+        self.tinfo.reset()
         self.stop_dancer()
-        self.do_clear_notes()
 
     def undo_game_reset (self, *args):
+        self.tracker_ui.select_tracker(tracker_info.NO_TRACKER)
         for entry in self.cleared.pop():
             self.gsd.add_value(*entry)
+        self.tinfo.load(self.cleared.pop())
+        self.tracker_ui.select_tracker(self.tinfo.current_tracker)
+        self.gsd.show_track()
         self.undo_clear_notes()
 
     def clear_top_notes_cb (self, *args):
@@ -538,20 +549,14 @@ class UI (gconf_wrapper.GConfWrapper):
             )
         clearer.perform()
 
-    def do_clear_notes(self, side = 'Both'):
+    def do_clear_notes(self, side):
         ''' Clear top, bottom, or all notes - in undoable fashion
 
-        The side argument is used to specify which notes
-        are to be cleared.
-        'Top' - just clear the top notes
-        'Bottom' - just clear the bottom notes
-        'Both' - clear all notes(argument default)
-
-        Store all of the cleared notes in the cleared_notes list so the undo
-        can pick up on them later.  The list items are in the format
-        (x, y, (top note, bottom note)).
+        The side argument is used to specify which notes are to be cleared.
+        'Top' - Clear only the top notes
+        'Bottom' - Clear only the bottom notes
         '''
-        self.cleared_notes.append(self.gsd.clear_notes(side))
+        self.cleared_notes.append((self.tinfo.current_tracker, self.gsd.clear_notes(side)))
         # Turn off auto-hint if the player clears the bottom notes
         if side == 'Bottom' and self.gconf['always_show_hints']:
             always_show_hint_wdgt = self.main_actions.get_action('AlwaysShowPossible')
@@ -563,19 +568,18 @@ class UI (gconf_wrapper.GConfWrapper):
     def undo_clear_notes(self):
         ''' Undo previously cleared notes
 
-        Clearing notes sets the cleared_notes list of tuples indicating the
-        notes that were cleared. They are in the format
-        (x, y, (top note ,bottom note))
+        Clearing notes fills the cleared_notes list of notes that were cleared.
         '''
-        for x, y, notes in self.cleared_notes.pop():
-            top, bottom = notes
-            if top:
-                self.gsd.__entries__[x, y].set_note_text(top_text = top)
-            if bottom:
-                self.gsd.__entries__[x, y].set_note_text(bottom_text = bottom)
+        cleared_tracker, cleared_notes = self.cleared_notes.pop()
+        # Change the tracker selection if it was tracking during the clear
+        if cleared_tracker != tracker_info.NO_TRACKER:
+            self.tracker_ui.select_tracker(cleared_tracker)
+        self.gsd.apply_notelist(cleared_notes)
         # Update the hints...in case we're undoing over top of them
         if self.gconf['always_show_hints']:
             self.gsd.update_all_hints()
+        # 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():
             self.start_dancer()
@@ -591,7 +595,7 @@ class UI (gconf_wrapper.GConfWrapper):
             self.gsd.update_all_hints()
         else:
             self.gsd.always_show_hints = False
-            self.gsd.clear_notes('Bottom')
+            self.gsd.clear_notes('AutoHint')
 
     @simple_debug
     def impossible_implication_cb (self, action):
@@ -602,17 +606,24 @@ class UI (gconf_wrapper.GConfWrapper):
 
     @simple_debug
     def setup_tracker_interface (self):
-        self.trackers = {}
         self.tracker_ui = TrackerBox(self)
         self.tracker_ui.show_all()
         self.tracker_ui.hide()
+        self.tinfo = tracker_info.TrackerInfo()
+        self.old_tracker_view = None
         self.game_box.add(self.tracker_ui)
 
     @simple_debug
     def tracker_toggle_cb (self, widg):
         if widg.get_active():
+            if self.old_tracker_view:
+                self.tinfo.set_tracker_view(self.old_tracker_view)
+                self.tracker_ui.select_tracker(self.tinfo.current_tracker)
+                self.gsd.show_track()
             self.tracker_ui.show_all()
         else:
+            self.old_tracker_view = self.tinfo.get_tracker_view()
+            self.tracker_ui.hide_tracker_cb(None)
             self.tracker_ui.hide()
 
     @simple_debug
@@ -708,6 +719,8 @@ class TrackerBox (gtk.VBox):
         self.builder.set_translation_domain(DOMAIN)
         self.builder.add_from_file(os.path.join(UI_DIR, 'tracker.ui'))
         self.main_ui = main_ui
+        self.tinfo = tracker_info.TrackerInfo()
+        self.tinfo.ui = self
         self.vb = self.builder.get_object('vbox1')
         self.vb.unparent()
         self.pack_start(self.vb, expand = True, fill = True)
@@ -721,45 +734,77 @@ class TrackerBox (gtk.VBox):
         for tree in self.tracker_model:
             if tree[0] > -1:
                 self.tracker_model.remove(tree.iter)
+        self.tinfo.reset()
+        self.tracker_actions.set_sensitive(False)
 
     @simple_debug
     def setup_tree (self):
         self.tracker_tree = self.builder.get_object('treeview1')
         self.tracker_model = gtk.ListStore(int, gtk.gdk.Pixbuf, str)
+        self.tracker_model.set_sort_column_id(0, gtk.SORT_ASCENDING)
         self.tracker_tree.set_model(self.tracker_model)
         col1 = gtk.TreeViewColumn("", gtk.CellRendererPixbuf(), pixbuf = 1)
-        col2 = gtk.TreeViewColumn("", gtk.CellRendererText(), text = 2)
+        rend = gtk.CellRendererText()
+        col2 = gtk.TreeViewColumn("", rend, text = 2)
+        col2.set_cell_data_func(rend, self.draw_tracker_name)
         self.tracker_tree.append_column(col2)
         self.tracker_tree.append_column(col1)
         # Our initial row...
-        self.tracker_model.append([-1, None, _('No Tracker')])
+        self.tracker_model.append([-1, None, _('Untracked')])
         self.tracker_tree.get_selection().connect('changed', self.selection_changed_cb)
 
     @simple_debug
     def setup_actions (self):
         self.tracker_actions = gtk.ActionGroup('tracker_actions')
         self.tracker_actions.add_actions(
-            [('Clear',
+            [('Remove',
               gtk.STOCK_CLEAR,
-              _('_Clear Tracker'),
-              None, _('Clear all moves tracked by selected tracker.'),
-              self.clear_cb
+              _('_Remove'),
+              None, _('Delete selected tracker.'),
+              self.remove_tracker_cb
+              ),
+             ('Hide',
+              gtk.STOCK_CLEAR,
+              _('H_ide'),
+              None, _('Hide current tracker entries.'),
+              self.hide_tracker_cb
+              ),
+             ('Apply',
+              gtk.STOCK_CLEAR,
+              _('A_pply'),
+              None, _('Apply all tracked values and remove the tracker.'),
+              self.apply_tracker_cb
               ),
              ]
             )
-        a = self.tracker_actions.get_action('Clear')
-        a.connect_proxy(self.builder.get_object('ClearTrackerButton'))
+        a = self.tracker_actions.get_action('Remove')
+        a.connect_proxy(self.builder.get_object('RemoveTrackerButton'))
+        a = self.tracker_actions.get_action('Hide')
+        a.connect_proxy(self.builder.get_object('HideTrackerButton'))
+        a = self.tracker_actions.get_action('Apply')
+        a.connect_proxy(self.builder.get_object('ApplyTrackerButton'))
         self.builder.get_object('AddTrackerButton').connect('clicked',
                                                           self.add_tracker)
         # Default to insensitive (they only become sensitive once a tracker is added)
         self.tracker_actions.set_sensitive(False)
 
+    def draw_tracker_name(self, column, cell, model, iter):
+        if model.get_value(iter, 0) == self.tinfo.showing_tracker and \
+            self.tinfo.showing_tracker != tracker_info.NO_TRACKER and \
+            self.tinfo.showing_tracker !=  self.tinfo.current_tracker:
+            cell.set_property('underline', pango.UNDERLINE_DOUBLE)
+        else:
+            cell.set_property('underline', pango.UNDERLINE_NONE)
+
     @simple_debug
-    def add_tracker (self, *args):
-        tracker_id = self.main_ui.gsd.create_tracker()
+    def add_tracker (self, *args, **keys):
+        if keys and keys.has_key('tracker_id'):
+            tracker_id = self.tinfo.create_tracker(keys['tracker_id'])
+        else:
+            tracker_id = self.tinfo.create_tracker()
         pixbuf = self.pixbuf_transform_color(
             STOCK_PIXBUFS['tracks'],
-            self.main_ui.gsd.get_tracker_color(tracker_id),
+            self.tinfo.get_color(tracker_id)
             )
         # select our new tracker
         self.tracker_tree.get_selection().select_iter(
@@ -768,6 +813,7 @@ class TrackerBox (gtk.VBox):
                                   _("Tracker %s") % (tracker_id + 1)]
                                   )
             )
+        self.tinfo.set_tracker(tracker_id)
 
     @simple_debug
     def pixbuf_transform_color (self, pixbuf, color):
@@ -785,10 +831,28 @@ class TrackerBox (gtk.VBox):
                                             pixbuf.get_width(), pixbuf.get_height(), pixbuf.get_rowstride())
 
     @simple_debug
-    def select_tracker (self, tracker_id):
+    def find_tracker (self, tracker_id):
         for row in self.tracker_model:
             if row[0] == tracker_id:
-                self.tracker_tree.get_selection().select_iter(row.iter)
+                return row
+        return None
+
+    @simple_debug
+    def select_tracker (self, tracker_id):
+        track_row = self.find_tracker(tracker_id)
+        if track_row:
+            self.tracker_tree.get_selection().select_iter(track_row.iter)
+            self.tinfo.set_tracker(tracker_id)
+
+    def redraw_row(self, tracker_id):
+        track_row = self.find_tracker(tracker_id)
+        if track_row:
+            self.tracker_model.row_changed(self.tracker_model.get_path(track_row.iter), track_row.iter)
+
+    def set_tracker_action_sense(self, enabled):
+        self.tracker_actions.set_sensitive(True)
+        for action in self.tracker_actions.list_actions():
+            action.set_sensitive(self.tinfo.showing_tracker != tracker_info.NO_TRACKER)
 
     @simple_debug
     def selection_changed_cb (self, selection):
@@ -796,32 +860,129 @@ class TrackerBox (gtk.VBox):
         if itr:
             selected_tracker_id = mod.get_value(itr, 0)
         else:
-            selected_tracker_id = -1
-        # This should be cheap since we don't expect many trackers...
-        # We cycle through each row and toggle it off if it's not
-        # selected; on if it is selected
-        for row in self.tracker_model:
-            tid = row[0]
-            if tid != -1: # -1 == no tracker
-                self.main_ui.gsd.toggle_tracker(tid, tid == selected_tracker_id)
-        self.tracker_actions.set_sensitive(selected_tracker_id != -1)
-
-    @simple_debug
-    def clear_cb (self, action):
+            selected_tracker_id = tracker_info.NO_TRACKER
+        if selected_tracker_id != tracker_info.NO_TRACKER:
+            self.main_ui.gsd.cover_track()
+        # Remove the underline on the showing_tracker
+        self.redraw_row(self.tinfo.showing_tracker)
+        self.tinfo.set_tracker(selected_tracker_id)
+        self.set_tracker_action_sense(self.tinfo.showing_tracker != tracker_info.NO_TRACKER)
+        # Show the tracker
+        if selected_tracker_id != tracker_info.NO_TRACKER:
+            self.main_ui.gsd.show_track()
+        self.main_ui.gsd.update_all_notes()
+        if self.main_ui.gconf['always_show_hints']:
+            self.main_ui.gsd.update_all_hints()
+
+    @simple_debug
+    def remove_tracker_cb (self, action):
         mod, itr = self.tracker_tree.get_selection().get_selected()
         # This should only be called if there is an itr, but we'll
         # double-check just in case.
         if itr:
-            selected_tracker_id = mod.get_value(itr, 0)
-            self.tracker_delete_tracks(selected_tracker_id)
+            clearer = Undo.UndoableObject(
+                self.do_delete_tracker,
+                self.undo_delete_tracker,
+                self.main_ui.history
+                )
+            clearer.perform()
 
     @simple_debug
-    def tracker_delete_tracks (self, tracker_id):
-        clearer = Undo.UndoableObject(
-            lambda *args: self.main_ui.cleared.append(self.main_ui.gsd.delete_by_tracker(tracker_id)),
-            lambda *args: [self.main_ui.gsd.add_value(*entry) for entry in self.main_ui.cleared.pop()],
-            self.main_ui.history)
-        clearer.perform()
+    def hide_tracker_cb (self, action):
+        hiding_tracker = self.tinfo.showing_tracker
+        self.select_tracker(tracker_info.NO_TRACKER)
+        self.main_ui.gsd.cover_track(True)
+        self.main_ui.gsd.update_all_notes()
+        self.set_tracker_action_sense(False)
+        self.redraw_row(hiding_tracker)
+
+    @simple_debug
+    def apply_tracker_cb (self, action):
+        '''Apply Tracker button action
+        '''
+        # Shouldn't be here if no tracker is showing
+        if self.tinfo.showing_tracker == tracker_info.NO_TRACKER:
+            return
+        # Apply the tracker in undo-able fashion
+        applyer = Undo.UndoableObject(
+            self.do_apply_tracker,
+            self.undo_apply_tracker,
+            self.main_ui.history
+            )
+        applyer.perform()
+
+    def do_apply_tracker(self):
+        '''Apply the showing tracker to untracked
+
+        All of the values and notes will be transferred to untracked and
+        the tracker is deleted.
+        '''
+        track_row = self.find_tracker(self.tinfo.showing_tracker)
+        if not track_row:
+            return
+        # Delete the tracker
+        cleared_values, cleared_notes = self.do_delete_tracker(True)
+        # Apply the values
+        for x, y, val, tid in cleared_values:
+            self.main_ui.gsd.set_value(x, y, val)
+        # Then apply the notes
+        self.main_ui.gsd.apply_notelist(cleared_notes, True)
+        # Store the undo counts
+        self.main_ui.cleared.append(len(cleared_values))
+        self.main_ui.cleared_notes.append(len(cleared_notes))
+
+    def undo_apply_tracker(self):
+        '''Undo a previous tracker apply
+
+        The number of cleared values and notes are stored during the apply.
+        The undo is called for each of them, then the tracker delete is
+        undone.
+        '''
+        # Undo all of the applied values and notes
+        value_count = self.main_ui.cleared.pop()
+        note_count = self.main_ui.cleared_notes.pop()
+        count = 0
+        while count < (value_count + note_count):
+            self.main_ui.history.undo()
+            count += 1
+        # Undo the tracker delete
+        self.undo_delete_tracker()
+
+    def do_delete_tracker(self, for_apply = False):
+        '''Delete the current tracker
+        '''
+        track_row = self.find_tracker(self.tinfo.showing_tracker)
+        if not track_row:
+            return
+        ui_row = [track_row[0], track_row[1], track_row[2]]
+        # For the values, store it like (tracker_id, list_of_cleared_values)
+        cleared_values = self.main_ui.gsd.delete_by_tracker()
+        self.main_ui.cleared.append((self.tinfo.showing_tracker, ui_row, cleared_values))
+        # The notes already have tracker info in them, so just store the list
+        cleared_notes = self.main_ui.gsd.clear_notes(tracker = self.tinfo.showing_tracker)
+        self.main_ui.cleared_notes.append(cleared_notes)
+        # Delete it from tracker_info
+        self.hide_tracker_cb(None)
+        self.tracker_model.remove(track_row.iter)
+        self.tinfo.delete_tracker(ui_row[0])
+        # Return all of the data for "Apply Tracker" button
+        if for_apply:
+            return (cleared_values, cleared_notes)
+
+    def undo_delete_tracker(self):
+        '''Undo a tracker delete
+        '''
+        # Values are stored like (tracker_id, list_of_cleared_values)
+        tracker_id, ui_row, cleared_values = self.main_ui.cleared.pop()
+        # Recreate it in tracker_info
+        self.tinfo.create_tracker(tracker_id)
+        # Add it to the tree
+        self.tracker_tree.get_selection().select_iter(self.tracker_model.append(ui_row))
+        # Add all the values
+        for value in cleared_values:
+            self.main_ui.gsd.add_value(*value)
+        # The notes already have tracker info in them, so just store the list
+        self.main_ui.gsd.apply_notelist(self.main_ui.cleared_notes.pop())
 
 def start_game ():
     if options.debug:
diff --git a/gnome-sudoku/src/lib/number_box.py b/gnome-sudoku/src/lib/number_box.py
index 1e5db11..dca23e6 100644
--- a/gnome-sudoku/src/lib/number_box.py
+++ b/gnome-sudoku/src/lib/number_box.py
@@ -3,7 +3,7 @@
 
 import gtk, gobject, pango, cairo
 import math
-import timer
+import tracker_info
 from gettext import gettext as _
 
 ERROR_HIGHLIGHT_COLOR = (1.0, 0, 0)
@@ -79,10 +79,12 @@ class NumberBox (gtk.Widget):
     _bottom_note_layout = None
     text_color = None
     highlight_color = None
+    shadow_color = None
     custom_background_color = None
 
     __gsignals__ = {
         'value-about-to-change':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        'notes-about-to-change':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         'changed':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         # undo-change - A hacky way to handle the fact that we want to
         # respond to undo's changes but we don't want undo to respond
@@ -104,6 +106,13 @@ class NumberBox (gtk.Widget):
         self.font.set_size(BASE_FONT_SIZE)
         self.note_font = self.font.copy()
         self.note_font.set_size(NOTE_FONT_SIZE)
+        self._top_note_layout = pango.Layout(self.create_pango_context())
+        self._top_note_layout.set_font_description(self.note_font)
+        self._bottom_note_layout = pango.Layout(self.create_pango_context())
+        self._bottom_note_layout.set_font_description(self.note_font)
+        self.top_note_list = []
+        self.bottom_note_list = []
+        self.tinfo = tracker_info.TrackerInfo()
         self.set_property('can-focus', True)
         self.set_property('events', gtk.gdk.ALL_EVENTS_MASK)
         self.connect('button-press-event', self.button_press_cb)
@@ -200,18 +209,12 @@ class NumberBox (gtk.Widget):
                 self.add_note_text(txt, top = True)
             elif e.state & gtk.gdk.MOD1_MASK:
                 self.remove_note_text(txt, top = True)
-            elif self.get_text() != txt:
-                # If there's no change, do nothing
-
-                # First do a removal event -- this is something of a
-                # kludge, but it works nicely with old code that was based
-                # on entries, which also behave this way (they generate 2
-                # events for replacing a number with a new number - a
-                # removal event and an addition event)
-
-                if self.get_text():
-                    self.set_text_interactive('')
-                # Then add
+            elif self.get_text() != txt or \
+                (self.tracker_id != tracker_info.NO_TRACKER and
+                 self.tinfo.current_tracker == tracker_info.NO_TRACKER):
+                # If there's no change, do nothing unless the player wants to
+                # change a tracked item while not tracking(ie commit a tracked
+                # change)
                 self.set_text_interactive(txt)
         elif txt in ['0', 'Delete', 'BackSpace']:
             self.set_text_interactive('')
@@ -300,7 +303,6 @@ class NumberBox (gtk.Widget):
 
     def number_changed_cb (self, num_selector):
         self.destroy_npicker()
-        self.set_text_interactive('')
         newval = num_selector.get_value()
         if newval:
             self.set_text_interactive(str(newval))
@@ -339,9 +341,8 @@ class NumberBox (gtk.Widget):
         if type(font) == str:
             font = pango.FontDescription(font)
         self.note_font = font
-        if self.top_note_text or self.bottom_note_text:
-            self.set_note_text(self.top_note_text,
-                               self.bottom_note_text)
+        self._top_note_layout.set_font_description(font)
+        self._bottom_note_layout.set_font_description(font)
         self.queue_draw()
 
     def set_text (self, text):
@@ -349,31 +350,159 @@ class NumberBox (gtk.Widget):
         self._layout = self.create_pango_layout(text)
         self._layout.set_font_description(self.font)
 
-    def set_notes (self, notes):
-        """Hackish method to allow easy use of Undo API.
-
-        Undo API requires a set method that is called with one
-        argument (the result of a get method)"""
-        self.set_note_text(top_text = notes[0],
-                           bottom_text = notes[1])
+    def show_note_text (self):
+        '''Display the notes for the current view
+        '''
+        self.top_note_text = self.get_note_display(self.top_note_list)[1]
+        self._top_note_layout.set_markup(self.get_note_display(self.top_note_list)[2])
+        self.bottom_note_text = self.get_note_display(self.bottom_note_list)[1]
+        self._bottom_note_layout.set_markup(self.get_note_display(self.bottom_note_list)[2])
         self.queue_draw()
 
-    def set_note_text (self, top_text = None, bottom_text = None):
+    def set_note_text (self, top_text = None, bottom_text = None, for_hint = False):
+        '''Change the notes
+        '''
         if top_text is not None:
-            self.top_note_text = top_text
-            self._top_note_layout = self.create_pango_layout(top_text)
-            self._top_note_layout.set_font_description(self.note_font)
+            self.update_notelist(self.top_note_list, top_text)
         if bottom_text is not None:
-            self.bottom_note_text = bottom_text
-            self._bottom_note_layout = self.create_pango_layout(bottom_text)
-            self._bottom_note_layout.set_font_description(self.note_font)
-        self.queue_draw()
+            self.update_notelist(self.bottom_note_list, bottom_text, for_hint)
+        self.show_note_text()
 
     def set_note_text_interactive (self, *args, **kwargs):
-        self.emit('value-about-to-change')
+        self.emit('notes-about-to-change')
         self.set_note_text(*args, **kwargs)
         self.emit('notes-changed')
 
+    def set_notelist(self, top_notelist, bottom_notelist):
+        '''Assign new note lists
+        '''
+        if top_notelist:
+            self.top_note_list = top_notelist
+        if bottom_notelist:
+            self.bottom_note_list = bottom_notelist
+
+    def get_note_display(self, notelist, tracker_id = None, include_untracked = True):
+        '''Parse a notelist for display
+
+        Parse a notelist for the display.
+        notelist - This method works on one notelist at a time, so
+            top_note_list or bottom_note_list must be passed in.
+        tracker_id - can specify a particular tracker.  The default is to use
+            tracker that is currently showing.
+        include_untracked - When set to True(default), the untracked notes will
+            be included in the output.  Set it to false to exclude untracked
+            notes.
+
+        The output is returned in 3 formats:
+        display_list - is tuple list in the format (notelist_index, tid, note)
+            notelist_index - the index within the notelist
+            tid - tracker id
+            note - value of the note
+        display_text - vanilla string representing all the values
+        markup_text - pango markup string that colors each note for its tracker
+        '''
+        display_list = []
+        display_text = ''
+        markup_text = ''
+        if tracker_id == None:
+            tracker_id = self.tinfo.showing_tracker
+        if include_untracked:
+            track_filter = [tracker_info.NO_TRACKER, tracker_id]
+        else:
+            track_filter = [tracker_id]
+        last_tracker = tracker_info.NO_TRACKER
+        for notelist_index, (tid, note) in enumerate(notelist[:]):
+            if tid not in track_filter:
+                continue
+            display_list.append((notelist_index, tid, note))
+            display_text += note
+            if tid != last_tracker:
+                if self.tinfo.get_color_markup(last_tracker):
+                    markup_text += '</span>'
+                if self.tinfo.get_color_markup(tid):
+                    markup_text += '<span foreground="' + str(self.tinfo.get_color_markup(tid)) + '">'
+                last_tracker = tid
+            markup_text += note
+        if self.tinfo.get_color_markup(last_tracker):
+            markup_text += '</span>'
+        return((display_list, display_text, markup_text))
+
+    def update_notelist(self, notelist, new_notes, for_hint = False):
+        '''Parse notes for a notelist
+
+        A notelist stores individual notes in the format (tracker, note).  The
+        sequence is also meaningful - it dictates the order in which the notes
+        are displayed.  One notelist is maintained for the top
+        notes(top_note_list), and one for the bottom(bottom_note_list).  This
+        method is responsible for maintaining those lists.
+
+        When updating for hints(for_hint == True), the old notes are replaced
+        completely by the new notes and set with NO_TRACKER.
+        '''
+        # Remove any duplicates
+        unique_notes = ""
+        for note in new_notes:
+            if note not in unique_notes:
+                unique_notes += note
+        # Create a list and text version of the notelist
+        display_list = self.get_note_display(notelist)[0]
+        display_text = self.get_note_display(notelist)[1]
+        if display_text == unique_notes:
+            return
+        # Remove deleted values from the notelist
+        del_offset = 0
+        for display_index, (notelist_index, tid, old_note) in enumerate(display_list[:]):
+            if old_note not in unique_notes or for_hint:
+                del notelist[notelist_index + del_offset]
+                del display_list[display_index + del_offset]
+                del_offset -= 1
+            else:
+                # Adjust the display_list index
+                display_list[display_index + del_offset] = (notelist_index + del_offset, tid, old_note)
+        # Insert any new values into the notelist
+        ins_offset = 0
+        display_index = 0
+        for new_index, new_note in enumerate(unique_notes):
+            add_note = False
+            # if the new notes are longer than the current ones - append
+            if len(display_list) <= display_index:
+                notelist_index = len(notelist)
+                ins_offset = 0
+                add_note = True
+            # Otherwise - advance until we find the appropriate place to insert
+            else:
+                old_note = display_list[display_index][2]
+                if new_note != old_note:
+                    notelist_index = display_list[display_index][0]
+                    add_note = True
+                display_index += 1
+            if add_note:
+                if for_hint:
+                    use_tracker = tracker_info.NO_TRACKER
+                else:
+                    use_tracker = self.tinfo.current_tracker
+                notelist.insert(notelist_index + ins_offset, (use_tracker, new_note))
+                display_list.insert(new_index, (notelist_index + ins_offset, self.tinfo.current_tracker, new_note))
+                ins_offset = ins_offset + 1
+        self.trim_untracked_notes(notelist)
+
+    def trim_untracked_notes(self, notelist):
+        untracked_text = self.get_note_display(notelist, tracker_info.NO_TRACKER)[1]
+        for tid, note in notelist[:]:
+            if note in untracked_text and tid != tracker_info.NO_TRACKER:
+                notelist.remove((tid, note))
+
+    def get_notes_for_undo(self):
+        '''Return the top and bottom notelists
+        '''
+        return((self.top_note_list[:], self.bottom_note_list[:]))
+
+    def set_notes_for_undo(self, notelists):
+        '''Reset the top and bottom notelists from an undo
+        '''
+        self.top_note_list, self.bottom_note_list = notelists
+        self.show_note_text()
+
     def do_realize (self):
         # The do_realize method is responsible for creating GDK (windowing system)
         # resources. In this example we will create a new gdk.Window which we
@@ -517,6 +646,14 @@ class NumberBox (gtk.Widget):
         cr.stroke()
 
     def draw_text (self, cr):
+        fontw, fonth = self._layout.get_pixel_size()
+        # Draw a shadow for tracked conflicts.  This is done to
+        # differentiate between tracked and untracked conflicts.
+        if self.shadow_color:
+            cr.set_source_rgb(*self.shadow_color)
+            for xoff, yoff in [(1,1),(2,2)]:
+                cr.move_to((BASE_SIZE/2)-(fontw/2) + xoff, (BASE_SIZE/2) - (fonth/2) + yoff)
+                cr.show_layout(self._layout)
         if self.text_color:
             cr.set_source_rgb(*self.text_color)
         elif self.read_only:
@@ -525,7 +662,6 @@ class NumberBox (gtk.Widget):
             cr.set_source_color(self.style.text[self.state])
         # And draw the text in the middle of the allocated space
         if self._layout:
-            fontw, fonth = self._layout.get_pixel_size()
             cr.move_to(
                 (BASE_SIZE/2)-(fontw/2),
                 (BASE_SIZE/2) - (fonth/2),
@@ -551,7 +687,8 @@ class NumberBox (gtk.Widget):
             cr.update_layout(self._bottom_note_layout)
             cr.show_layout(self._bottom_note_layout)
 
-    def set_text_color (self, color):
+    def set_text_color (self, color, shadow = None):
+        self.shadow_color = shadow
         self.text_color = color
         self.queue_draw()
 
@@ -565,10 +702,6 @@ class NumberBox (gtk.Widget):
     def show_notes (self):
         pass
 
-    def set_value_from_undo (self, v):
-        self.set_value(v)
-        self.emit('undo_change')
-
     def set_value (self, v):
         if 0 < v <= self.upper:
             self.set_text(str(v))
@@ -591,19 +724,41 @@ class NumberBox (gtk.Widget):
 class SudokuNumberBox (NumberBox):
 
     normal_color = None
+    tracker_id = None
     error_color = (1.0, 0, 0)
     highlight_color = ERROR_HIGHLIGHT_COLOR
 
-    def set_color (self, color):
-        self.normal_color = color
+    def set_value(self, val, tracker_id = None):
+        if tracker_id == None:
+            self.tracker_id = self.tinfo.current_tracker
+        else:
+            self.tracker_id = tracker_id
+        self.normal_color = self.tinfo.get_color(self.tracker_id)
         self.set_text_color(self.normal_color)
+        super(SudokuNumberBox, self).set_value(val)
+
+    def get_value_for_undo(self):
+        return(self.tracker_id, self.get_value(), self.tinfo.get_trackers_for_cell(self.x, self.y))
+
+    def set_value_for_undo (self, undo_val):
+        tracker_id, value, all_traces = undo_val
+        # When undo sets a value, switch to that tracker
+        if value:
+            self.tinfo.ui.select_tracker(tracker_id)
+        self.set_value(value, tracker_id)
+        self.tinfo.reset_trackers_for_cell(self.x, self.y, all_traces)
+        self.emit('undo_change')
 
-    def unset_color (self):
-        self.set_color(None)
+    def recolor(self, tracker_id):
+        self.normal_color = self.tinfo.get_color(tracker_id)
+        self.set_text_color(self.normal_color)
 
     def set_error_highlight (self, val):
         if val:
-            self.set_text_color(self.error_color)
+            if (self.tracker_id != tracker_info.NO_TRACKER):
+                self.set_text_color(self.error_color, self.normal_color)
+            else:
+                self.set_text_color(self.error_color)
         else:
             self.set_text_color(self.normal_color)
 
diff --git a/gnome-sudoku/src/lib/saver.py b/gnome-sudoku/src/lib/saver.py
index 06bd441..357bf81 100644
--- a/gnome-sudoku/src/lib/saver.py
+++ b/gnome-sudoku/src/lib/saver.py
@@ -3,6 +3,7 @@ import gtk, pickle, types, os, errno
 import defaults
 from gtk_goodies.dialog_extras import show_message
 from gettext import gettext as _
+import tracker_info
 
 SAVE_ATTRIBUTES = [('gsd.hints'),
                    ('gsd.impossible_hints'),
@@ -48,13 +49,11 @@ def jar_game (ui):
     jar = {} # what we will pickle
     ui.timer.mark_timing()
     jar['game'] = ui.gsd.grid.to_string()
-    jar['trackers'] = ui.gsd.trackers
-    jar['tracking'] = ui.gsd.__trackers_tracking__
-    jar['notes'] = []
+    jar['tracker_info'] = tracker_info.TrackerInfo().save()
+    jar['tracked_notes'] = []
     for e in ui.gsd.__entries__.values():
-        top, bot = e.get_note_text()
-        if top or bot:
-            jar['notes'].append((e.x, e.y, top, bot))
+        if e.top_note_list or e.bottom_note_list:
+            jar['tracked_notes'].append((e.x, e.y, e.top_note_list, e.bottom_note_list))
     for attr in SAVE_ATTRIBUTES:
         jar[attr] = super_getattr(ui, attr)
     return jar
@@ -64,22 +63,39 @@ def set_value_from_jar (dest, jar):
         super_setattr(dest, attr, jar[attr])
 
 def open_game (ui, jar):
+    tinfo = tracker_info.TrackerInfo()
+    tinfo.set_tracker(tracker_info.NO_TRACKER)
     ui.gsd.load_game(jar['game'])
-    # this is a bit easily breakable... we take advantage of the fact
-    # that we create tracker IDs sequentially and that {}.items()
-    # sorts by keys by default
-    for tracker, tracked in jar.get('trackers', {}).items():
-        # add 1 tracker per existing tracker...
-        ui.tracker_ui.add_tracker()
-        for x, y, val in tracked:
-            ui.gsd.add_tracker(x, y, tracker, val = val)
-    for tracker, tracking in jar.get('tracking', {}).items():
-        if tracking:
-            ui.tracker_ui.select_tracker(tracker)
-    set_value_from_jar(ui, jar)
+    # The 'notes' and 'trackers' sections are for transition from the old
+    # style tracker storage.  The tracker values and notes are stored in the
+    # 'tracked_notes' and 'tracker_info' sections now.
     if jar.has_key('notes') and jar['notes']:
         for x, y, top, bot in jar['notes']:
             ui.gsd.__entries__[(x, y)].set_note_text(top, bot)
+    if jar.has_key('trackers'):
+        for tracker, tracked in jar.get('trackers', {}).items():
+            # add 1 tracker per existing tracker...
+            ui.tracker_ui.add_tracker()
+            for x, y, val in tracked:
+                tinfo.add_trace(x, y, val)
+    set_value_from_jar(ui, jar)
+    if jar.has_key('tracking'):
+        for tracker, tracking in jar.get('tracking', {}).items():
+            if tracking:
+                ui.tracker_ui.select_tracker(tracker)
+    if jar.has_key('tracked_notes') and jar['tracked_notes']:
+        for x, y, top, bot in jar['tracked_notes']:
+            ui.gsd.__entries__[(x, y)].set_notelist(top, bot)
+    if jar.has_key('tracker_info'):
+        trackers = jar['tracker_info'][2]
+        for tracking in trackers.keys():
+            ui.tracker_ui.add_tracker(tracker_id = tracking)
+        tinfo.load(jar['tracker_info'])
+        ui.tracker_ui.select_tracker(tinfo.current_tracker)
+        if tinfo.showing_tracker != tracker_info.NO_TRACKER:
+            ui.gsd.show_track()
+    # Display the notes
+    ui.gsd.update_all_notes()
 
 def pickle_game (ui, target):
     close_me = False
diff --git a/gnome-sudoku/src/lib/tracker_info.py b/gnome-sudoku/src/lib/tracker_info.py
new file mode 100644
index 0000000..798c4ac
--- /dev/null
+++ b/gnome-sudoku/src/lib/tracker_info.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+#!/usr/bin/python
+
+import random
+import copy
+
+NO_TRACKER = -1 # Tracker id for untracked values
+
+class TrackerInfo(object):
+    '''Tracker state machine(singleton)
+
+    The singleton instance of this class is used to manipulate tracker
+    selection and tracked values, as well as interrogate tracker colors.
+
+    _tracks - dictionary for tracked values.  The tracker id is used as the
+        key.  A tracker is a dictionary that stored tracked values keyed by
+        its coordinates(x, y).  _tracks[tracker_id][(x, y)] == tracked value
+
+    current_tracker - The tracker id for the currently selected tracker
+    showing_tracker - The tracker id for the tracker that is currently being
+        viewed.  The point to this member is to store off the tracker when
+        the player switches to "Untracked" so that the last tracker they were
+        working on stays in view after the switch.
+    '''
+    __single = None
+    _tracks = {}
+    _colors = {}
+    current_tracker = NO_TRACKER
+    showing_tracker = NO_TRACKER
+
+    def __new__(cls, *args, **kwargs):
+        '''Overridden to implement as a singleton
+        '''
+        # Check to see if a __single exists already for this class
+        # Compare class types instead of just looking for None so
+        # that subclasses will create their own __single objects
+        if cls != type(cls.__single):
+            cls.__single = object.__new__(cls, *args, **kwargs)
+        return cls.__single
+
+    def __init__(self):
+        # Only initialize the colors once
+        if self._colors:
+            return
+        # Use tango colors recommended here:
+        # http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines
+        for tracker_id, cols in enumerate(
+        [(32, 74, 135), # Sky Blue 3
+         (78, 154, 6), # Chameleon 3
+         (206, 92, 0), # Orange 3
+         (143, 89, 2), # Chocolate 3
+         (92, 53, 102), # Plum 3
+         (85, 87, 83), # Aluminium 5
+         (196, 160, 0) # Butter 3
+        ]):
+            self._colors[tracker_id] = tuple([x / 255.0 for x in cols])
+
+    def load(self, pickle):
+        self.current_tracker, self.showing_tracker, self._tracks = pickle
+
+    def save(self):
+        return (self.current_tracker, self.showing_tracker, self.get_trackers())
+
+    def create_tracker (self, tracker_id = 0):
+        '''Create storage for a new tracker
+
+        tracker_id can be passed in to attempt creation of a specific id, but
+        if the tracker_id already exists then the passed number will be
+        incremented until a suitable key can be allocated.
+        '''
+        if not tracker_id:
+            tracker_id = 0
+        while self._tracks.has_key(tracker_id):
+            tracker_id += 1
+        self._tracks[tracker_id] = {}
+        return tracker_id
+
+    def get_tracker(self, tracker_id):
+        if self._tracks.has_key(tracker_id):
+            return self._tracks[tracker_id]
+
+    def delete_tracker(self, tracker_id):
+        if self._tracks.has_key(tracker_id):
+            del self._tracks[tracker_id]
+
+    def reset (self):
+        ''' Reset the tracker information
+        '''
+        self._tracks = {}
+        self.current_tracker = NO_TRACKER
+        self.showing_tracker = NO_TRACKER
+
+    def use_trackers (self, trackers):
+        self._tracks = trackers
+
+    def get_trackers(self):
+        return copy.deepcopy(self._tracks)
+
+    def set_tracker(self, tracker_id):
+        self.current_tracker = tracker_id
+        if tracker_id != NO_TRACKER:
+            self.showing_tracker = tracker_id
+
+    def hide_tracker(self):
+        self.showing_tracker = NO_TRACKER
+
+    def get_tracker_view(self):
+        return((self.current_tracker, self.showing_tracker))
+
+    def set_tracker_view(self, tview):
+        self.current_tracker, self.showing_tracker = tview
+
+    def get_color (self, tracker_id):
+        # Untracked items don't get specially colored
+        if tracker_id == NO_TRACKER:
+            return None
+        # Create a random color for new trackers that are beyond the defaults
+        if not self._colors.has_key(tracker_id):
+            random_color = self._colors[0]
+            while random_color in self._colors.values():
+                # If we have generated all possible colors, this will
+                # enter an infinite loop
+                random_color = (random.randint(0, 100)/100.0,
+                                random.randint(0, 100)/100.0,
+                                random.randint(0, 100)/100.0)
+            self._colors[tracker_id] = random_color
+        return self._colors[tracker_id]
+
+    def get_color_markup(self, tracker_id):
+        color_tuple = self.get_color (tracker_id)
+        if not color_tuple:
+            return None
+        color_markup = '#'
+        color_markup += str(hex(int(color_tuple[0]*255))[2:]).zfill(2)
+        color_markup += str(hex(int(color_tuple[1]*255))[2:]).zfill(2)
+        color_markup += str(hex(int(color_tuple[2]*255))[2:]).zfill(2)
+        return color_markup.upper()
+
+    def get_current_color(self):
+        return self.get_color(self.current_tracker)
+
+    def get_showing_color(self):
+        return self.get_color(self.showing_tracker)
+
+    def add_trace(self, x, y, value, tracker_id = None):
+        '''Add a tracked value
+
+        By default(tracker_id set to None) this method adds a value to the
+        current tracker.  tracker_id can be passed in to add it to a specific
+        tracker.
+        '''
+        if tracker_id == None:
+            to_tracker = self.current_tracker
+        else:
+            to_tracker =  tracker_id
+        # Need a tracker
+        if to_tracker == NO_TRACKER:
+            return
+        # Make sure the dictionary is available for the tracker.
+        if not self._tracks.has_key(to_tracker):
+            self._tracks[to_tracker] = {}
+        # Add it
+        self._tracks[to_tracker][(x, y)] = value
+
+    def remove_trace(self, x, y, from_tracker = None):
+        '''Remove a tracked value
+
+        By default(from_tracker set to None) this method removes all tracked
+        values for a particular cell(x, y coords).  from_tracker can be passed
+        to remove tracked values from a particular tracker only.
+        '''
+        if from_tracker == None:
+            from_tracks = self._tracks.keys()
+        else:
+            from_tracks = [from_tracker]
+        # Delete them
+        for tracker in from_tracks:
+            if self._tracks.has_key(tracker) and self._tracks[tracker].has_key((x, y)):
+                del self._tracks[tracker][(x, y)]
+
+    def get_trackers_for_cell(self, x, y):
+        '''Return all trackers for a cell
+
+        This function is used for the undo mechanism.  A list in the format
+        (tracker, value) is returned so that it may later be reinstated with
+        reset_trackers_for_cell().
+        '''
+        ret = []
+        for tracker, track in self._tracks.items():
+            if track.has_key((x, y)):
+                ret.append((tracker, track[(x, y)]))
+        return ret
+
+    def reset_trackers_for_cell(self, x, y, old_trackers):
+        '''Reset all trackers to a previous state for a cell
+
+        This function is used for the undo mechanism.  It reinstates the
+        tracked values the list created by get_trackers_for_cell().
+        '''
+        # Remove all the current traces
+        for tracker, track in self._tracks.items():
+            if track.has_key((x, y)):
+                del self._tracks[tracker][(x, y)]
+        # Add the old ones back
+        for tracker, value in old_trackers:
+            self._tracks[tracker][(x, y)] = value
+
+



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