#!/usr/bin/env python
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
# USA
#-------------------------------------------------------------------------
#
# python modules
#
#-------------------------------------------------------------------------

from __future__ import with_statement
import bisect
import time
import sys

import ctypes
   
#-------------------------------------------------------------------------
#
# GNOME/GTK modules
#
#-------------------------------------------------------------------------
from gi.repository import GObject, Gtk

title = "List Store as used in Gramps"
description = """
The GtkListStore is used to store data in list form, to be used later on by a GtkTreeView to display it. This demo builds a simple GtkListStore and displays it. See the Stock Browser demo for a more advanced example.
"""

class Bug:
    def __init__(self, is_fixed, number, severity, description):
        self.is_fixed = is_fixed
        self.number = number
        self.severity = severity
        self.description = description

# initial data we use to fill in the store
data = [Bug(False, 60482, "Normal",     "scrollable notebooks and hidden tabs"),
        Bug(False, 60620, "Critical",   "gdk_window_clear_area (gdkwindow-win32.c) is not thread-safe"),
##        Bug(False, 50214, "Major",      "Xft support does not clean up correctly"),
##        Bug(True,  52877, "Major",      "GtkFileSelection needs a refresh method. "),
##        Bug(False, 56070, "Normal",     "Can't click button after setting in sensitive"),
##        Bug(True,  56355, "Normal",     "GtkLabel - Not all changes propagate correctly"),
##        Bug(False, 50055, "Normal",     "Rework width/height computations for TreeView"),
##        Bug(False, 58278, "Normal",     "gtk_dialog_set_response_sensitive () doesn't work"),
##        Bug(False, 55767, "Normal",     "Getters for all setters"),
##        Bug(False, 56925, "Normal",     "Gtkcalender size"),
##        Bug(False, 56221, "Normal",     "Selectable label needs right-click copy menu"),
##        Bug(True,  50939, "Normal",     "Add shift clicking to GtkTextView"),
##        Bug(False, 6112,  "Enhancement","netscape-like collapsable toolbars"),
##        Bug(False, 1,     "Normal",     "First bug :=)")
        ]

datamap = {}
for x in data:
  datamap[x.number] = x


#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006  Donald N. Allingham
# Copyright (C) 2009       Benny Malengier
# Copyright (C) 2010       Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# $Id: flatbasemodel.py 17422 2011-05-05 20:02:48Z m_d_n $

"""
This module provides the flat treemodel that is used for all flat treeviews.

For performance, GRAMPS does not use Gtk.TreeStore, as that would mean keeping
the entire database table of an object in memory. 
Instead, it suffices to keep in memory the sortkey and the matching handle, 
as well as a map of sortkey,handle to treeview path, and vice versa. 

For a flat view, the index of sortkey,handle will be the path, so it suffices
to keep in memory a map that given a sortkey,handle returns the path.
As we need to be able to insert/delete/update objects, and for that the handle
is all we know initially, and as sortkey,handle is uniquely determined by 
handle, instead of keeping a map of sortkey,handle to path, we keep a map of
handle to path

As a user selects another column to sort, the sortkey must be rebuild, and the
map remade. 

The class FlatNodeMap keeps a sortkeyhandle list with (sortkey, handle) entries,
and a handle2path dictionary. As the Map is flat, the index in sortkeyhandle
corresponds to the path.

The class FlatBaseModel, is the base class for all flat treeview models. 
It keeps a FlatNodeMap, and obtains data from database as needed
"""


