# GNU Solfege - ear training for GNOME
# Copyright (C) 2005  Tom Cato Amundsen
#
# 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

import gtk
import gobject
import pprint
import copy
import sys
import os
import cfg
import gu
import lessonfilegui

class LearningTree:
    def __init__(self, lessonfile_manager):
        self.lessonfile_manager = lessonfile_manager
    def load(self, filename):
        self.m_visibility = 0
        g = {'_': lambda s: s}
        try:
            self.m_topics, self.m_deps = eval(file(filename, 'r').read(), g)
        except ValueError:
            self.m_topics, self.m_deps, self.m_visibility = eval(file(filename, 'r').read(), g)
        junk_id = []
        for topic in self.m_topics:
            for j in topic['lessons']:
                if j not in self.lessonfile_manager.m_uiddb:
                    print >> sys.stderr, "Junking the registration of lesson_id '%s'\nfrom learning tree. File not found." % j
                    junk_id.append(j)
        for j in junk_id:
            for topic in self.m_topics:
                if j in topic['lessons']:
                    del topic['lessons'][topic['lessons'].index(j)]
            if j in self.m_deps:
                del self.m_deps[j]
            for d in self.m_deps:
                if j in self.m_deps[d]:
                    self.m_deps[d].remove(j)
        self.sort_topics()
        self.calculate_visibilities()
    def save(self, filename):
        ofile = file(filename, 'w')
        print >> ofile, "[ # toplevel list"
        print >> ofile, "  [ # start of topics list"
        for topic in self.m_topics:
            print >> ofile, "    {'name': _('%s'), " % topic['name']
            print >> ofile, "     'lessons': ["
            for lesson_id in topic['lessons']:
                print >> ofile, "          '%s'," % lesson_id
            print >> ofile, "     ]},"
        print >> ofile, "  ], # end of topics list"
        ofile.write("%s,\n%s]" % (pprint.pformat(self.m_deps), self.m_visibility))
        ofile.close()
    def new_topic(self, topicname):
        assert topicname not in self.m_topics
        self.m_topics.append({'name': topicname, 'lessons': []})
    def delete_topic(self, topic_idx):
        del self.m_topics[topic_idx]
    def sort_topics(self):
        """
        Sort the lessons in each topic.
        """
        for topic in self.m_topics:
            topic['lessons'].sort(lambda a, b: self.cmp(a, b))
    def move_topic_up(self, topic_idx):
        assert topic_idx > 0
        self.m_topics[topic_idx], self.m_topics[topic_idx - 1] \
            = self.m_topics[topic_idx - 1], self.m_topics[topic_idx]
    def move_topic_down(self, topic_idx):
        assert topic_idx < len(self.m_topics) - 1
        self.m_topics[topic_idx], self.m_topics[topic_idx + 1] \
            = self.m_topics[topic_idx + 1], self.m_topics[topic_idx]
    def move_lesson_up(self, topic_idx, lesson_idx):
        """
        Return True if successful, else None.
        The tree is unchanged if we return None.
        """
        assert lesson_idx > 0
        move_id = self.m_topics[topic_idx]['lessons'][lesson_idx]
        prev_id = self.m_topics[topic_idx]['lessons'][lesson_idx - 1]
        if prev_id not in list(self.iter_subdeps(move_id)):
            self.m_topics[topic_idx]['lessons'][lesson_idx-1],\
              self.m_topics[topic_idx]['lessons'][lesson_idx] = \
              self.m_topics[topic_idx]['lessons'][lesson_idx],\
              self.m_topics[topic_idx]['lessons'][lesson_idx-1]
            return True
    def move_lesson_down(self, topic_idx, lesson_idx):
        """
        Return true if successful, else None.
        The tree is unchanged if we return None.
        """
        assert lesson_idx < len(self.m_topics[topic_idx]['lessons'])
        return self.move_lesson_up(topic_idx, lesson_idx + 1)
    def set_topic_title(self, topic_idx, new_title):
        self.m_topics[topic_idx]['name'] = new_title
    def add_lesson(self, topic_idx, lesson_id):
        """
        Each lesson can only be once in a topic.
        Return True if sucessful, False if not
        """
        if lesson_id not in self.m_topics[topic_idx]['lessons']:
            if not lesson_id in self.m_deps:
                self.m_deps[lesson_id] = []
            self.m_topics[topic_idx]['lessons'].append(lesson_id)
            self.m_topics[topic_idx]['lessons'].sort(lambda a, b: self.cmp(a, b))
        else:
            return False
    def delete_lesson(self, topic_idx, lesson_idx):
        del self.m_topics[topic_idx]['lessons'][lesson_idx]
    def add_dependency(self, topic_idx, lesson_idx, dep_id):
        lesson_id = self.m_topics[topic_idx]['lessons'][lesson_idx]
        assert dep_id not in self.m_deps[lesson_id]
        self.m_deps[lesson_id].append(dep_id)
        self.sort_topics()
    def delete_dependency(self, topic_idx, lesson_idx, dep_idx):
        lesson_id = self.m_topics[topic_idx]['lessons'][lesson_idx]
        del self.m_deps[lesson_id][dep_idx]
        self.sort_topics()
    def iterate_topics(self):
        for i in self.m_topics:
            yield i['name']
    def iterate_lessons(self, n):
        """
        Iterate the lessons of topic number n.
        Yields lesson_ids
        """
        for i in self.m_topics[n]['lessons']:
            yield i
    def iterate_topics_for_id(self, lesson_id):
        """
        Yield a tuple (INT, STR) for each topic lesson_id is
        a member of, where
        INT -- the number of the topic
        STR -- the title of the topic
        """
        for n, topic in enumerate(self.m_topics):
            if lesson_id[0] in topic['lessons']:
                yield (n, topic['name'])
    def iterate_deps_for_id(self, lesson_id):
        """
        Iterate all the direct dependencies for lesson_id.
        It does not iterate the sub-dependencies.
        """
        for dep in self.m_deps[lesson_id]:
            yield dep
    def iterate_deps(self, topic_idx, lesson_idx):
        lesson_id = self.m_topics[topic_idx]['lessons'][lesson_idx]
        for dep in self.m_deps[lesson_id]:
            yield dep
    def iterate_possible_deps_for(self, topic_idx, lesson_idx):
        """
        All lessons, except those on the x-list.
        You get on the x-list if:
        1. is OBJECT
        2. already in the depends list of OBJECT
        3. depend on anything in the x-list
        4. is a dep (of dep)* of OBJ


        Filter out lessons that
        1. is OBJECT
        2. is in depends tree below OBJECT
        3. has OBJECT in its depends tree
        """
        # The lesson_id we are finding possible deps for
        this_id = self.m_topics[topic_idx]['lessons'][lesson_idx]
        # First, lets make a list of all lessons that currently are in a topic
        # (we use a list instead of a string because it is faster.
        #  a set (available from python 2.4 would have been even better)
        used = {}
        for topic in self.m_topics:
            for lesson_id in topic['lessons']:
                used[lesson_id] = True
        # Filter out this_id (point 1 in the list in the docstring)
        del used[this_id]
        def check(lesson_id):
            # Filter out according to #2 and #3 in the docstring
            if lesson_id in list(self.iter_subdeps(this_id)) \
                    or this_id in list(self.iter_subdeps(lesson_id)):
                return False
            return True
        for i in filter(check, used.keys()):
            yield i
    def iter_subdeps(self, lesson_id):
        for n in self.m_deps[lesson_id]:
            yield n
            for nn in self.iter_subdeps(n):
                yield nn
    def iterate_all_lesson_ids(self):
        """
        Yield the lesson_id of all lesson files that are in a topic.
        Only yield each lesson_id once.
        """
        d = {}
        for topic in self.m_topics:
            for lesson_id in topic['lessons']:
                if not lesson_id in d:
                    d[lesson_id] = True
                    yield lesson_id
    def is_practisable(self, lesson_id):
        for i in self.iterate_deps_for_id(lesson_id):
            if not self.lessonfile_manager.is_test_passed(i):
                return False
        return True
    def calculate_visibilities(self):
        self.m_visibilities = {}
        v = self.m_deps.keys()
        v.sort(lambda a, b: self.cmp(a, b))
        for i in v:
            if i not in self.lessonfile_manager.m_uiddb:
                continue
            if not self.lessonfile_manager.get(i, 'test'):
                self.m_visibilities[i] = 0
            elif not list(self.iterate_deps_for_id(i)):
                self.m_visibilities[i] = 0
            elif self.is_practisable(i):
                self.m_visibilities[i] = 0
            else:
                self.m_visibilities[i] = max([self.m_visibilities[x] for x in self.m_deps[i]]) + 1
    def cmp(self, id_a, id_b):
        """
        Return -1, 0, 1, like a cmp function.
        """
        deps_a = list(self.iter_subdeps(id_b))
        if id_a in deps_a:
            return -1
        deps_b = list(self.iter_subdeps(id_a))
        if id_b in deps_b:
            return 1
        return cmp(len(deps_b), len(deps_a))
    def get_use_count(self, lesson_id):
        """
        Return an integer telling how many times the lesson lesson_id
        is used as an exercise.
        """
        count = 0
        for topic in self.m_topics:
            for i in topic['lessons']:
                if i == lesson_id:
                    count += 1
        return count
    def get_dep_use_count(self, lesson_id):
        """
        Return an integer telling how many lessons that depends on lesson_id.
        """
        count = 0
        for v in self.m_deps.values():
            for i in v:
                if i == lesson_id:
                    count += 1
        return count
    def remove_all_deps_of(self, del_id):
        for v in self.m_deps.values():
            if del_id in v:
                del v[v.index(del_id)]
    def dump(self):
        pprint.pprint(self.m_topics)
        pprint.pprint(self.m_deps)


