# GNU Solfege - free ear training software
# Copyright (C) 2009  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 3 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, see <http://www.gnu.org/licenses/>.


from __future__ import absolute_import

import logging
import os
import StringIO
import sys
import time
import weakref

import gobject
import gtk

if __name__ == '__main__':
    from solfege import i18n
    i18n.setup(".", "C")
    import solfege.statistics
    solfege.db = solfege.statistics.DB()

import solfege
from solfege import cfg
from solfege import filesystem
from solfege import gu
from solfege import learningtree as pd
from solfege import lessonfilegui

class LessonFileDialogCommon(gtk.Dialog):
    def __init__(self, model):
        gtk.Dialog.__init__(self, _("Select lesson file"), self, buttons=
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,))
        self.m_model = model
        lessonfilegui.handle_lesson_id_crash(solfege.app.lessonfile_manager)
        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)
        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 s in self.m_model.iterate_topics_for_id(
                self.g_liststore.get(iter, 2)[0]):
            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, model):
        LessonFileDialogCommon.__init__(self, model)
        hbox = gtk.HBox()
        hbox.set_spacing(gu.hig.SPACE_SMALL)
        hbox.show()
        self.vbox.pack_start(hbox, False)
        self.vbox.reorder_child(hbox, 0)
        g_show_used = gu.nCheckButton('SelectLessonFileDialog',
            'show_used_files', _("_Show files used in other topics"),
            False, self.update_filelist)
        hbox.pack_start(g_show_used, False)
        b = gtk.Button(_("_Search for new lesson files"))
        b.connect('clicked', self.search_for_lesson_files)
        b.show()
        hbox.pack_start(b, False)
        self.g_progress = gtk.ProgressBar()
        self.g_progress.set_text(_("Parsing files"))
        hbox.pack_start(self.g_progress, False)
    def search_for_lesson_files(self, btn):
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        self.g_progress.show()
        for percent in solfege.app.nonidle_reparse():
            self.g_progress.set_fraction(percent if percent <= 1.0 else 1.0)
            while gtk.events_pending():
                gtk.main_iteration()
        self.g_progress.hide()
        self.update_filelist()
        self.window.set_cursor(None)
    def update_filelist(self, w=None):
        self.g_liststore.clear()
        use_count = self.m_model.get_use_dict()
        if cfg.get_bool('SelectLessonFileDialog/show_used_files'):
            lesson_id_list = list(solfege.app.lessonfile_manager.iterate_lesson_ids())
        else:
            lesson_id_list = [i for i in list(solfege.app.lessonfile_manager.iterate_lesson_ids()) if use_count.get(i, 0) == 0]
        for lesson_id in lesson_id_list:
            d = solfege.app.lessonfile_manager.m_uiddb[lesson_id]
            self.g_liststore.append((d['header']['title'], d['filename'], lesson_id, use_count.get(lesson_id, 0),
                solfege.app.lessonfile_manager.get(lesson_id, 'module')))


def editor_of(obj):
    """
    Return the toplevel page, the one that is a Editor object.
    """
    p = obj
    while not isinstance(p, Editor):
        p = p.m_parent
    return p

def parent_page(obj):
    """
    Return the parent page of obj. Return None if this is the toplevel page.
    """
    p = obj
    while True:
        p = p.m_parent
        if isinstance(p, Page):
            return p
        if p is None:
            return None
    