#-------------------------------------------------------------------------
#
# FlatNodeMap
#
#-------------------------------------------------------------------------
class FlatNodeMap(object):
    """
    A NodeMap for a flat treeview. In such a TreeView, the paths possible are
    0, 1, 2, ..., n-1, where n is the number of items to show. For the model
    it is needed to keep the Path to Iter mappings of the TreeView in memory
    
    The order of what is shown is based on the unique key: (sortkey, handle)
    Naming:
        * srtkey : key on which to sort
        * hndl   : handle of the object, makes it possible to retrieve the 
                   object from the database. As handle is unique, it is used
                   as the iter for the TreeView
        * index  : the index in the internal lists. When a view is in reverse, 
                    this is not kept physically, but instead via an offset
        * path   : integer path in the TreeView. This will be index if view is
                    ascending, but will begin at back of list if view shows
                    the entries in reverse.
        * index2hndl : list of (srtkey, hndl) tuples. The index gives the 
                        (srtkey, hndl) it belongs to. 
                       This normally is only a part of all possible data 
        * hndl2index : dictionary of *hndl: index* values
        
    The implementation provides a list of (srtkey, hndl) of which the index is
    the path, and a dictionary mapping hndl to index.
    To obtain index given a path, method real_index() is available
    
    ..Note: If a string sortkey is used, apply conv_unicode_tosrtkey_ongtk
            on it , so as to have localized sort
    """

    def __init__(self):
        """
        Create a new instance.
        """
        self._index2hndl = []
        self._fullhndl = self._index2hndl
        self._identical = True
        self._hndl2index = {}
        self._reverse = False
        self.__corr = (0, 1)

    def destroy(self):
        """
        Unset all elements that can prevent garbage collection
        """
        self._index2hndl = None
        self._fullhndl = None
        self._hndl2index = None

    def set_path_map(self, index2hndllist, fullhndllist, identical=True,
                     reverse=False):
        """
        This is the core method to set up the FlatNodeMap
        Input is a list of (srtkey, handle), of which the index is the path
        Calling this method sets the index2hndllist, and creates the hndl2index
        map. 
        fullhndllist is the entire list of (srtkey, handle) that is possible, 
        normally index2hndllist is only part of this list as determined by
        filtering. To avoid memory, if both lists are the same, pass only one
        list twice and set identical to True.
        Reverse sets up how the path is determined from the index. If True the
        first index is the last path
        
        :param index2hndllist: the ascending sorted (sortkey, handle) values 
                    as they will appear in the flat treeview. This often is 
                    a subset of all possible data
        :type index2hndllist: a list of (sortkey, handle) tuples
        :param fullhndllist: the list of all possilbe ascending sorted 
                    (sortkey, handle) values as they will appear in the flat 
                     treeview if all data is shown.
        :type fullhndllist: a list of (sortkey, handle) tuples
        :param identical: identify if index2hndllist and fullhndllist are the
                        same list, so only one is kept in memory.
        :type identical: bool
        """
        self._index2hndl = index2hndllist
        self._hndl2index = {}
        self._identical = identical
        self._fullhndl = self._index2hndl if identical else fullhndllist
        self._reverse = reverse
        self.reverse_order()
        print 'set_path_map'
    
    def full_srtkey_hndl_map(self):
        """
        The list of all possible (sortkey, handle) tuples. 
        This is stored in FlatNodeMap so that it would not be needed to 
        reiterate over the database to obtain all posibilities.
        """
        return self._fullhndl

    def reverse_order(self):
        """
        This method keeps the index2hndl map, but sets it up the index in 
        reverse order. If the hndl2index map does not exist yet, it is created
        in the acending order as given in index2hndl
        The result is always a hndl2index map wich is correct, so or ascending
        order, or reverse order.
        """
        if self._hndl2index:
            #if hndl2index is build already, invert order, otherwise keep 
            # requested order
            self._reverse = not self._reverse
        if self._reverse:
            self.__corr = (len(self._index2hndl) - 1, -1)
        else:
            self.__corr = (0, 1)
        if not self._hndl2index:
            self._hndl2index = dict([key[1], index]
                for index, key in enumerate(self._index2hndl))
    
    def real_path(self, index):
        """
        Given the index in the maps, return the real path. 
        If reverse = False, then index is path, otherwise however, the 
        path must be calculated so that the last index is the first path
        """
        print 'real_path', index, self.__corr[0] + self.__corr[1] * index
        return self.__corr[0] + self.__corr[1] * index
    
    def real_index(self, path):
        """
        Given the path in the view, return the real index.
        If reverse = False, then path is index, otherwise however, the 
        index must be calculated so that the last index is the first path
        """
        return self.__corr[0] + self.__corr[1] * path

    def clear_map(self):
        """
        Clears out the index2hndl and the hndl2index
        """
        self._index2hndl = []
        self._hndl2index = {}
        self._fullhndl = self._index2hndl
        self._identical = True

    def get_path(self, handle):
        """
        Return the path from the passed handle.
        
        :param handle: the key of the object for which the path in the treeview
                        is needed
        :param type: an object handle
        :Returns: the path, or None if handle does not link to a path
        """
        print 'get_path', handle, self._hndl2index.get(handle)
        index = self._hndl2index.get(handle)
        return None if index is None else self.real_path(index)

    def get_sortkey(self, handle):
        """
        Return the sortkey used for the passed handle.
    
        :param handle: the key of the object for which the sortkey
                        is needed
        :param type: an object handle
        :Returns: the sortkey, or None if handle is not present
        """
        index = self._hndl2index.get(handle)
        return None if index is None else self._index2hndl[index][0]

    def get_handle(self, path):
        """
        Return the handle from the path. The path is assumed to be an integer.
        This is accomplished by indexing into the index2hndl
        
        Will raise IndexError if the maps are not filled yet, or if it is empty.
        Caller should take care of this if it allows calling with invalid path
        
        :param path: path as it appears in the treeview
        :type path: integer
        """
        return self._index2hndl[self.real_index(path)][1]

    def find_next_handle(self, handle):
        """
        Finds the next handle based off the passed handle. This is accomplished
        by finding the index associated with the handle, adding or substracting
        one to find the next index, then finding the handle associated with 
        that.
        
        :param handle: the key of the object for which the next handle shown
                        in the treeview is needed
        :param type: an object handle
        """
        index = self._hndl2index.get(handle)
        if self._reverse : 
            index -= 1
            if index < 0:
                # -1 does not raise IndexError, as -1 is last element. Catch.
                return None
        else:
            index += 1

        try:
            return self._index2hndl[index][1]
        except IndexError:
            return None
    
    def get_first_handle(self):
        """
        Return the first handle that must be shown (corresponding to path 0)
        
        Will raise IndexError if the maps are not filled yet, or if it is empty.
        Caller should take care of this if it allows calling with invalid path
        """
        return self._index2hndl[self.real_index(0)][1]

    def __len__(self):
        """
        Return the number of entries in the map.
        """
        return len(self._index2hndl)
    
    def max_rows(self):
        """
        Return maximum number of entries that might be present in the 
        map
        """
        return len(self._fullhndl)
    
    def insert(self, srtkey_hndl, allkeyonly=False):
        """
        Insert a node. Given is a tuple (sortkey, handle), and this is added
        in the correct place, while the hndl2index map is updated.
        Returns the path of the inserted row
        
        :param srtkey_hndl: the (sortkey, handle) tuple that must be inserted
        
        :Returns: path of the row inserted in the treeview
        :Returns type: integer or None
        """
        print 'adding to nodemap'
        if srtkey_hndl[1] in self._hndl2index:
            print ('WARNING: Attempt to add row twice to the model (%s)' %
                    srtkey_hndl[1])
            return
        if not self._identical:
            bisect.insort_left(self._fullhndl, srtkey_hndl)
            if allkeyonly:
                #key is not part of the view
                return None
        insert_pos = bisect.bisect_left(self._index2hndl, srtkey_hndl)
        self._index2hndl.insert(insert_pos, srtkey_hndl)
        #make sure the index map is updated
        for hndl, index in self._hndl2index.iteritems():
            if index >= insert_pos:
                self._hndl2index[hndl] += 1
        self._hndl2index[srtkey_hndl[1]] = insert_pos
        #update self.__corr so it remains correct
        if self._reverse:
            self.__corr = (len(self._index2hndl) - 1, -1)
        return self.real_path(insert_pos)
    
    def delete(self, srtkey_hndl):
        """
        Delete the row with the given (sortkey, handle).
        This then rebuilds the hndl2index, subtracting one from each item
        greater than the deleted index.
        path of deleted row is returned
        If handle is not present, None is returned
        
        :param srtkey_hndl: the (sortkey, handle) tuple that must be inserted
        
        :Returns: path of the row deleted from the treeview
        :Returns type: integer or None
        """
        #remove it from the full list first
        if not self._identical:
            del_pos = bisect.bisect_left(self._fullhndl, srtkey_hndl)
            #check that indeed this is correct:
            if not self._fullhndl[del_pos][1] == srtkey_hndl[1]:
                raise KeyError, 'Handle %s not in list of all handles' %  \
                                                srtkey_hndl[1]
            del self._fullhndl[del_pos]
        #now remove it from the index maps
        handle = srtkey_hndl[1]
        try:
            index = self._hndl2index[handle]
        except KeyError:
            # key not present in the treeview
            return None
        del self._index2hndl[index]
        del self._hndl2index[handle]
        #update self.__corr so it remains correct
        delpath = self.real_path(index)
        if self._reverse:
            self.__corr = (len(self._index2hndl) - 1, -1)
        #update the handle2path map so it remains correct
        for key, val in self._hndl2index.iteritems():
            if val > index:
                self._hndl2index[key] -= 1
        return delpath