class LessonFileDialogCommon(gtk.Dialog):
    def __init__(self, app, tree, cur_topic_idx):
        gtk.Dialog.__init__(self, _("Select lesson file"), self, buttons=
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,))
        self.m_app = app
        self.m_tree = tree
        self.m_app.lessonfile_manager.parse()
        lessonfilegui.handle_lesson_id_crash(self.m_app.lessonfile_manager)
        self.m_cur_topic_idx = cur_topic_idx
        self.g_ok_button = self.add_button("gtk-ok", gtk.RESPONSE_OK)
        self.g_ok_button.set_sensitive(False)
        self.set_default_size(800, 500)
        vbox = gtk.VBox()
        self.vbox.pack_start(vbox, False)
        self.g_liststore = gtk.ListStore(gobject.TYPE_STRING,
            gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT,
            gobject.TYPE_STRING)
        self.update_filelist()
        self.g_treeview = gtk.TreeView(self.g_liststore)
        self.g_treeview.connect('cursor-changed', self.on_cursor_changed)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_("Used"), renderer, text=3)
        column.set_sort_column_id(3)
        column.set_resizable(True)
        self.g_treeview.append_column(column)
        column = gtk.TreeViewColumn(_("Title"), renderer, text=0)
        column.set_resizable(True)
        column.set_sort_column_id(0)
        self.g_treeview.append_column(column)
        column = gtk.TreeViewColumn(_("Filename"), renderer, text=1)
        column.set_resizable(True)
        self.g_treeview.append_column(column)
        def sort_func(store, itera, iterb):
            return cmp(store.get(itera, 1)[0], store.get(iterb, 1)[0])
        self.g_treeview.get_model().set_sort_func(1, sort_func)
        column.set_sort_column_id(1)
        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.add(self.g_treeview)
        self.vbox.pack_start(scrolled_window)
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        hbox.set_border_width(12)
        self.vbox.pack_start(hbox, False)
        hbox.pack_start(gtk.Label(_("Module:")), False)
        self.g_module = gtk.Label()
        hbox.pack_start(self.g_module, False)
        #
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        hbox.set_border_width(12)
        self.vbox.pack_start(hbox, False)
        label = gtk.Label(_("Used by topic(s):"))
        label.set_alignment(0.0, 0.0)
        hbox.pack_start(gtk.Label(_("Used by topic(s):")), False)
        self.g_topics_vbox = gtk.VBox()
        hbox.pack_start(self.g_topics_vbox, False)
        #
        self.show_all()
        self.g_treeview.connect('row-activated', self.on_row_activated)
    def get_selected_lesson_id(self):
        """
        Returns None if no lesson file is selected.
        """
        i = self.g_treeview.get_cursor()[0]
        if i:
            i = i[0]
            iter = self.g_liststore.iter_nth_child(None, i)
            return self.g_liststore.get(iter, 2)[0]
    def on_row_activated(self, treeview, row, treeviewcolumn):
        self.response(gtk.RESPONSE_OK)
    def on_cursor_changed(self, treeview):
        i = self.g_treeview.get_cursor()[0][0]
        iter = self.g_liststore.iter_nth_child(None, i)
        self.g_module.set_text(self.g_liststore.get(iter, 4)[0])
        self.g_topics_vbox.foreach(lambda w: w.destroy())
        for n, s in self.m_tree.iterate_topics_for_id(
                self.g_liststore.get(iter, 2)):
            label = gtk.Label(s)
            label.set_alignment(0.0, 0.5)
            label.show()
            self.g_topics_vbox.pack_start(label)
        self.g_ok_button.set_sensitive(True)


