#!/usr/bin/env python

# vim: set ts=4 sw=4 et:

#
# Copyright (C) 2005 Vincent Untz <vuntz@gnome.org>
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
#

import gobject
import gtk
import sys

try:
    set
except:
    from sets import Set as set

class PessulusSimpleEditableTreeview (gobject.GObject):
    (
        COLUMN_EDITABLE,
    ) = range (1)

    __gsignals__ = {
        "changed" : ( gobject.SIGNAL_RUN_LAST, None, (gobject.TYPE_PYOBJECT,) )
    }

    def __init__ (self, treeview, addbutton, editbutton, removebutton, sort = True, strip_text = True):
        gobject.GObject.__init__(self)

        self.content_set = None
        self.selected_content = None
        self.new_edited_path = None
        self.editing = False
        self.sensitive = True
        self.strip_text = strip_text

        self.treeview = treeview
        self.addbutton = addbutton
        self.editbutton = editbutton
        self.removebutton = removebutton

        self.liststore = gtk.ListStore (str)
        self.treeview.set_model (self.liststore)
        if sort:
            self.liststore.set_sort_column_id (self.COLUMN_EDITABLE,
                                               gtk.SORT_ASCENDING)

        self.__create_columns ()

        self.addbutton.connect ("clicked", self.__on_add_button_clicked)
        self.editbutton.connect ("clicked", self.__on_edit_button_clicked)
        self.removebutton.connect ("clicked", self.__on_remove_button_clicked)

        treeselection = self.treeview.get_selection ()
        treeselection.set_mode (gtk.SELECTION_SINGLE)
        treeselection.connect ("changed", self.__on_treeselection_changed)

        self.__update_sensitivity ()

    def __create_columns (self):
        self.column = gtk.TreeViewColumn ()
        self.treeview.append_column (self.column)

        self.cell = gtk.CellRendererText ()
        self.column.pack_start (self.cell, True)
        self.column.set_attributes (self.cell, text = self.COLUMN_EDITABLE)

        self.cell.set_property("editable", True)
        self.cell.connect("edited", self.__on_cell_edited)
        # we want to know if we're editing or not
        self.cell.connect("editing-started", self.__on_cell_editing_started)
        # we need to connect to this signal since if we add a row
        # and don't edit it, it shouldn't be really added
        self.cell.connect("editing-canceled", self.__on_cell_editing_canceled)

    def __on_cell_edited (self, cell, path, new_text):
        self.editing = False
        self.__update_sensitivity ()

        new = False
        if self.new_edited_path:
            if self.new_edited_path != path:
                new = True
            else:
                print >> sys.stderr, "Warning: path should have been a new edited one in the treeview"
            self.new_edited_path = None

        if self.strip_text:
            text = new_text.strip ()
        else:
            text = new_text

        # don't accept new items that are ""
        if text == "" and new:
            del (self.liststore[path])
            return

        if new or self.liststore[path][self.COLUMN_EDITABLE] != text:
            # save the selected content so that it will be selected again
            # later
            self.selected_content = text

            if not new:
                self.content_set.remove (self.liststore[path][self.COLUMN_EDITABLE])
            if text != "":
                self.content_set.add (text)

            self.__update_model ()
            self.emit ("changed", self.content_set)

    def __on_cell_editing_started (self, cell, editable, path):
        self.editing = True
        self.__update_sensitivity ()

    def __on_cell_editing_canceled (self, cell):
        self.editing = False
        self.__update_sensitivity ()

        if self.new_edited_path:
            del (self.liststore[self.new_edited_path])
            self.new_edited_path = None

    def __on_add_button_clicked (self, button):
        # add a row and start editing it
        iter = self.liststore.append ()
        path = self.liststore.get_path (iter)
        self.treeview.set_cursor_on_cell (path, self.column, self.cell, True)
        self.new_edited_path = path

    def __on_edit_button_clicked (self, button):
        model, iter = self.treeview.get_selection ().get_selected ()

        # if nothing selected...
        if not iter:
            print >> sys.stderr, "Warning: ask for edition in treeview while nothing selected"
            return

        path = model.get_path (iter)
        self.treeview.set_cursor_on_cell (path, self.column, self.cell, True)

    def __on_remove_button_clicked (self, button):
        model, iter = self.treeview.get_selection ().get_selected ()

        # if nothing selected...
        if not iter:
            print >> sys.stderr, "Warning: ask for removal in treeview while nothing selected"
            return

        selected_value = model[iter][self.COLUMN_EDITABLE]

        # if selection should not be here...
        if selected_value not in self.content_set:
            print >> sys.stderr, "Warning: %s should not be in the treeview" % selected_value
            del (model[iter])
            return
        
        # try to find the future selection (after this item has been removed)
        # note that we can not save a path since the paths might change
        new_selected_iter = model.iter_next (iter)
        if not new_selected_iter:
            # there's no next item. This item was the last one. Select the
            # item that's before.
            children = model.iter_n_children (None)
            if children > 1:
                new_selected_iter = model.iter_nth_child (None, children - 2)
        if new_selected_iter:
            path = model.get_path (new_selected_iter)
            self.selected_content = model[path][self.COLUMN_EDITABLE]

        self.content_set.remove (selected_value)
        self.__update_model ()
        self.emit ("changed", self.content_set)

    def __on_treeselection_changed (self, treeselection):
        self.__update_sensitivity ()

    def __update_sensitivity (self):
        self.treeview.set_sensitive (self.sensitive)

        sensitive = self.sensitive and not self.editing
        self.addbutton.set_sensitive (sensitive)

        selected = self.treeview.get_selection ().count_selected_rows () > 0

        sensitive = self.sensitive and not self.editing and selected
        self.editbutton.set_sensitive (sensitive)

        sensitive = self.sensitive and selected
        self.removebutton.set_sensitive (sensitive)

    def __update_model (self):
        def select_if_should (model, path, iter, data):
            if self.selected_content == model[path][data.COLUMN_EDITABLE]:
                data.treeview.get_selection ().select_path (path)
                data.treeview.scroll_to_cell (path)
                return True

        self.liststore.clear ()

        for value in self.content_set:
            iter = self.liststore.append ()
            self.liststore.set (iter, self.COLUMN_EDITABLE, value)

        if self.selected_content:
            self.liststore.foreach (select_if_should, self)
            self.selected_content = None

        self.__update_sensitivity ()

    def set_sensitive (self, sensitive):
        self.sensitive = sensitive
        self.__update_sensitivity ()

    def update_set (self, set):
        if set == self.content_set:
            return

        model, iter = self.treeview.get_selection ().get_selected ()
        if iter:
            # save the selected content so that it will be selected again
            # later
            self.selected_content = model[iter][self.COLUMN_EDITABLE]

        self.content_set = set.copy ()
        self.__update_model ()

if gtk.pygtk_version < (2, 8, 0):
    gobject.type_register (PessulusSimpleEditableTreeview)


syntax highlighted by Code2HTML, v. 0.9.1