#-------------------------------------------------------------------------
#
# FlatBaseModel
#
#-------------------------------------------------------------------------
##TODO GTK3: no more GenericTreemodel to inherit from!
class FlatBaseModel(GObject.GObject, Gtk.TreeModel):
    """
    The base class for all flat treeview models. 
    It keeps a FlatNodeMap, and obtains data from database as needed
    """
    __gtype_name__ = 'FlatBaseModel'
    
    fmap = [
        lambda x: x.is_fixed,
        lambda x: x.number,
        lambda x: x.severity,
        lambda x: x.description,
        lambda x: 0,
        lambda x: 'add',
        lambda x: False,
        lambda x: False,
        ]
    
    COLTYPE = [
                GObject.TYPE_BOOLEAN,
                GObject.TYPE_INT,
                GObject.TYPE_STRING,
                GObject.TYPE_STRING,
                GObject.TYPE_INT,
                GObject.TYPE_STRING,
                GObject.TYPE_BOOLEAN,
                GObject.TYPE_BOOLEAN
                ]

    def __init__(self):
        cput = time.clock()
        GObject.GObject.__init__(self)
        
        self.prev_handle = None
        self.prev_data = None
##TODO GTK3: no more leak ref
##        self.set_property("leak_references", False)
        self._in_build = False

        self.node_map = FlatNodeMap()
        self.map = lambda x: datamap[x]
        self.rebuild_data = self._rebuild_search

        self.rebuild_data()

    def destroy(self):
        """
        Unset all elements that prevent garbage collection
        """
        if self.node_map:
            self.node_map.destroy()
        self.node_map = None
        self.rebuild_data = self._rebuild_search

    def total(self):
        """
        Total number of items that maximally can be shown
        """
        return self.node_map.max_rows()

    def displayed(self):
        """
        Number of items that are currently displayed
        """
        return len(self.node_map)

    def tooltip_column(self):
        """
        Return the column for tooltips.
        """
        return self._tooltip_column
        
    def color_column(self):
        """
        Return the color column.
        """
        return None

    def clear_cache(self, handle=None):
        """
        If you use a cache, overwrite here so it is cleared when this 
        method is called (on rebuild)
        :param handle: if None, clear entire cache, otherwise clear the handle
                       entry if present
        """
        pass

    def sort_keys(self):
        """
        Return the (sort_key, handle) list of all data that can maximally 
        be shown. 
        This list is sorted ascending
        """
        res = []
        for x in data:
            res.append((x.description, x.number))
        newres = sorted(res)
        return newres

    def _rebuild_search(self, ignore=None):
        """ function called when view must be build, given a search text
            in the top search bar
        """
        self.clear_cache()
        self._in_build = True
        allkeys = self.node_map.full_srtkey_hndl_map()
        if not allkeys:
            allkeys = self.sort_keys()
        ident = True
        dlist = allkeys
        self.node_map.set_path_map(dlist, allkeys, identical=ident, 
                                       reverse=False)

        self._in_build = False
        
    def add_row_by_handle(self, handle):
        """
        Add a row. This is called after object with handle is created.
        Row is only added if search/filter data is such that it must be shown
        """
        if self.node_map.get_path(handle) is not None:
            return # row is already displayed
        data = self.map(handle)
        insert_val = (map(conv_unicode_tosrtkey_ongtk, self.sort_func(data)),
                            handle)
        if not self.search or \
                (self.search and self.search.match(handle, self.db)):
            #row needs to be added to the model
            insert_path = self.node_map.insert(insert_val)

            if insert_path is not None:
                node = self.get_iter(insert_path)
                self.row_inserted(insert_path, node)
        else:
            self.node_map.insert(insert_val, allkeyonly=True)

    def delete_row_by_handle(self, handle):
        """
        Delete a row, called after the object with handle is deleted
        """
        if self.node_map.get_path(handle) is None:
            return # row is not currently displayed
        self.clear_cache(handle)
        delete_val = (self.node_map.get_sortkey(handle), handle)
        delete_path = self.node_map.delete(delete_val)
        #delete_path is an integer from 0 to n-1
        if delete_path is not None: 
            self.row_deleted(delete_path)

    def update_row_by_handle(self, handle):
        """
        Update a row, called after the object with handle is changed
        """
        if self.node_map.get_path(handle) is None:
            return # row is not currently displayed
        self.clear_cache(handle)
        oldsortkey = self.node_map.get_sortkey(handle)
        newsortkey = map(conv_unicode_tosrtkey_ongtk, self.sort_func(self.map(
                            handle)))
        if oldsortkey is None or oldsortkey != newsortkey:
            #or the changed object is not present in the view due to filtering
            #or the order of the object must change. 
            self.delete_row_by_handle(handle)
            self.add_row_by_handle(handle)
        else:
            #the row is visible in the view, is changed, but the order is fixed
            path = self.node_map.get_path(handle)
            node = self.get_iter(path)
            self.row_changed(path, node)

    def handle2path(self, handle):
        """
        Obtain from a handle, a path.
        Part of common api with flat/treebasemodel
        """
        return self.on_get_path(handle)

    def do_get_flags(self):
        """
        Returns the GtkTreeModelFlags for this particular type of model
        See Gtk.TreeModel
        """
        print "get_flags"
        return Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST

    def do_get_n_columns(self):
        """
        Return the number of columns. Must be implemented in the child objects
        See Gtk.TreeModel
        """
        print 'get_n_columns'
        return len(self.COLTYPE)

    def do_get_iter_first(self):
        print 'get iter first'
        #raw_input('proceed')
        return self.node_map.get_first_handle()

    def do_get_path(self, handle):
        """
        Return the tree path (a tuple of indices at the various
        levels) for a particular iter. We use handles for unique key iters
        See Gtk.TreeModel
        """
        print "get_path"
        #raw_input('proceed')
        #print  handle, self.node_map.get_path(handle.stamp)
        return Gtk.TreePath(self.node_map.get_path(handle.stamp))

    def do_get_column_type(self, index):
        """
        See Gtk.TreeModel
        """
        print 'do get_column_type', index
        #raw_input('proceed')
        return self.COLTYPE[index]

    def do_get_iter(self, path):
        """
        See Gtk.TreeModel
        """
        print "get_iter", path.get_indices()
        handle = self.node_map.get_handle(path.get_indices()[0])
        #raw_input('proceed')
        return (True, handle)

    def do_get_value(self, handle, col):
        """
        See Gtk.TreeModel. 
        col is the model column that is needed, not the visible column!
        """
        if handle is None:
            return self.do_get_value(self.first, col)
        print "get value", handle.stamp, col
        #raw_input('proceed')
        if handle.stamp != self.prev_handle:
           self.prev_data = self.map(handle.stamp)
           self.prev_handle = handle.stamp
        print 'get value return val=', self.fmap[col](self.prev_data)
        return self._convert_value(help, col, self.fmap[col](self.prev_data))
    
    def do_iter_next(self, handle):
        """
        Returns the next node at this level of the tree
        See Gtk.TreeModel
        """
        #print "iter next"
        #raw_input('proceed')
        #print "stamp", handle.stamp
        newstamp = self.node_map.find_next_handle(handle.stamp)
        if newstamp is None:
            return False
        handle.stamp = newstamp
        return True

    def do_iter_previous(self, handle):
        print 'iter previous'
        return

    def do_iter_children(self, handle):
        """
        Return the first child of the node
        See Gtk.TreeModel
        """
        print "iter_children"
        #raw_input('proceed')
        if handle is None and len(self.node_map):
            return (True, self.node_map.get_first_handle())
        return (False, None)

    def do_iter_has_child(self, handle):
        """
        Returns true if this node has children
        See Gtk.TreeModel
        """
        print 'iter_has_child'
        #raw_input('proceed')
        if handle is None:
            return len(self.node_map) > 0
        return False

    def do_iter_n_children(self, handle):
        """
        See Gtk.TreeModel
        """
        print 'iter_n_children'
        #raw_input('proceed')
        if handle is None:
            return len(self.node_map)
        return 0

    def do_iter_nth_child(self, handle, nth):
        """
        See Gtk.TreeModel
        """
        print 'iter_nth_child', handle.stamp, nth
        #raw_input('proceed')
        return (False, None)

    def do_iter_parent(self, handle):
        """
        Returns the parent of this node
        See Gtk.TreeModel
        """
        print 'iter_parent'
        #raw_input('proceed')
        return None

    def do_ref_node(self, iter):
        print 'ref node'

    def do_unref_node(self, iter):
        print 'unref node'