class SelectLessonFileDialog(LessonFileDialogCommon):
    def __init__(self, app, tree, cur_topic_idx):
        LessonFileDialogCommon.__init__(self, app, tree, cur_topic_idx)
        g_show_used = gu.nCheckButton('SelectLessonFileDialog',
            'show_used_files', _("_Show files used in other topics"),
            False, self.update_filelist)
        self.vbox.pack_start(g_show_used, False)
        self.vbox.reorder_child(g_show_used, 0)
    def update_filelist(self, *v):
        self.g_liststore.clear()
        if cfg.get_bool('SelectLessonFileDialog/show_used_files'):
            lesson_id_list = [i for i in list(self.m_app.lessonfile_manager.iterate_lesson_ids()) if i not in self.m_tree.m_topics[self.m_cur_topic_idx]['lessons']]
        else:
            lesson_id_list = [i for i in list(self.m_app.lessonfile_manager.iterate_lesson_ids()) if self.m_tree.get_use_count(i) == 0]
        for lesson_id in lesson_id_list:
            d = self.m_app.lessonfile_manager.m_uiddb[lesson_id]
            self.g_liststore.append((d['header']['title'], d['filename'], lesson_id, self.m_tree.get_use_count(lesson_id), self.m_app.lessonfile_manager.get(lesson_id, 'module')))