class Section(gtk.VBox):
    """
    A section consists of a heading and a list of links.
    self.g_link_box is a vbox that contains the links.
    """
    def __init__(self, model, parent):
        gtk.VBox.__init__(self)
        self.m_model = model
        self.m_parent = parent
        assert isinstance(model, pd.LinkList)
        hbox = gtk.HBox()
        hbox.set_spacing(6)
        self.pack_start(hbox, False)
        # This is displayed and used when we edit the heading
        self.g_heading_entry = gtk.Entry()
        self.g_heading_entry.set_no_show_all(True)
        hbox.pack_start(self.g_heading_entry)
        self.g_heading = gtk.Label()
        self.g_heading.set_alignment(0.0, 0.5)
        # FIXME escape m_name
        self.g_heading.set_markup("<b>%s</b>" % model.m_name)
        hbox.pack_start(self.g_heading, False)
        #
        button_hbox = gtk.HBox()
        button_hbox.set_spacing(0)
        hbox.pack_start(button_hbox, False)
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
        edit_btn = gtk.Button()
        edit_btn.add(im)
        edit_btn.connect('clicked', self.on_edit_heading)
        button_hbox.pack_start(edit_btn, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
        edit_btn = gtk.Button()
        edit_btn.add(im)
        edit_btn.connect('button-release-event', self.on_add)
        button_hbox.pack_start(edit_btn, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_CUT, gtk.ICON_SIZE_MENU)
        b = gtk.Button()
        b.add(im)
        b.connect('clicked', self.on_cut)
        button_hbox.pack_start(b, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_PASTE, gtk.ICON_SIZE_MENU)
        b = gtk.Button()
        b.add(im)
        b.connect('clicked', self.on_paste, -1)
        Editor.clipboard.register_paste_button(b, (pd.LinkList, pd.Page, unicode))
        button_hbox.pack_start(b, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
        self.g_move_down_btn = gtk.Button()
        self.g_move_down_btn.add(im)
        self.g_move_down_btn.connect('clicked', self.on_move_down)
        button_hbox.pack_start(self.g_move_down_btn, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
        self.g_move_up_btn = gtk.Button()
        self.g_move_up_btn.add(im)
        self.g_move_up_btn.connect('clicked', self.on_move_up)
        button_hbox.pack_start(self.g_move_up_btn, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_MENU)
        self.g_move_left_btn = gtk.Button()
        self.g_move_left_btn.add(im)
        self.g_move_left_btn.connect('clicked',
            parent.m_parent.on_move_section_left, self)
        button_hbox.pack_start(self.g_move_left_btn, False)
        #
        im = gtk.Image()
        im.set_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_MENU)
        self.g_move_right_btn = gtk.Button()
        self.g_move_right_btn.add(im)
        self.g_move_right_btn.connect('clicked',
            parent.m_parent.on_move_section_right, self)
        button_hbox.pack_start(self.g_move_right_btn, False)
        #
        self.g_link_box = gtk.VBox()
        self.pack_start(self.g_link_box, False)
        for link in self.m_model:
            self.g_link_box.pack_start(self.create_linkrow(link))
        # The button to click to add a new link
        hbox = gtk.HBox()
        self.pack_start(hbox)
    def on_move_down(self, btn):
        self.m_parent.move_section_down(self)
    def on_move_up(self, btn):
        self.m_parent.move_section_up(self)
    def on_edit_heading(self, btn):
        self.g_heading_entry.set_text(self.m_model.m_name)
        self.g_heading_entry.show()
        self.g_heading.hide()
        self.g_heading_entry.grab_focus()
        def finish_edit(entry):
            self.g_heading_entry.disconnect(sid)
            self.g_heading_entry.disconnect(keyup_id)
            self.g_heading_entry.disconnect(keydown_sid)
            self.m_model.m_name = entry.get_text()
            self.g_heading.set_markup(u"<b>%s</b>" % entry.get_text())
            self.g_heading_entry.hide()
            self.g_heading.show()
        sid = self.g_heading_entry.connect('activate', finish_edit)
        def keydown(entry, event):
            if event.keyval == gtk.keysyms.Tab:
                finish_edit(entry)
        keydown_sid = self.g_heading_entry.connect('key-press-event', keydown)
        def keyup(entry, event):
            if event.keyval == gtk.keysyms.Escape:
                self.g_heading_entry.disconnect(sid)
                self.g_heading_entry.disconnect(keyup_id)
                self.g_heading_entry.hide()
                self.g_heading.show()
                return True
        keyup_id = self.g_heading_entry.connect('key-release-event', keyup)
    def on_add(self, btn, event):
        menu = gtk.Menu()
        item = gtk.MenuItem(_("Add link to new page"))
        item.connect('activate', self.on_add_link_to_new_page)
        menu.append(item)
        item = gtk.MenuItem(_("Add link to exercise"))
        item.connect('activate', self.on_add_link)
        menu.append(item)
        menu.show_all()
        menu.popup(None, None, None, event.button, event.time)
    def on_add_link(self, btn):
        dlg = SelectLessonFileDialog(editor_of(self).m_model)
        ret = dlg.run()
        if ret in (gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT, gtk.RESPONSE_CANCEL):
            pass
        else:
            assert ret == gtk.RESPONSE_OK
            self.m_model.append(dlg.get_selected_lesson_id())
            self.g_link_box.pack_start(
                self.create_linkrow(dlg.get_selected_lesson_id()), False)
        dlg.destroy()
    def on_add_link_to_new_page(self, menuitem):
        page = pd.Page(_("Untitled%s") % "", [pd.Column()])
        self.m_model.append(page)
        self.g_link_box.pack_start(self.create_linkrow(page))
    def create_linkrow(self, link_this):
        hbox = gtk.HBox()
        def ff(btn, page):
            if id(page) in editor_of(self).m_page_mapping:
                editor_of(self).show_page_id(id(page))
            else:
                if not page[0]:
                    page[0].append(pd.LinkList(link_this.m_name))
                p = Page(page, parent_page(self))
                p.show()
                editor_of(self).add_page(p)
        if isinstance(link_this, pd.Page):
            linkbutton = gu.ClickableLabel(link_this.m_name)
            linkbutton.connect('clicked', ff, link_this)
        else:
            linkbutton = gu.ClickableLabel(solfege.db.get_field(link_this, 'title'))
        hbox.pack_start(linkbutton)
        linkbutton.connect('button-press-event', self.on_right_click_row, link_this)
        hbox.show_all()
        return hbox
    def on_right_click_row(self, button, event, linked):
        idx = self.m_model.index(linked)
        if event.button == 3:
            m = gtk.Menu()
            item = gtk.ImageMenuItem(gtk.STOCK_DELETE)
            item.connect('activate', self.on_delete_link, linked)
            m.append(item)
            item = gtk.ImageMenuItem(gtk.STOCK_CUT)
            item.connect('activate', self.on_cut_link, idx)
            m.append(item)
            item = gtk.ImageMenuItem(gtk.STOCK_PASTE)
            item.set_sensitive(bool(Editor.clipboard))
            item.connect('activate', self.on_paste, idx)
            m.append(item)
            item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
            item.connect('activate', self.on_edit_linktext, linked)
            item.set_sensitive(bool(not isinstance(linked, basestring)))
            m.append(item)
            m.show_all()
            m.popup(None, None, None, event.button, event.time)
            return True
    def on_delete_link(self, menuitem, linked):
        idx = self.m_model.index(linked)
        if id(linked) in editor_of(self).m_page_mapping:
            editor_of(self).destroy_window(id(linked))
        self.g_link_box.get_children()[idx].destroy()
        del self.m_model[idx]
    def on_edit_linktext(self, menuitem, linked):
        idx = self.m_model.index(linked)
        # row is the hbox containing the linkbutton
        row = self.g_link_box.get_children()[idx]
        linkbutton = row.get_children()[0]
        entry = gtk.Entry()
        entry.set_text(linkbutton.get_label())
        row.pack_start(entry)
        linkbutton.hide()
        entry.show()
        entry.grab_focus()
        def finish_edit(entry):
            linkbutton.set_label(entry.get_text().decode("utf-8"))
            linkbutton.get_children()[0].set_alignment(0.0, 0.5)
            linkbutton.show()
            self.m_model[idx].m_name = entry.get_text().decode("utf-8")
            entry.destroy()
        sid = entry.connect('activate', finish_edit)
        def keydown(entry, event):
            if event.keyval == gtk.keysyms.Tab:
                finish_edit(entry)
        entry.connect('key-press-event', keydown)
        def keyup(entry, event):
            if event.keyval == gtk.keysyms.Escape:
                linkbutton.show()
                entry.disconnect(sid)
                entry.destroy()
                return True
        entry.connect('key-release-event', keyup)
    def on_cut(self, btn):
        self.m_parent.cut_section(self)
    def on_cut_link(self, menuitem, idx):
        Editor.clipboard.append(self.m_model[idx])
        del self.m_model[idx]
        self.g_link_box.get_children()[idx].destroy()
    def on_paste(self, btn, idx):
        assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
        pobj = Editor.clipboard.pop()
        if isinstance(pobj, pd.LinkList):
            mobj = pd.Page(pobj.m_name, [pd.Column(pobj)])
        else:
            mobj = pobj
        if idx == -1:
            self.m_model.append(mobj)
            self.g_link_box.pack_start(self.create_linkrow(mobj))
        else:
            self.m_model.insert(idx, mobj)
            row = self.create_linkrow(mobj)
            self.g_link_box.pack_start(row)
            self.g_link_box.reorder_child(row, idx)


class Column(gtk.VBox):
    def __init__(self, model, parent):
        gtk.VBox.__init__(self)
        self.set_spacing(gu.hig.SPACE_MEDIUM)
        self.m_model = model
        self.m_parent = parent
        assert isinstance(model, pd.Column)
        self.g_section_box = gtk.VBox()
        self.g_section_box.set_spacing(gu.hig.SPACE_MEDIUM)
        self.pack_start(self.g_section_box, False)
        for section in model:
            assert isinstance(section, pd.LinkList)
            gui_section = Section(section, self)
            self.g_section_box.pack_start(gui_section, False)
        hbox = gtk.HBox()
        self.pack_start(hbox, False)
        b = gtk.Button(_("Add section"))
        hbox.pack_start(b, False)
        b.connect('clicked', self.on_add_section)
        b = gtk.Button(stock=gtk.STOCK_PASTE)
        b.connect('clicked', self.on_paste)
        Editor.clipboard.register_paste_button(b, pd.LinkList)
        hbox.pack_start(b, False)
    def __del__(self):
        logging.debug("Column.__del__")
    def cut_section(self, section):
        idx = self.g_section_box.get_children().index(section)
        Editor.clipboard.append(self.m_model[idx])
        del self.m_model[idx]
        self.g_section_box.get_children()[idx].destroy()
    def on_add_section(self, btn):
        # We write "Untitled%s" % "" instead of just "Untitled" here
        # since "Untitled%s" is already translated in many languages.
        section = pd.LinkList(_("Untitled%s" % ""))
        self.m_model.append(section)
        gui_section = Section(section, self)
        self.g_section_box.pack_start(gui_section, False)
        gui_section.show_all()
    def move_section_down(self, section):
        idx = self.g_section_box.get_children().index(section)
        if idx < len(self.g_section_box.get_children()) - 1:
            self.g_section_box.reorder_child(section, idx + 1)
            self.m_model[idx], self.m_model[idx + 1] \
                    = self.m_model[idx + 1], self.m_model[idx]
            self.m_parent.update_buttons()
    def move_section_up(self, section):
        idx = self.g_section_box.get_children().index(section)
        if idx > 0:
            self.g_section_box.reorder_child(section, idx - 1)
            self.m_model[idx], self.m_model[idx - 1] \
                    = self.m_model[idx - 1], self.m_model[idx]
            self.m_parent.update_buttons()
    def on_paste(self, widget):
        """
        Paste the clipboard as a new section to this column.
        """
        assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
        assert isinstance(Editor.clipboard[-1], pd.LinkList)
        pobj = Editor.clipboard.pop()
        self.m_model.append(pobj)
        sect = Section(pobj, self)
        sect.show_all()
        self.g_section_box.pack_start(sect, False)


class Page(gtk.VBox):
    def __init__(self, model, parent):
        gtk.VBox.__init__(self)
        self.m_model = model
        self.m_parent = parent
        sc = gtk.ScrolledWindow()
        sc.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.pack_start(sc)
        self.g_column_box = gtk.HBox()
        self.g_column_box.set_spacing(gu.hig.SPACE_LARGE)
        self.g_column_box.set_border_width(gu.hig.SPACE_SMALL)
        # We pack column into this box
        sc.add_with_viewport(self.g_column_box)
        self.show_all()
        if model:
            self.update_from_model()
    def __del__(self):
        logging.debug("Page.__del__:", self.m_model.m_name)
    def on_add_column(self, *btn):
        column = pd.Column()
        self.m_model.append(column)
        gcol = Column(column, self)
        gcol.show_all()
        self.g_column_box.pack_start(gcol)
    def on_move_section_left(self, button, section):
        column_idx = self.g_column_box.get_children().index(section.m_parent)
        section_idx = section.m_parent.g_section_box.get_children().index(section)
        if column_idx > 0:
            to_column = self.g_column_box.get_children()[column_idx - 1]
            section.reparent(to_column.g_section_box)
            section.m_parent = to_column
            to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
            self.m_model[column_idx - 1].append(self.m_model[column_idx][section_idx])
            del self.m_model[column_idx][section_idx]
            # Remove the right-most column if we moved the
            # last section out of it.
            if not self.g_column_box.get_children()[-1].g_section_box.get_children():
                assert len(self.m_model[-1]) == 0
                del self.m_model[-1]
                self.g_column_box.get_children()[-1].destroy()
            self.update_buttons()
    def on_move_section_right(self, button, section):
        # the column we move from
        column_idx = self.g_column_box.get_children().index(section.m_parent)
        section_idx = section.m_parent.g_section_box.get_children().index(section)
        if column_idx == len(self.g_column_box.get_children()) - 1:
            self.on_add_column()
        to_column = self.g_column_box.get_children()[column_idx + 1]
        section.reparent(to_column.g_section_box)
        section.m_parent = to_column
        to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
        to_section_idx = len(self.m_model[column_idx + 1])
        self.m_model[column_idx + 1].append(self.m_model[column_idx][section_idx])
        del self.m_model[column_idx][section_idx]
        self.update_buttons()
    def update_from_model(self):
        for child in self.g_column_box.get_children():
            child.destroy()
        for column in self.m_model:
            self.g_column_box.pack_start(Column(column, self))
        self.g_column_box.show_all()
        self.update_buttons()
    def update_buttons(self):
        num_cols = len(self.g_column_box.get_children())
        for col_idx, column in enumerate(self.g_column_box.get_children()):
            num_sects = len(column.g_section_box.get_children())
            for sect_idx, section in enumerate(column.g_section_box.get_children()):
                section.g_move_up_btn.set_sensitive(sect_idx != 0)
                section.g_move_down_btn.set_sensitive(sect_idx != num_sects -1)
                section.g_move_left_btn.set_sensitive(col_idx != 0)
                if [col for col in self.g_column_box.get_children() if not col.g_section_box.get_children()] and col_idx == num_cols - 1:
                    section.g_move_right_btn.set_sensitive(False)
                else:
                    section.g_move_right_btn.set_sensitive(True)


class Clipboard(list):
    def __init__(self, v=[]):
        list.__init__(v)
        self.m_paste_buttons = []
    def pop(self, i=-1):
        ret = list.pop(self, i)
        self.update_buttons()
        return ret
    def append(self, obj):
        list.append(self, obj)
        self.update_buttons()
    def register_paste_button(self, button, accepts_types):
        self.m_paste_buttons.append((button, accepts_types))
    def update_buttons(self):
        for button, types in self.m_paste_buttons:
            button.set_sensitive(bool(self) and isinstance(self[-1], types))


class Editor(gtk.Window, gu.EditorDialogBase):
    savedir = os.path.join(filesystem.user_data(), 'learningtrees')
    # The clipboard will be shared between all Editor instances
    clipboard = Clipboard()
    def __init__(self, filename=None):
        gtk.Window.__init__(self)
        logging.debug("fpeditor.Editor.__init__(%s)" % filename)
        gu.EditorDialogBase.__init__(self, filename)
        self.set_default_size(800, 600)
        self.g_main_box = gtk.VBox()
        self.add(self.g_main_box)
        self.g_actiongroup.add_actions([
            ('GoBack', gtk.STOCK_GO_BACK, None, None, None, self.go_back),
        ])
        self.setup_toolbar()
        # This dict maps the windows created for all pages belonging to
        # the file.
        self.m_page_mapping = {}
        self.m_model = None
        if filename:
            self.load_file(filename)
        else:
            self.m_model = pd.Page(_("Untitled%s") % self.m_instance_number,
                    pd.Column())
            self.set_not_modified()
        self.m_page_mapping[id(self.m_model)] = Page(self.m_model, self)
        self.g_visible_page = self.m_page_mapping[id(self.m_model)]
        self.g_main_box.pack_start(self.m_page_mapping[id(self.m_model)])
        self.clipboard.update_buttons()
        self.show_all()
    def __del__(self):
        logging.debug("fpeditor.Editor.__del__, filename=" % self.m_filename)
    def add_page(self, page):
        """
        Add and show the page.
        """
        editor_of(self).m_page_mapping[id(page.m_model)] = page
        self.g_main_box.pack_start(page)
        self.show_page(page)
    def show_page_id(self, page_id):
        self.show_page(self.m_page_mapping[page_id])
    def show_page(self, page):
        """
        Hide the currently visible page, and show PAGE instead.
        """
        self.g_visible_page.hide()
        self.g_visible_page = page
        page.show()
        self.g_ui_manager.get_widget("/Toolbar/GoBack").set_sensitive(
            not isinstance(self.g_visible_page.m_parent, Editor))
    def go_back(self, *action):
        self.show_page(self.g_visible_page.m_parent)
    def setup_toolbar(self):
        self.g_ui_manager.insert_action_group(self.g_actiongroup, 0)
        uixml = """
        <ui>
         <toolbar name='Toolbar'>
          <toolitem action='GoBack'/>
          <toolitem action='New'/>
          <toolitem action='Open'/>
          <toolitem action='Save'/>
          <toolitem action='SaveAs'/>
          <toolitem action='Close'/>
          <toolitem action='Help'/>
         </toolbar>
         <accelerator action='Close'/>
         <accelerator action='New'/>
         <accelerator action='Open'/>
         <accelerator action='Save'/>
        </ui>
        """
        self.g_ui_manager.add_ui_from_string(uixml)
        toolbar = self.g_ui_manager.get_widget("/Toolbar")
        self.g_main_box.pack_start(toolbar, False)
        self.g_main_box.reorder_child(toolbar, 0)
        self.g_ui_manager.get_widget("/Toolbar").set_style(gtk.TOOLBAR_BOTH)
    def destroy_window(self, window_id):
        """
        Destroy the window with the id 'windowid' and all subwindows.
        """
        def do_del(wid):
            for key in [k for k in self.m_page_mapping.keys()
                    if id(parent_page(self.m_page_mapping[k]).m_model) == wid]:
                do_del(key)
            editor_of(self).m_page_mapping[wid].destroy()
            del editor_of(self).m_page_mapping[wid]
        do_del(window_id)
    @staticmethod
    def edit_file(fn):
        if fn in Editor.instance_dict:
            Editor.instance_dict[fn].present()
        else:
            try:
                win = Editor(fn)
            except IOError, e:
                gu.dialog_ok(_("Loading file '%(filename)s' failed: %(msg)s") %
                        {'filename': fn, 'msg': str(e).decode('utf8', 'replace')})
            win.show()
    @staticmethod
    def tuple_to_filename(place, filename):
        if place == 'solfege':
            return os.path.join("learningtrees", filename)
        else:
            assert place == 'user'
            return os.path.join(filesystem.user_data(), 'learningtrees', filename)
    def load_file(self, filename):
        """
        Load a file into a empty, newly created Editor object.
        """
        assert self.m_model == None
        self.m_model = pd.load_tree(filename, C_locale=True)
        self.m_filename = filename
        # 
        if not os.path.isabs(filename):
            if not os.access(filename, os.W_OK):
                m = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
                    gtk.BUTTONS_CLOSE, _("The default learning tree is write protected in your install. This is normal. If you want to edit a learning tree, you have to select one of the trees stored in .solfege/learningtrees in your home directory."))
                m.run()
                m.destroy()
        self.set_not_modified()
        self.set_title(self.m_filename)
    def set_not_modified(self):
        """
        Store the current state of the data in self.m_orig_dump so that
        is_modified() will return False until we make new changes.
        """
        io = StringIO.StringIO()
        self.m_model.dump(io)
        self.m_orig_dump = io.getvalue()
    def is_modified(self):
        """
        Return True if the data has changed since the last call to
        set_not_modified()
        """
        io = StringIO.StringIO()
        self.m_model.dump(io)
        s = io.getvalue()
        return s != self.m_orig_dump
    @property
    def m_changed(self):
        return self.is_modified()
    def save(self, w=None):
        assert self.m_filename
        f = open(self.m_filename, 'w')
        self.m_model.dump(f)
        f.close()
        self.set_not_modified()
        # We do test for solfege.win since it is not available during testing
        if getattr(solfege, 'win', None):
            solfege.win.on_learning_tree_changed()
    def on_show_help(self, *w):
        return

if __name__ == '__main__':
    gtk.link_button_set_uri_hook(lambda a, b: None)
    e = Editor()
    e.load_file("learningtrees/learningtree.txt")
    gtk.main()