class ListStoreApp:
    (COLUMN_FIXED,
     COLUMN_NUMBER,
     COLUMN_SEVERITY,
     COLUMN_DESCRIPTION,
     COLUMN_PULSE,
     COLUMN_ICON,
     COLUMN_ACTIVE,
     COLUMN_SENSITIVE,
     NUM_COLUMNS) = range(9)

    def __init__(self):
        self.window = Gtk.Window()
        self.window.set_title('Gtk.ListStore Demo')
        self.window.connect('destroy', Gtk.main_quit)

        vbox = Gtk.VBox(spacing=8)
        self.window.add(vbox)

        label = Gtk.Label('This is the bug list (note: not based on real data, it would be nice to have a nice ODBC interface to bugzilla or so, though).')
        vbox.pack_start(label, False, False, 0)

        sw = Gtk.ScrolledWindow()
        sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
        sw.set_policy(Gtk.PolicyType.NEVER,
                      Gtk.PolicyType.AUTOMATIC)
        vbox.pack_start(sw, True, True, 0)

        self.create_model()
        treeview = Gtk.TreeView(model=self.model)
        treeview.set_rules_hint(True)
        treeview.set_search_column(self.COLUMN_DESCRIPTION)
        sw.add(treeview)

        self.add_columns(treeview)

        self.window.set_default_size(280, 250)
        self.window.show_all()
        
        self.window.connect('delete-event', self.window_closed)
        #self.timeout = GObject.timeout_add (80, self.spinner_timeout)

    def window_closed(self, window, event):
        pass
        #if self.timeout != 0:
        #    GObject.source_remove(self.timeout)

    def spinner_timeout(self):
        if self.model is None:
            return False
            
        iter_ = self.model.get_iter_first()
        pulse = self.model.get(iter_, self.COLUMN_PULSE)[0]
        if pulse == 999999999:
            pulse = 0
        else:
            pulse += 1

        #self.model.set_value(iter_, self.COLUMN_PULSE, pulse)
        #self.model.set_value(iter_, self.COLUMN_ACTIVE, True)

        return True

    def create_model(self):
        #we use the gramps store:
        self.model = FlatBaseModel()

    def add_columns(self, treeview):
        model = treeview.get_model()
        
        # column for is_fixed toggle
        renderer = Gtk.CellRendererToggle()
        renderer.connect('toggled', self.is_fixed_toggled, model)

        column = Gtk.TreeViewColumn("Fixed?", renderer,
                                    active=self.COLUMN_FIXED)
        column.set_fixed_width(50)
        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        treeview.append_column(column)

        # column for severities
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Severity", renderer,
                                    text=self.COLUMN_SEVERITY)
        column.set_sort_column_id(self.COLUMN_SEVERITY)
        treeview.append_column(column)

        # column for description
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Description", renderer,
                                    text=self.COLUMN_DESCRIPTION)
        column.set_sort_column_id(self.COLUMN_DESCRIPTION)
        treeview.append_column(column)

        # column for spinner
        renderer = Gtk.CellRendererSpinner()
        column = Gtk.TreeViewColumn("Spinning", renderer,
                                    pulse=self.COLUMN_PULSE,
                                    active=self.COLUMN_ACTIVE)
        column.set_sort_column_id(self.COLUMN_PULSE)
        treeview.append_column(column)

        # column for symbolic icon
        renderer = Gtk.CellRendererPixbuf()
        renderer.props.follow_state = True
        column = Gtk.TreeViewColumn("Symbolic icon", renderer,
                                    icon_name=self.COLUMN_ICON,
                                    sensitive=self.COLUMN_SENSITIVE)
        column.set_sort_column_id(self.COLUMN_ICON)
        treeview.append_column(column)

    def is_fixed_toggled(self, cell, path_str, model):
        # get toggled iter
        iter_ = model.get_iter(path_str)
        is_fixed = model.get_value(iter_, self.COLUMN_FIXED)

        # do something with value
        is_fixed ^= 1

        model.set_value(iter_, self.COLUMN_FIXED, is_fixed)

def main(demoapp=None):
    app = ListStoreApp()
    Gtk.main()

if __name__ == '__main__':
    main()