class DepsLessonFileDialog(LessonFileDialogCommon):
    def __init__(self, app, tree, cur_topic_idx, i):
        """
        i is the index from the lesson in the topic in the tree.
        """
        self.m_idx = i
        LessonFileDialogCommon.__init__(self, app, tree, cur_topic_idx)
        #self.update_filelist(i)
    def update_filelist(self):
        self.g_liststore.clear()
        for lesson_id in list(self.m_tree.iterate_possible_deps_for(self.m_cur_topic_idx, self.m_idx)):
            d = self.m_app.lessonfile_manager.m_uiddb[lesson_id]
            self.g_liststore.append((d['header']['title'], d['filename'], lesson_id, self.m_tree.get_use_count(lesson_id), self.m_app.lessonfile_manager.get(lesson_id, 'module')))


class Window(gtk.Window):
    def __init__(self, app):
        gtk.Window.__init__(self)
        self.m_app = app
        self.set_default_size(500, 500)
        self.vbox = gtk.VBox()
        self.add(self.vbox)
        hbox = gtk.HBox()
        self.vbox.pack_start(hbox, False)
        g = gtk.Button(_("Save"))
        g.connect('clicked', self.on_save)
        hbox.pack_start(g, False)
        g = gtk.Button(_("Save as..."))
        g.connect('clicked', self.on_save_as)
        hbox.pack_start(g, False)
        g = gtk.Button(_("Close"))
        g.connect('clicked', self.close_window)
        hbox.pack_start(g, False)
        self.g_content_vbox = gtk.VBox()
        self.vbox.pack_start(self.g_content_vbox)
        self.g_content_vbox.set_border_width(12)
        self.g_content_vbox.set_spacing(8)
        buttons_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
        # Visibility
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        l = gtk.Label(_("Default visibility:"))
        hbox.pack_start(l, False)
        self.g_visibility = gtk.Entry()
        self.g_visibility.connect('changed', self.on_visibility_entry_changed)
        hbox.pack_start(self.g_visibility, False)
        self.g_content_vbox.pack_start(hbox, False)
        ##### Topics
        self.g_topics_box = gtk.VBox()
        self.g_content_vbox.pack_start(self.g_topics_box, False)
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        self.g_topics_box.pack_start(hbox, False)
        vbox = gtk.VBox() # The box with topic buttons
        buttons_sizegroup.add_widget(vbox)
        hbox.pack_start(vbox, False)
        g = gtk.Button(_("New topic"))
        g.connect('clicked', self.on_new_topic)
        vbox.pack_start(g, False)
        self.g_delete_topic = gtk.Button(_("Delete topic"))
        self.g_delete_topic.connect('clicked', self.on_delete_topic)
        vbox.pack_start(self.g_delete_topic, False)
        self.g_move_topic_up = gtk.Button(_("Move topic up"))
        self.g_move_topic_up.connect('clicked', self.on_move_topic_up)
        vbox.pack_start(self.g_move_topic_up, False)
        self.g_move_topic_down = gtk.Button(_("Move topic down"))
        self.g_move_topic_down.connect('clicked', self.on_move_topic_down)
        vbox.pack_start(self.g_move_topic_down, False)
        vbox = gtk.VBox() # The box with topic tree and heading
        hbox.pack_start(vbox)
        label = gtk.Label("<b>%s</b>" % _("_Topics:"))
        label.set_alignment(0.0, 0.5)
        label.set_use_underline(True)
        label.set_use_markup(True)
        vbox.pack_start(label, False)
        self.g_topic_scrollwin = gtk.ScrolledWindow()
        self.g_topic_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vbox.pack_start(self.g_topic_scrollwin)
        self.g_topics = gtk.TreeView()
        label.set_mnemonic_widget(self.g_topics)
        self.g_topics.set_headers_visible(False)
        self.g_topics.connect('cursor-changed', self.on_topic_change)
        self.g_topic_scrollwin.add(self.g_topics)
        #
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self.on_topic_title_edited)
        column = gtk.TreeViewColumn(_("Title"), renderer, text=0)
        self.g_topics.append_column(column)
        self.g_topics_liststore = gtk.ListStore(gobject.TYPE_STRING)
        self.g_topics.set_model(self.g_topics_liststore)
        ##### Lessons
        self.g_lessons_box = gtk.VBox()
        self.g_content_vbox.pack_start(self.g_lessons_box, False)
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        self.g_lessons_box.pack_start(hbox, False)
        vbox = gtk.VBox() # The box with lesson buttons
        buttons_sizegroup.add_widget(vbox)
        hbox.pack_start(vbox, False)
        self.g_add_lesson = gtk.Button(_("Add lesson"))
        self.g_add_lesson.connect('clicked', self.on_new_lesson)
        vbox.pack_start(self.g_add_lesson, False)
        self.g_delete_lesson = gtk.Button(_("Delete lesson"))
        self.g_delete_lesson.connect('clicked', self.on_delete_lesson)
        vbox.pack_start(self.g_delete_lesson, False)
        #
        self.g_move_lesson_up = gtk.Button(_("Move lesson up"))
        self.g_move_lesson_up.connect('clicked', self.on_move_lesson_up)
        vbox.pack_start(self.g_move_lesson_up, False)
        #
        self.g_move_lesson_down = gtk.Button(_("Move lesson down"))
        self.g_move_lesson_down.connect('clicked', self.on_move_lesson_down)
        vbox.pack_start(self.g_move_lesson_down, False)
        #
        vbox = gtk.VBox() # The box with the lesson tree and heading
        hbox.pack_start(vbox)
        self.g_lessons_heading = gtk.Label()
        self.g_lessons_heading.set_alignment(0.0, 0.5)
        vbox.pack_start(self.g_lessons_heading, False)
        self.g_lesson_scrollwin = gtk.ScrolledWindow()
        self.g_lesson_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.g_lessons = gtk.TreeView()
        self.g_lessons_heading.set_mnemonic_widget(self.g_lessons)
        self.g_lesson_scrollwin.add(self.g_lessons)
        vbox.pack_start(self.g_lesson_scrollwin)
        self.g_lessons.set_headers_visible(False)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_("Title"), renderer, text=0)
        self.g_lessons.append_column(column)
        self.g_lessons_liststore = gtk.ListStore(gobject.TYPE_STRING,
            gobject.TYPE_STRING)
        self.g_lessons.set_model(self.g_lessons_liststore)
        self.g_lessons.connect('cursor-changed', self.on_lesson_change)
        ### Deps
        self.g_deps_box = gtk.VBox()
        self.g_content_vbox.pack_start(self.g_deps_box, False)
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        self.g_deps_box.pack_start(hbox, False)
        vbox = gtk.VBox() # The box with deps
        buttons_sizegroup.add_widget(vbox)
        hbox.pack_start(vbox, False)
        g = gtk.Button(_("New dependency"))
        g.connect('clicked', self.on_add_dep)
        vbox.pack_start(g, False)
        self.g_delete_dependency = gtk.Button(_("Delete depencency"))
        self.g_delete_dependency.connect('clicked', self.on_delete_dep)
        vbox.pack_start(self.g_delete_dependency, False)
        vbox = gtk.VBox() # The box with deps tree and heading
        hbox.pack_start(vbox)
        self.g_deps_heading = gtk.Label()
        self.g_deps_heading.set_alignment(0.0, 0.5)
        self.g_deps_heading.set_line_wrap(True)
        vbox.pack_start(self.g_deps_heading, False)
        self.g_deps_scrollwin = gtk.ScrolledWindow()
        self.g_deps_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vbox.pack_start(self.g_deps_scrollwin, False)

        self.g_deps = gtk.TreeView()
        self.g_deps_scrollwin.add(self.g_deps)
        self.g_deps.set_headers_visible(False)
        self.g_deps.connect('cursor-changed', self.on_deps_change)
        column = gtk.TreeViewColumn(_("Title"), renderer, text=0)
        self.g_deps.append_column(column)
        #vbox.pack_start(self.g_deps, False)
        self.g_deps_liststore = gtk.ListStore(gobject.TYPE_STRING,
            gobject.TYPE_STRING)
        self.g_deps.set_model(self.g_deps_liststore)
        ###
        self.g_fileinfo_box = gtk.VBox()
        self.g_content_vbox.pack_start(self.g_fileinfo_box, False)
        box = gtk.HBox()
        box.set_spacing(8)
        self.g_fileinfo_box.pack_start(box, False)
        box.pack_start(gtk.Label(_("Lesson filename:")), False)
        self.g_filename = gtk.Label()
        box.pack_start(self.g_filename, False)
        box = gtk.HBox()
        box.set_spacing(8)
        self.g_fileinfo_box.pack_start(box, False)
        box.pack_start(gtk.Label(_("Exercise module:")), False)
        self.g_exercisemodule = gtk.Label()
        box.pack_start(self.g_exercisemodule, False)
        ###
        self.show_all()
        self.g_lessons_box.hide()
        self.g_deps_box.hide()
        self.g_fileinfo_box.hide()
        self.connect('delete_event', self.on_delete_event)
        self.m_changed = False
    def on_delete_event(self, *v):
        self.close_window()
    def close_window(self, *v):
        if self.tree_has_changes():
            if gu.dialog_yesno(_("Save changes?"), self):
                self.on_save()
        self.m_app.m_ui.g_learning_tree_editor = None
        self.destroy()
    def on_topic_title_edited(self, renderer, path, txt):
        iter = self.g_topics_liststore.iter_nth_child(None, int(path))
        self.g_topics_liststore.set_value(iter, 0, txt)
        self.m_tree.set_topic_title(int(path), txt)
    def on_deps_change(self, treeview_ignore=None):
        self.g_delete_dependency.set_sensitive(True)
    def on_lesson_change(self, treeview_ignore=None):
        self.g_deps_liststore.clear()
        topic_idx = self.g_topics.get_cursor()[0][0]
        lesson_idx = self.g_lessons.get_cursor()[0][0]
        for x in self.m_tree.iterate_deps(topic_idx, lesson_idx):
            self.g_deps_liststore.append((
                self.m_app.lessonfile_manager.get(x, 'title'), x))
        iter = self.g_lessons_liststore.get_iter(self.g_lessons.get_cursor()[0])
        lesson_title = self.g_lessons_liststore.get_value(iter, 0)
        if list(self.m_tree.iterate_deps(topic_idx, lesson_idx)):
            self.g_deps.show()
            self.g_deps_heading.set_text("<b>%s</b>" % _("Dependencies of the lesson '%s'") % lesson_title)
        else:
            self.g_deps.hide()
            self.g_deps_heading.set_text("<b>%s</b>" % _("No dependencies for the lesson '%s'.") % lesson_title)
        self.g_deps_heading.set_use_markup(True)
        self.g_fileinfo_box.show()
        self.g_deps_box.show()
        self.g_delete_lesson.set_sensitive(True)
        self.g_delete_dependency.set_sensitive(False)
        self.g_filename.set_text(self.m_app.lessonfile_manager.get(
            self.m_tree.m_topics[topic_idx]['lessons'][lesson_idx], 'filename'))
        self.g_exercisemodule.set_text(str(self.m_app.lessonfile_manager.get(
            self.m_tree.m_topics[topic_idx]['lessons'][lesson_idx], 'module')))
    def on_topic_change(self, w=None):
        self.g_lessons_liststore.clear()
        i = self.g_topics.get_cursor()[0][0]
        for lesson_id in self.m_tree.iterate_lessons(i):
            self.g_lessons_liststore.append((
                self.m_app.lessonfile_manager.get(lesson_id, 'title'),
                lesson_id))
        self.g_deps_liststore.clear()
        self.g_lessons_box.show()
        if list(self.m_tree.iterate_lessons(i)):
            self.g_lessons_heading.set_text("<b>%s</b>" % _("_Lessons in the topic '%s':") % list(self.m_tree.iterate_topics())[i])
            self.g_lessons.show()
        else:
            self.g_lessons_heading.set_text("<b>%s</b>" % _("No lessons in the topic '%s'.") % list(self.m_tree.iterate_topics())[i])
            self.g_lessons.hide()
        self.g_lessons_heading.set_use_markup(True)
        self.g_lessons_heading.set_use_underline(True)
        self.g_fileinfo_box.hide()
        self.g_deps_box.hide()
        self.g_move_topic_up.set_sensitive(True)
        self.g_move_topic_down.set_sensitive(True)
        self.g_delete_topic.set_sensitive(True)
        self.g_add_lesson.set_sensitive(True)
        self.g_delete_lesson.set_sensitive(False)
    def update_gui(self):
        #FIXME rename?
        self.g_topics_liststore.clear()
        self.g_lessons_liststore.clear()
        self.g_deps_liststore.clear()
        for n in self.m_tree.iterate_topics():
            self.g_topics_liststore.append((n,))
        self.g_move_topic_up.set_sensitive(False)
        self.g_move_topic_down.set_sensitive(False)
        self.g_delete_topic.set_sensitive(False)
        self.set_title(self.m_filename)
    def on_visibility_entry_changed(self, *v):
        try:
            if self.g_visibility.get_text():
                self.m_tree.m_visibility = int(self.g_visibility.get_text())
        except ValueError, e:
            print >> sys.stderr, e
            print >> sys.stderr, "Setting learning tree visibility to 0."
            self.m_tree.m_visibility = 0
    def _do_save(self, filename):
        """
        Save the tree to 'filename', and return True if sucessful.
        Display message describing the problem and return False is failed.
        """
        try:
            self.m_tree.save(filename)
        except IOError, e:
            gu.dialog_ok(_("An error occured while saving the file:\n%s") % e,
                    self)
            return False
        return True
    def on_save(self, *v):
        if self._do_save(self.m_filename):
            self.m_app.m_ui.on_learning_tree_changed()
            self._m_orig_deps = copy.deepcopy(self.m_tree.m_deps)
            self._m_orig_topics = copy.deepcopy(self.m_tree.m_topics)
            self._m_orig_visibility = self.m_tree.m_visibility
    def tree_has_changes(self):
        """
        Return True if the tree has unsaved changes.
        """
        return not (self.m_tree.m_topics == self._m_orig_topics
                and self.m_tree.m_deps == self._m_orig_deps
                and self.m_tree.m_visibility == self._m_orig_visibility)
    def on_save_as(self, *v):
        dialog = gtk.FileChooserDialog(_("Save as..."), self,
                                   gtk.FILE_CHOOSER_ACTION_SAVE,
                                   (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                    gtk.STOCK_SAVE_AS, gtk.RESPONSE_ACCEPT))
        dialog.set_default_response(gtk.RESPONSE_ACCEPT)
        run = dialog.run()
        filename = dialog.get_filename()
        dialog.destroy()
        if run == gtk.RESPONSE_ACCEPT:
            if self._do_save(filename):
                self.m_filename = filename
                self.set_title(os.path.basename(self.m_filename))
    def on_new_topic(self, *v):
        dlg = gtk.Dialog(_("New topic"), self, buttons=
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
            gtk.STOCK_OK, gtk.RESPONSE_OK))
        dlg.set_default_response(gtk.RESPONSE_OK)
        vbox = gtk.VBox()
        dlg.vbox.pack_start(vbox)
        vbox.set_border_width(18)
        self.g_heading = gtk.Label("<b>%s</b>" % _("Create new topic"))
        self.g_heading.set_alignment(0.0, 0.5)
        vbox.pack_start(self.g_heading, False)
        hbox = gtk.HBox()
        hbox.set_spacing(12)
        vbox.pack_start(hbox, False)
        self.g_heading.set_use_markup(True)
        self.g_message = gtk.Label(_("Topic name:"))
        self.g_entry = gtk.Entry()
        self.g_entry.set_activates_default(True)
        hbox.pack_start(self.g_message, False)
        hbox.pack_start(self.g_entry, False)
        dlg.show_all()
        ret = dlg.run()
        dlg.destroy()
        if ret == gtk.RESPONSE_OK:
            n = self.g_entry.get_text()
            if n:
                self.m_tree.new_topic(n)
                self.g_topics_liststore.append((n,))
                iter = self.g_topics_liststore.get_iter_first()
                while self.g_topics_liststore.iter_next(iter):
                    iter = self.g_topics_liststore.iter_next(iter)
                self.g_topics.set_cursor(self.g_topics_liststore.get_path(iter))
    def on_delete_topic(self, *v):
        i = self.g_topics.get_cursor()[0]
        if i:
            i = i[0]
            iter = self.g_topics_liststore.iter_nth_child(None, i)
            if gu.dialog_yesno(_("Delete topic '%s'?") % self.m_tree.m_topics[i]['name'], self):
                self.m_tree.delete_topic(i)
                self.update_gui()
                self.g_add_lesson.set_sensitive(False)
    def on_move_topic_up(self, *v):
        i = self.g_topics.get_cursor()[0]
        if i:
            i = i[0]
            if i > 0:
                self.m_tree.move_topic_up(i)
                itera = self.g_topics_liststore.iter_nth_child(None, i)
                iterb = self.g_topics_liststore.iter_nth_child(None, i - 1)
                self.g_topics_liststore.swap(itera, iterb)
                self.g_topics.scroll_to_cell(
                    self.g_topics_liststore.get_string_from_iter(itera))
    def on_move_topic_down(self, *v):
        i = self.g_topics.get_cursor()[0]
        if i:
            i = i[0]
            itera = self.g_topics_liststore.iter_nth_child(None, i)
            iterb = self.g_topics_liststore.iter_next(itera)
            if iterb:
                self.g_topics_liststore.swap(itera, iterb)
                self.g_topics.scroll_to_cell(
                    self.g_topics_liststore.get_string_from_iter(itera))
                self.m_tree.move_topic_down(i)
    def on_delete_dep(self, *v):
        i = self.g_deps.get_cursor()[0]
        if i:
            i = i[0]
            topic_idx = self.g_topics.get_cursor()[0][0]
            iter = self.g_deps_liststore.iter_nth_child(None, i)
            dep_id = self.g_deps_liststore.get(iter, 1)
            ###
            lesson_idx = self.g_lessons.get_cursor()[0][0]
            self.m_tree.delete_dependency(topic_idx, lesson_idx, i)
            self.on_topic_change()
            self.g_lessons.set_cursor("%i" % lesson_idx)

    def on_add_dep(self, *v):
        i = self.g_lessons.get_cursor()[0]
        if i:
            i = i[0]
            topic_idx = self.g_topics.get_cursor()[0][0]
            dlg = DepsLessonFileDialog(self.m_app, self.m_tree, topic_idx, i)
            ret = dlg.run()
            if ret == gtk.RESPONSE_OK:
                lesson_id = dlg.get_selected_lesson_id()
                if lesson_id:
                    iter = self.g_lessons_liststore.iter_nth_child(None, i)
                    added_to_id = self.g_lessons_liststore.get(iter, 1)
                    self.m_tree.add_dependency(topic_idx, int(i), lesson_id)
                    self.on_topic_change()
                    iter = self.g_lessons_liststore.get_iter_first()
                    while iter:
                        if added_to_id == self.g_lessons_liststore.get(iter, 1):
                            self.g_lessons.set_cursor(self.g_lessons_liststore.get_string_from_iter(iter))
                            break
                        iter = self.g_lessons_liststore.iter_next(iter)
            dlg.destroy()
    def on_new_lesson(self, *v):
        topic_idx = self.g_topics.get_cursor()[0][0]
        dlg = SelectLessonFileDialog(self.m_app, self.m_tree, topic_idx)
        dlg.run()
        lesson_id = dlg.get_selected_lesson_id()
        if lesson_id:
            self.m_tree.add_lesson(topic_idx, lesson_id)
            self.on_topic_change()
        dlg.destroy()
    def on_delete_lesson(self, *v):
        topic_idx = self.g_topics.get_cursor()[0][0]
        i = self.g_lessons.get_cursor()[0]
        if i:
            i = i[0]
            iter = self.g_lessons_liststore.iter_nth_child(None, i)
            del_id = self.m_tree.m_topics[topic_idx]['lessons'][i]
            do_del = True
            if self.m_tree.get_use_count(del_id) == 1:
                if self.m_tree.get_dep_use_count(del_id) > 0:
                    do_del = gu.dialog_yesno(_("Lessons depend on this lesson. Delete anyway, and update the dependency list for lessons that depend on this lesson?"), self)
                    if do_del:
                        self.m_tree.remove_all_deps_of(del_id)
            if do_del:
                self.m_tree.delete_lesson(topic_idx, i)
                self.on_topic_change()
    def on_move_lesson_up(self, *v):
        topic_idx = self.g_topics.get_cursor()[0][0]
        i = self.g_lessons.get_cursor()[0]
        if i:
            i = i[0]
            if i > 0 and self.m_tree.move_lesson_up(topic_idx, i):
                itera = self.g_lessons_liststore.iter_nth_child(None, i)
                iterb = self.g_lessons_liststore.iter_nth_child(None, i-1)
                self.g_lessons_liststore.swap(itera, iterb)
                self.g_lessons.scroll_to_cell(
                    self.g_lessons_liststore.get_string_from_iter(itera))
    def on_move_lesson_down(self, *v):
        topic_idx = self.g_topics.get_cursor()[0][0]
        i = self.g_lessons.get_cursor()[0]
        if i:
            i = i[0]
            if i + 1 < len(self.m_tree.m_topics[topic_idx]['lessons']) and \
                    self.m_tree.move_lesson_down(topic_idx, i):
                itera = self.g_lessons_liststore.iter_nth_child(None, i)
                iterb = self.g_lessons_liststore.iter_next(itera)
                self.g_lessons_liststore.swap(itera, iterb)
                self.g_lessons.scroll_to_cell(
                    self.g_lessons_liststore.get_string_from_iter(itera))
    def load_file(self, filename):
        self.m_tree = LearningTree(self.m_app.lessonfile_manager)
        # We use _m_orig_tree just to see if things has changed.
        self.m_filename = filename
        self.m_tree.load(filename)
        self.g_visibility.set_text(str(self.m_tree.m_visibility))
        self._m_orig_deps = copy.deepcopy(self.m_tree.m_deps)
        self._m_orig_topics = copy.deepcopy(self.m_tree.m_topics)
        self._m_orig_visibility = self.m_tree.m_visibility
        self.update_gui()
        #FIXME uncomment when complete
        #if self.g_topics_liststore.get_iter_first():
        #    self.g_topics.set_cursor("0", None)

