# vim: set fileencoding=iso-8859-1 :
# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008  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/>.

import logging
import logging.handlers
import optionparser

opt_parser = optionparser.SolfegeOptionParser()
options, args = opt_parser.parse_args()

if options.debug:
    handler = logging.StreamHandler()
    logging.getLogger().addHandler(handler)
    logging.getLogger().setLevel(logging.DEBUG)
else:
    handler = logging.handlers.MemoryHandler(1)
    logging.getLogger().addHandler(handler)


import webbrowser
import textwrap
# We move x-www-browser to the end of the list because on my
# debian etch system, the browser does will freeze solfege until
# I close the browser window.
try:
    i = webbrowser._tryorder.index("x-www-browser")
    webbrowser._tryorder.append(webbrowser._tryorder[i])
    del webbrowser._tryorder[i]
except ValueError:
    pass

import sys
import traceback
import os
import time
import urllib
import shutil

import winlang

import runtime

import buildinfo

solfege_copyright = u"Copyright © 1999-2008 Tom Cato Amundsen <tca@gnu.org>, and others."

warranty = """
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/>.
"""

import osutils
import app

if options.version:
    if buildinfo.is_release():
        rev_info = ""
    else:
        rev_info = u"\n    " + u"\n    ".join(buildinfo.get_bzr_revision_info_list())
    print (u"""GNU Solfege %s%s
This is free software. It is covered by the GNU General Public License,
and you are welcome to change it and/or distribute copies of it under
certain conditions. Invoke as `solfege --warranty` for more information.

%s
        """ % (buildinfo.VERSION_STRING,
        rev_info,
        solfege_copyright)).encode(sys.getfilesystemencoding(), 'replace')
    sys.exit()

if options.warranty:
    print "GNU Solfege %s" % buildinfo.VERSION_STRING
    print solfege_copyright.encode(sys.getfilesystemencoding(), 'replace')
    print warranty
    sys.exit()

# silent, GNOME, be silent!
#sys.argv.append('--disable-sound')
runtime.init(options)

import gtk
# We need to set a uri hook that does nothing, since ubuntu 9.04 rc give
# GConf errors and GtkWarnings without it.
gtk.link_button_set_uri_hook(lambda a, b: None)

import utils
import i18n

class SplashWin(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
        self.set_position(gtk.WIN_POS_CENTER)
        self.set_resizable(True)
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_OUT)
        self.add(frame)
        vbox = gtk.VBox()
        vbox.set_border_width(20)
        frame.add(vbox)
        l = gtk.Label(_("Starting GNU Solfege %s") % buildinfo.VERSION_STRING)
        l.set_name("Heading1")
        vbox.pack_start(l)
        l = gtk.Label("http://www.solfege.org")
        vbox.pack_start(l)
        self.g_infolabel = gtk.Label('')
        vbox.pack_start(self.g_infolabel)
        self.show_all()
    def show_progress(self, txt):
        self.g_infolabel.set_text(txt)
        while gtk.events_pending():
            gtk.main_iteration(0)

if not options.no_splash:
    splash_win = SplashWin()
    time.sleep(0.1)
    gtk.gdk.flush()
    while gtk.events_pending():
        gtk.main_iteration(0)
else:
    splash_win = None

if splash_win:
    splash_win.show_progress("importing application modules")

import tracebackwindow
# redirect error messages to a window that will popup if
# something bad happens.

sys.stderr = tracebackwindow.TracebackWindow(options.show_gtk_warnings)

from configwindow import ConfigWindow
import mpd
import mpd.musicdisplayer
import gu
import cfg
import stock
import lessonfile
import lessonfilegui
import abstract

import chord
import idproperty
import chordvoicing
import harmonicinterval
import melodicinterval
import singinterval
import nameinterval

import idbyname
import dictation
import twelvetone
import idtone
import compareintervals
import singchord
import rhythm
import identifybpm
import harmonicprogressiondictation
import singanswer
import elembuilder
import rhythmtapping
import rhythmtapping2

import learning_tree_editor
from trainingsetdlg import TrainingSetDialog
from practisesheetdlg import PractiseSheetDialog
from helpbrowser import HelpBrowser, DocViewer
import filesystem
import statistics

class MusicViewerWindow(gtk.Dialog):
    def __init__(self, app):
        gtk.Dialog.__init__(self)
        self.m_app = app
        self.set_default_size(500, 300)
        self.g_music_displayer = mpd.musicdisplayer.MusicDisplayer()
        self.vbox.pack_start(self.g_music_displayer)
        b = gu.bButton(self.action_area, _("Close"), self.m_app.close_musicviewer)
        b.grab_focus()
        self.connect('destroy', self.m_app.close_musicviewer)
        self.show_all()
    def display_music(self, music):
        fontsize = cfg.get_int('config/feta_font_size=20')
        self.g_music_displayer.display(music, fontsize)

class SelectWinBase(gtk.ScrolledWindow):
    def __init__(self, app):
        gtk.ScrolledWindow.__init__(self)
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self._on_focus_in_blocked = False
        self.m_app = app
        self.set_size_request(500, 300)
        self.m_linkbuttons = []
        self.g_searchentry = None
    def on_focus_in(self, w, event):
        """
        Set the vadjustment so that the window will scroll to make the button
        that has the focus visible in the scrolled window.
        """
        if self._on_focus_in_blocked:
            return
        a = w.get_allocation()
        adj = self.get_vadjustment()
        if a.y + a.height > adj.value + adj.page_size:
            if w.get_data('last'):
                adj.value = adj.upper - adj.page_size
            else:
                adj.value = a.y - adj.page_size + a.height
        elif a.y < adj.value:
            # If the widget is the first row of the first category, then...
            if w.get_data('first'):
                adj.value = 0.0
            else:
                adj.value = a.y
    def on_key_press_event(self, mainwin, event):
        if event.type == gtk.gdk.KEY_PRESS and event.state == 0:
            adj = self.get_vadjustment()
            if event.keyval in (gtk.keysyms.End, gtk.keysyms.KP_End):
                self.m_linkbuttons[-1].grab_focus()
                return True
            elif event.keyval in (gtk.keysyms.Home, gtk.keysyms.KP_Home):
                if self.g_searchentry:
                    self.get_vadjustment().value = 0.0
                    self.g_searchentry.grab_focus()
                else:
                    self.m_linkbuttons[0].grab_focus()
                return True
            if event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.KP_Page_Down):
                # First we find the button that now has the keyboard focus
                for idx, btn in enumerate(self.m_linkbuttons):
                    if btn.is_focus():
                        break
                # Then we find a button that is approximately adj.page_increment
                # down and grabs that buttons focus.
                for to_btn in self.m_linkbuttons[idx:]:
                    if to_btn.get_allocation().y - adj.value > adj.page_size:
                        self._on_focus_in_blocked = True
                        to_btn.grab_focus()
                        self._on_focus_in_blocked = False
                        return True
                self.m_linkbuttons[-1].grab_focus()
                return True
            if event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.KP_Page_Up):
                for idx, btn in enumerate(self.m_linkbuttons):
                    if btn.is_focus():
                        break
                for to_btn in self.m_linkbuttons:
                    if to_btn.get_allocation().y > adj.value - adj.page_increment:
                        self._on_focus_in_blocked = True
                        to_btn.grab_focus()
                        self._on_focus_in_blocked = False
                        return True


class FrontPage(SelectWinBase):
    def __init__(self, app):
        SelectWinBase.__init__(self, app)
        main_vbox = gtk.VBox()
        self.add_with_viewport(main_vbox)
        hbox = gtk.HBox()
        hbox.set_border_width(12)
        main_vbox.pack_start(hbox, False)
        self.g_searchentry = gtk.Entry()
        hbox.pack_start(self.g_searchentry, True)
        self.g_searchentry.connect('activate', self.on_search)
        gu.bButton(hbox, _("Search"), callback=self.on_search, expand=False)
        hbox = gu.bHBox(main_vbox)
        for column in app.m_frontpage_data['columns']:
            first = True
            vbox = gtk.VBox()
            hbox.pack_start(vbox, False)
            for sect_id, section in enumerate(column):
                category = gu.hig_dlg_vbox()
                vbox.pack_start(category, False)
                b1, b2 = gu.hig_category_vbox(_(section['heading']), spacing=0)
                category.pack_start(b1, False) 
                for idx, collection_id in enumerate(section['collection-ids']):
                    label = gu.ClickableLabel(_(app.m_frontpage_data['collections'][collection_id]['name']))
                    if first:
                        label.set_data('first', True)
                        first = False
                    label.connect('clicked',
                          lambda f, collection_id=collection_id:
                          self.m_app.m_ui.display_exercises(
                          app.m_frontpage_data['collections'][collection_id]))
                    self.m_linkbuttons.append(label)
                    label.connect('focus-in-event', self.on_focus_in)
                    b2.pack_start(label, True)
            label.set_data('last', True)
        self.show_all()
    def on_search(self, *button):
        self.m_app.m_ui.display_exercises(
                {'name': _("Search results for '%s':" % self.g_searchentry.get_text()),
             'children': [x[0] for x in statistics.db.search(
                 self.g_searchentry.get_text().lower(), ('uuid',))]})


class ExerciseView(SelectWinBase):
    def __init__(self, app):
        SelectWinBase.__init__(self, app)
        self.g_box = None
        self.show_all()
    def display_exercises(self, data):
        """
        data = {'name': _('Ascending melodic intervals'),
                 'children': [
                    '7954749b-307d-43e9-b16f-77a942cfdd86',
                    'eb76c2c3-414d-4682-8a16-b4d3c5475574',
                    '3ac10629-fd95-42f6-88f0-ba6e60ecd0b2',]
                }
        or a list of dicts like the one above.
        """
        if self.g_box:
            self.g_box.destroy()
        self.g_box = gu.hig_dlg_vbox()
        self.add_with_viewport(self.g_box)
        # The category_list hack here let us avoid an extra level
        # dict with 'children' in the datafile for Exercise views that display
        # only one category of exercises.
        if not data['children']:
            self.g_box.pack_start(gtk.Label(_("No matches")))
            self.show_all()
            return
        if isinstance(data['children'][0], basestring):
            category_list = [data]
        else:
            category_list = data['children']
        first = True
        self.m_linkbuttons = []
        for category in category_list:
            b1, b2 = gu.hig_category_vbox(_(category['name']), spacing=0)
            self.g_box.pack_start(b1, False)
            for idx, uuid in enumerate(category['children']):
                txt = _(statistics.db.get_field(uuid, "title"))
                max_chars = 35
                if len(txt) > max_chars:
                    # translators, this is used when the gui feel the string
                    # is too long to be displayed. On many languages it is
                    # logical to add the dots '...' after the truncated word.
                    # Anyway, as always with messages with a | inside, dont
                    # include or translate "truncated|" in this string. Just
                    # enter the 3 dots, or whatever is correct for your language.
                    txt2 = _i("truncated|%s...") % txt[:max_chars]
                else:
                    txt2 = txt
                label = gu.ClickableLabel(txt2)
                if txt != txt2:
                    label.set_tooltip_text(txt)
                self.m_linkbuttons.append(label)
                if first:
                    label.set_data('first', True)
                    first = False
                label.connect('clicked', self.on_link_clicked, uuid)
                label.connect('focus-in-event', self.on_focus_in)
                b2.pack_start(label, False)
        label.set_data('last', True)
        self.show_all()
        self.m_linkbuttons[0].grab_focus()
    def on_link_clicked(self, w, uuid):
        self.m_app.practise_lesson_id(uuid)
    def on_end_practise(self, w=None):
        pass


class TestsView(ExerciseView):
    def on_link_clicked(self, w, uuid):
        self.m_app.test_lesson_id(uuid)


class MainWin(gtk.Window, cfg.ConfigUtils):
    default_learning_tree = ('solfege', 'learningtree.txt')
    debug_learning_tree = ('solfege', 'debugtree.txt')
    def __init__(self, options, datadir, lessonfile_manager):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self._vbox = gtk.VBox()
        self._vbox.show()
        self.add(self._vbox)
        stock.SolfegeIconFactory(self, datadir)
        gtk.settings_get_default().set_property('gtk-button-images', True)
        self.set_default_size(400, 400)
        pixbuf = self.render_icon('solfege-icon', gtk.ICON_SIZE_DIALOG)
        self.set_icon(pixbuf)
        cfg.ConfigUtils.__dict__['__init__'](self, 'mainwin')
        self.set_resizable(self.get_bool('gui/mainwin_user_resizeable'))
        self.add_watch('gui/mainwin_user_resizeable', lambda s: self.set_resizable(self.get_bool('gui/mainwin_user_resizeable')))
        self.connect('destroy', self.quit_program)
        self.connect('key_press_event', self.on_key_press_event)
        self.g_about_window = None
        self.g_help_browser = None
        self.g_learning_tree_editor = None
        self.m_exercise = None
        self.m_viewer = None
        self.box_dict = {}
        self.g_config_window = None
        self.g_path_info_dlg = None
        self.g_musicviewer_window = None
        self.m_app = app.SolfegeApp(options, self, lessonfile_manager)
        self.g_ui_manager = gtk.UIManager()
        self.m_action_groups = {
            'Exercises': gtk.ActionGroup('Exercises'),
            'Exit': gtk.ActionGroup('Exit'),
            'NotExit': gtk.ActionGroup('NotExit'),
        }
        for a in self.m_action_groups.values():
            self.g_ui_manager.insert_action_group(a, 1)
        self.setup_menu()
        self.main_box = gtk.VBox()
        self.main_box.show()
        self._vbox.pack_start(self.main_box)
        self.display_frontpage()
    def change_learning_tree(self, tree_fn):
        """
        Change to a different learning tree.
        """
        self.set_list('app/learningtree', tree_fn)
        self.on_learning_tree_changed()
    def on_learning_tree_changed(self, *v):
        """
        We call this when we have changed the current tree or
        switched tree.
        """
        self.m_tree = learning_tree_editor.LearningTree(self.m_app.lessonfile_manager)
        try:
            loc, filename = self.get_list("app/learningtree")
        except ValueError:
            loc, filename = self.default_learning_tree
        if (loc, filename) == ('solfege', 'debugtree.txt') and not self.m_app.m_options.debug:
            self.set_list("app/learningtree", ('solfege', 'learningtree.txt'))
            loc, filename = self.default_learning_tree
        try:
            if loc == 'solfege':
                self.m_tree.load(os.path.join("learningtrees", filename))
            else:
                assert loc == 'user'
                self.m_tree.load(os.path.join(filesystem.user_data(), 'learningtrees', filename))
        except IOError:
            if splash_win:
                splash_win.hide()
            self.m_tree.load(os.path.join("learningtrees", self.default_learning_tree[1]))
            self.set_list('app/learningtree', self.default_learning_tree)
            gu.dialog_ok(_("Learning tree '%s' not found. Using default tree." % filename))
            if splash_win:
                splash_win.show()
    def setup_menu(self):
        self.m_action_groups['Exit'].add_actions([
          ('FileMenu', None, _('_File')),
          ('AppQuit', 'gtk-quit', None, None, None, self.quit_program),
        ])
        self.m_action_groups['NotExit'].add_actions([
          ('TheoryMenu', None, _('The_ory')),
          ('TheoryIntervals', None, _('_Intervals'), None, None,
            lambda o: self.m_app.handle_href('theory-intervals.html')),
          ('EditMenu', None, _("_Edit")),
          ('TreeEditor', None, _('Edit learning tree'), None, None,
            self.do_tree_editor),
          ('ExportTrainingSet', None, _('New training set editor window'), None, None,
            self.new_training_set_editor),
          ('EditPractiseSheet', None, _('Ear training test printout editor'), None, None,
            self.new_practisesheet_editor),
          ('OpenPreferencesWindow', 'gtk-preferences', None, '<ctrl>F12', None,
            self.open_preferences_window),
          ('HelpMenu', None, _('_Help')),
          ('FrontPage', None, _('_Front page'), 'F5', None,
              self.display_frontpage),
          ('TestsPage', None, _('_Tests page'), 'F6', None,
              self.display_testpage),
          ('HelpHelp', 'gtk-help', _('_Help on the current exercise'), 'F1', None,
            lambda o: self.m_app.please_help_me()),
          ('HelpTheory', None, _('_Music theory on the current exercise'), 'F3', None, lambda o: self.m_app.show_exercise_theory()),
          ('HelpIndex', None, _('_User manual'), None, None,
            lambda o: self.m_app.handle_href('index.html')),
          ('HelpAllLessonFiles', None, _('All installed _lesson files'), None,
            None, lambda o: self.m_app.handle_href('solfege:all-lessonfiles')),
          ('HelpShowPathInfo', None, _('File locations'), None,
            None, self.show_path_info),
          ('HelpOnline', None, _('_Mailinglists, web page etc.'), None, None,
            lambda o: self.m_app.handle_href('online-resources.html')),
          ('HelpReportingBugs', None, _('Reporting _bugs'), None, None,
            lambda o: self.m_app.handle_href('bug-reporting.html')),
          ('HelpAbout', 'gtk-about', None, None, None, self.show_about_window),
          ('ShowBugReports', None, _('See your bug reports'), None, None,
            self.show_bug_reports),
        ])

        self.g_ui_manager.add_ui_from_file("ui.xml")

        self.add_accel_group(self.g_ui_manager.get_accel_group())
        self.g_ui_manager.get_accel_group().connect_group(gtk.keysyms.KP_Equal,
           gtk.gdk.CONTROL_MASK, 0,
           lambda w, a, b, c: self.box_dict['docviewer'].m_htmlwidget.g_view.zoom_reset())
        self.g_ui_manager.get_accel_group().connect_group(gtk.keysyms.KP_Subtract,
           gtk.gdk.CONTROL_MASK, 0,
           lambda w, a, b, c: self.box_dict['docviewer'].m_htmlwidget.g_view.zoom_out())
        self.g_ui_manager.get_accel_group().connect_group(gtk.keysyms.KP_Add,
           gtk.gdk.CONTROL_MASK, 0,
           lambda w, a, b, c: self.box_dict['docviewer'].m_htmlwidget.g_view.zoom_in())
        hdlbox = gtk.HandleBox()
        hdlbox.show()
        hdlbox.add(self.g_ui_manager.get_widget('/Menubar'))
        self._vbox.pack_start(hdlbox, False)
        self.m_help_on_current_merge_id = None
    def create_learning_trees_menu(self):
        if self.m_learning_trees_merge_id:
            self.g_ui_manager.remove_ui(self.m_learning_trees_merge_id)
        trees = learning_tree_editor.get_learning_tree_list(self.m_app.m_options.debug)
        if 'LearningTreesMenu' not in [a.get_name() for a in self.m_action_groups['NotExit'].list_actions()]:
            actions = [('LearningTreesMenu', None, _('Learning tree'))]
        else:
            actions = []
        s = """
<menubar name='Menubar'>
 <menu action='FileMenu'>
  <menu action='LearningTreesMenu' position='top'>\n"""
        old_cls = trees[0][0]
        for cls, fn, fullpath in trees:
            # FIXME: the separator seems to be invisible most of the time. Why???
            if old_cls != cls:
                s += "<separator/>\n"
                old_cls = cls
            t = learning_tree_editor.LearningTree(self.m_app.lessonfile_manager)
            try:
                t.read_file(fullpath)
            except Exception, e:
                if splash_win:
                    splash_win.hide()
                gu.dialog_ok(_("Failed to load learning tree"), self, 
                    _("Ignoring the file \"%(filename)s\" because of this error: %(error)s") % {
                    'filename': fullpath,
                    'error': str(e).decode(sys.getfilesystemencoding(), 'replace'),
                    }, gtk.MESSAGE_ERROR)
                continue
            s += "  <menuitem action='%s:%s'/>\n" % (cls, fn)
            action_name = '%s:%s' % (cls, fn)
            if action_name not in [x.get_name() for x in self.m_action_groups['NotExit'].list_actions()]:
                actions.append((action_name, None, _(t.m_title), None, None,
                lambda o, c=cls, f=fn: self.change_learning_tree((c, f))))
        s += "</menu></menu></menubar>"
        self.m_action_groups['NotExit'].add_actions(actions)
        self.m_learning_trees_merge_id = self.g_ui_manager.add_ui_from_string(s)
    def show_help_on_current(self):
        """
        Show the menu entries for the exercise help and music theory
        pages on the Help menu.
        """
        if self.m_help_on_current_merge_id:
            return
        self.m_help_on_current_merge_id = self.g_ui_manager.add_ui_from_string("""
<menubar name='Menubar'>
  <menu action='HelpMenu'>
    <placeholder name='PerExerciseHelp'>
      <menuitem position='top' action='HelpHelp' />
      <menuitem action='HelpTheory' />
    </placeholder>
  </menu>
</menubar>""")
    def hide_help_on_current(self):
        """
        Hide the menu entries for the help and music theory pages on the
        Help menu.
        """
        if not self.m_help_on_current_merge_id:
            return
        self.g_ui_manager.remove_ui(self.m_help_on_current_merge_id)
        self.m_help_on_current_merge_id = None
    def show_bug_reports(self, *v):
        m = gtk.Dialog(_("Question"), self, 0)
        m.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        m.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        vbox = gtk.VBox()
        m.vbox.pack_start(vbox, False)
        vbox.set_spacing(18)
        vbox.set_border_width(12)
        l = gtk.Label(_("Please enter the email used when you submitted the bugs:"))
        vbox.pack_start(l, False)
        self.g_email = gtk.Entry()
        m.action_area.get_children()[0].grab_default()
        self.g_email.set_activates_default(True)
        vbox.pack_start(self.g_email, False)
        m.show_all()
        ret = m.run()
        m.destroy()
        if ret == gtk.RESPONSE_OK:
            params = urllib.urlencode({
                    'pagename': 'SITS-Incoming/SearchBugs',
                    'q': 'SITS-Incoming/"Submitter: %s"' % utils.mangle_email(self.g_email.get_text()),
                })
            try:
                webbrowser.open_new("http://www.solfege.org?%s" % params)
            except Exception, e:
                self.display_error_message2(_("Error opening web browser"), str(e))
    def display_exception_message(self, exception, lessonfile=None):
        """Call this function only inside an except clause."""
        from exceptiondialog import ExceptionDialog
        sourcefile, lineno, func, code = traceback.extract_tb(sys.exc_info()[2])[0]
        # We can replace characters because we will only display the
        # file name, not open the file.
        sourcefile = sourcefile.decode(sys.getfilesystemencoding(), 'replace')
        m = ExceptionDialog(exception)
        if lessonfile:
            m.add_text(_("Please check the lesson file %s.") % lessonfile)
        if sourcefile:
            m.add_text(_('The exception was caught in\n"%(filename)s", line %(lineno)i.') % {'filename': sourcefile, 'lineno': lineno})
        if 'm_nonwrapped_text' in dir(exception):
            m.add_nonwrapped_text(exception.m_nonwrapped_text)
        m.run()
        m.destroy()
    def display_error_message2(self, text, secondary_text):
        """
        This is the new version of display_error_message, and it will
        eventually replace the old.
        """
        if splash_win and splash_win.props.visible:
            splash_win.hide()
            reshow_splash = True
        else:
            reshow_splash = False
        if not isinstance(text, unicode):
            text = text.decode(locale.getpreferredencoding(), 'replace')
        if not isinstance(secondary_text, unicode):
            secondary_text = secondary_text.decode(locale.getpreferredencoding(), 'replace')
        m = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
                              gtk.BUTTONS_CLOSE, text)
        if secondary_text:
            m.format_secondary_text(secondary_text)
        m.run()
        m.destroy()
        if reshow_splash:
            splash_win.show()
            while gtk.events_pending():
                gtk.main_iteration(0)
    def display_error_message(self, msg, title=None, secondary_text=None):
        if splash_win and splash_win.props.visible:
            splash_win.hide()
            reshow_splash = True
        else:
            reshow_splash = False
        if not isinstance(msg, unicode):
            msg = msg.decode(locale.getpreferredencoding(), 'replace')
        m = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
                              gtk.BUTTONS_CLOSE, None)
        m.set_markup(gu.escape(msg))
        if title:
            m.set_title(title)
        if secondary_text:
            m.format_secondary_text(secondary_text)
        m.run()
        m.destroy()
        if reshow_splash:
            splash_win.show()
            while gtk.events_pending():
                gtk.main_iteration(0)
    def show_path_info(self, w):
        if not self.g_path_info_dlg:
            self.g_path_info_dlg = gtk.Dialog(_("File locations"), self,
                buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
            vbox = gu.hig_dlg_vbox()
            self.g_path_info_dlg.vbox.pack_start(vbox)
            box1, box2 = gu.hig_category_vbox(_("File locations"))
            vbox.pack_start(box1)
            sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
            box2.pack_start(gu.hig_label_widget(_("Solfege config file:"), gtk.Label(filesystem.rcfile()), sizegroup))
            box2.pack_start(gu.hig_label_widget(_("Solfege standard lesson files:"), gtk.Label(os.path.join(os.getcwdu(), "lesson-files")), sizegroup))
            box2.pack_start(gu.hig_label_widget(_("The users private lesson files:"), gtk.Label(filesystem.user_lessonfiles()), sizegroup))
            box2.pack_start(gu.hig_label_widget(_("The users private learning trees:"), gtk.Label(os.path.join(filesystem.user_data(), "learningtrees")), sizegroup))
            box2.pack_start(gu.hig_label_widget(_("User manual in HTML format:"), gtk.Label(os.path.join(os.getcwdu(), "help")), sizegroup))
            self.g_path_info_dlg.show_all()
            def f(*w):
                self.g_path_info_dlg.hide()
                return True
            self.g_path_info_dlg.connect('response', f)
            self.g_path_info_dlg.connect('delete-event', f)
        else:
            self.g_path_info_dlg.show()
    def show_about_window(self, widget):
        trans = _("SOLFEGETRANSLATORS")
        if trans == 'SOLFEGETRANSLATORS':
            trans = ""
        pixbuf = self.render_icon('solfege-icon', gtk.ICON_SIZE_DIALOG)
        a = self.g_about_window = gtk.AboutDialog()
        a.set_logo(pixbuf)
        a.set_name("GNU Solfege")
        a.set_website("http://www.solfege.org")
        a.set_version(buildinfo.VERSION_STRING)
        a.set_copyright("\n".join((solfege_copyright, warranty)))
        a.set_authors(["Tom Cato Amundsen",
              'Giovanni Chierico %s' % _("(some lessonfiles)"),
              'Michael Becker %s' % _("(some lessonfiles)"),
              'Joe Lee %s' % _("(sound code for the MS Windows port)"),
              'Steve Lee %s' % _("(ported winmidi.c to gcc)"),
              'Thibaus Cousin %s' % _("(spec file for SuSE 8.2)"),
              'David Coe %s' %_("(spec file cleanup)"),
              'David Petrou %s' % _("(testing and portability fixes for FreeBSD)"),
              'Han-Wen Nienhuys %s' % _("(the music font from Lilypond)"),
              'Jan Nieuwenhuizen %s' % _("(the music font from Lilypond)"),
              'Davide Bonetti %s' % _("(scale exercises)"),
              ])
        a.set_documenters(["Tom Cato Amundsen",
                "Tom Eykens",
                ])
        a.set_translator_credits(_("SOLFEGETRANSLATORS"))
        self.g_about_window.run()
        self.g_about_window.destroy()
    def do_tree_editor(self, *v):
        # Raise it if it already exist.
        if self.g_learning_tree_editor:
            self.g_learning_tree_editor.present()
            return
        # And if it don't exist, then we have to create it.
        self.g_learning_tree_editor = win = learning_tree_editor.Window(self.m_app)
        try:
            loc, filename = self.get_list("app/learningtree")
        except ValueError:
            loc, filename = self.default_learning_tree
        win.load_file(loc, filename)
        win.show()
    def post_constructor(self):
        global splash_win
        self.m_learning_trees_merge_id = None
        self.create_learning_trees_menu()
        self.g_ui_manager.add_ui_from_file("help-menu.xml")
        if self.m_app.m_sound_init_exception is not None:
            if splash_win:
                splash_win.destroy()
                splash_win = None
            self.m_app.display_sound_init_error_message(self.m_app.m_sound_init_exception)
        # MIGRATION 3.9.0
        if sys.platform == "win32" \
            and os.path.exists(os.path.join(filesystem.get_home_dir(), "lessonfiles")) \
            and not os.path.exists(filesystem.user_lessonfiles()):
                if splash_win:
                    splash_win.hide()
                do_move = gu.dialog_yesno(_('In Solfege 3.9.0, the location where Solfege look for lesson files you have created was changed. The files has to be moved from "%(old)s" and into the folder "%(gnu)s" in your "%(doc)s" folder.\nMay I move the files automatically for you now?' % {
                    'doc':  os.path.split(os.path.split(filesystem.user_data())[0])[1],
                    'gnu':  os.path.join(filesystem.appname, 'lessonfiles'),
                    'old': os.path.join(filesystem.get_home_dir(), "lessonfiles"),
                  }), parent=self)
                if do_move:
                    try:
                        os.makedirs(filesystem.user_data())
                        shutil.copytree(os.path.join(filesystem.get_home_dir(), "lessonfiles"),
                                        os.path.join(filesystem.user_data(), "lessonfiles"))
                    except (OSError, shutil.Error), e:
                        gu.dialog_ok(_("Error while copying directory:\n%s" % e))
                    else:
                        gu.dialog_ok(_("Files copied. The old files has been left behind. Please delete them when you have verified that all files was copied correctly."))

                if splash_win:
                    splash_win.show()
        # MIGRATION 3.9.3 when we added langenviron.bat and in 3.11
        # we migrated to langenviron.txt because we does not use cmd.exe
        if sys.platform == 'win32' and winlang.win32_get_langenviron() != self.get_string('app/lc_messages'):
            gu.dialog_ok(_("Migrated old language setup. You might have to restart the program all translated messages to show up."))
            winlang.win32_put_langenviron(self.get_string('app/lc_messages'))
        if self.m_app.lessonfile_manager.m_discards:
            if splash_win:
                splash_win.hide()
            self.display_discarded_lessonfiles_info()
            if splash_win:
                splash_win.show()
        # MIGRATION 3.11.1: earlier editors would create new learning trees
        # below app_data() instead of user_data().
        if (sys.platform == "win32" and 
            os.path.exists(os.path.join(filesystem.app_data(),
                                        "learningtrees"))):
            if not os.path.exists(os.path.join(filesystem.user_data(), "learningtrees")):
                os.mkdir(os.path.join(filesystem.user_data(), "learningtrees"))
            for fn in os.listdir(os.path.join(filesystem.app_data(), "learningtrees")):
                if not os.path.exists(os.path.join(filesystem.user_data(), "learningtrees", fn)):
                    shutil.move(os.path.join(filesystem.app_data(), "learningtrees", fn),
                            os.path.join(filesystem.user_data(), "learningtrees"))
                else:
                    # We add the .bak exstention if the file already exists.
                    shutil.move(os.path.join(filesystem.app_data(), "learningtrees", fn),
                            os.path.join(filesystem.user_data(), "learningtrees", u"%s.bak" % fn))
                os.rmdir(os.path.join(os.path.join(filesystem.app_data(), "learningtrees")))
        item = self.g_ui_manager.get_widget("/Menubar/FileMenu/LearningTreesMenu")
        item.connect('activate', lambda s: self.create_learning_trees_menu())
        try:
            i18n.locale_setup_failed
            print >> sys.stderr, "\n".join(textwrap.wrap("Translations are disabled because your locale settings are broken. This is not a bug in GNU Solfege, so don't report it. The README file distributed with the program has some more details."))
        except AttributeError:
            pass
    def display_discarded_lessonfiles_info(self):
        assert self.m_app.lessonfile_manager.m_discards
        dlg = gtk.Dialog(_("Discarded lesson files"), buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        dlg.set_default_response(gtk.RESPONSE_ACCEPT)
        cbox = gtk.VBox()
        dlg.vbox.pack_start(cbox)
        cbox.set_border_width(12)
        dlg.set_default_size(400, 300)
        l = gtk.Label(_("The lesson files listed below was discarded by the program, and will not be available in the program until the errors are corrected."))
        l.set_line_wrap(True)
        cbox.pack_start(l, False)
        sc = gtk.ScrolledWindow()
        sc.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vbox = gtk.VBox()
        sc.add_with_viewport(vbox)
        cbox.pack_start(sc)
        for idx, info in enumerate(self.m_app.lessonfile_manager.m_discards):
            sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
            ibox = gtk.VBox()
            row1 = gtk.HBox()
            row1.set_spacing(8)
            ibox.pack_start(row1, False)
            l = gtk.Label(_("Filename:"))
            l.set_alignment(1.0, 0.5)
            sizegroup.add_widget(l)
            row1.pack_start(gtk.Label(_("Filename:")), False)
            row1.pack_start(gtk.Label(info['filename']), False)
            row2 = gtk.HBox()
            row2.set_spacing(8)
            ibox.pack_start(row2, False)
            l = gtk.Label(_("Reason:"))
            l.set_alignment(1.0, 0.5)
            sizegroup.add_widget(l)
            row2.pack_start(l, False)
            if 'exception' in info:
                estr = str(info['exception']).decode(sys.getfilesystemencoding(), 'replace')
                row2.pack_start(gtk.Label(estr), False)
            elif 'reason' in info:
                if info['reason'] == 'no module':
                    msg = _("Missing module declaration")
                else:
                    msg = _("Unknown")
                row2.pack_start(gtk.Label(msg), False)
            vbox.pack_start(ibox, False)
            if idx < len(self.m_app.lessonfile_manager.m_discards) - 1:
                vbox.pack_start(gtk.HSeparator(), False)
        dlg.show_all()
        dlg.run()
        dlg.destroy()

    def activate_exercise(self, module, urlobj=None):
        if self.m_viewer:
            self.box_dict[self.m_viewer].hide()
        self.m_viewer = module
        self.box_dict[module].show()
        # We need this test because not all exercises use a notebook.
        if self.box_dict[self.m_viewer].g_notebook:
            if urlobj and urlobj.action in ['practise', 'config', 'statistics']:
                self.box_dict[self.m_viewer].g_notebook.set_current_page(
                   ['practise', 'config', 'statistics'].index(urlobj.action))
            else:
                self.box_dict[self.m_viewer].g_notebook.set_current_page(0)
        if self.box_dict[module].m_t.m_P:
            if isinstance(self.box_dict[module].m_t, abstract.Teacher):
                self.set_title("Solfege - " + self.box_dict[module].m_t.m_P.header.title)
            else:
                self.set_title("Solfege - " + self.box_dict[module].m_t.exercise_data['name'].replace("_", ""))
        else:
            self.set_title("Solfege")
    def _show_docviewer(self):
        if 'docviewer' not in self.box_dict:
            self.box_dict['docviewer'] = DocViewer(self.m_app.handle_href)
            self.box_dict['docviewer'].set_size_request(500, 300)
            self.main_box.pack_start(self.box_dict['docviewer'])
        if self.m_viewer and (self.m_viewer != 'docviewer'):
            self.box_dict[self.m_viewer].hide()
        self.m_viewer = 'docviewer'
        self.box_dict['docviewer'].show()
    def display_docfile(self, fn, anchor):
        """
        Display the HTML file named by fn in the help browser window.
        """
        if not self.g_help_browser:
            self.g_help_browser = HelpBrowser(self.m_app)
        if self.get_bool('gui/web_browser_as_help_browser'):
            for lang in self.g_help_browser.g_docviewer.m_language, "C":
                filename = os.path.join(os.getcwdu(), u"help", lang, fn)
                if os.path.isfile(filename):
                    break
            try:
                webbrowser.open(filename)
            except Exception, e:
                self.display_error_message2(_("Error opening web browser"), str(e))
        else:
            self.g_help_browser.show()
            self.g_help_browser.present()
            self.g_help_browser.show_docfile(fn, anchor)
    def display_testpage(self, w=None):
        if self.m_viewer != 'testspage':
            if self.m_viewer:
                self.box_dict[self.m_viewer].hide()
        if 'testspage' not in self.box_dict:
            frontpage = TestsView(self.m_app)
            self.box_dict['testspage'] = frontpage
            self.main_box.pack_start(frontpage)
        self.m_viewer = 'testspage'
        self.box_dict[self.m_viewer].show()
        self.box_dict[self.m_viewer].get_vadjustment().value = 0.0
        v  = []
        for k in ('asc-mel-int', 'des-mel-int', 'mel-int', 'harm-int',
                'sing-int', 'chords', 'cadences', 'solfa-syll'):
            # some 'collections' in the datafile contains two or more records
            collection = self.m_app.m_frontpage_data['collections'][k]
            if type(collection['children'][0]) == dict:
                for kk in collection['children']:
                    v.append(kk)
                continue
            v.append(collection)
        v = {'name': _("Tests"),
            'children': v}
        self.box_dict[self.m_viewer].display_exercises(v)
    def display_frontpage(self, widget=None):
        if self.m_viewer != 'frontpage':
            if self.m_viewer:
                self.box_dict[self.m_viewer].hide()
        if 'frontpage' not in self.box_dict:
            frontpage = FrontPage(self.m_app)
            self.box_dict['frontpage'] = frontpage
            self.main_box.pack_start(frontpage)
            w, h = frontpage.get_children()[0].get_children()[0].size_request()
            if w > 500:
                frontpage.set_size_request(w + 24, 300)
        self.m_viewer = 'frontpage'
        self.box_dict[self.m_viewer].show()
        self.box_dict[self.m_viewer].g_searchentry.grab_focus()
        self.box_dict[self.m_viewer].get_vadjustment().value = 0.0
    def display_exercises(self, data):
        if self.m_viewer != 'exerciseviewer':
            if self.m_viewer:
                self.box_dict[self.m_viewer].hide()
        if 'exerciseviewer' not in self.box_dict:
            eview = ExerciseView(self.m_app)
            self.box_dict['exerciseviewer'] = eview
            self.main_box.pack_start(eview)
        self.m_viewer = 'exerciseviewer'
        self.box_dict[self.m_viewer].show()
        self.box_dict[self.m_viewer].display_exercises(data)
    def display_docfile_in_mainwin(self, fn, anchor=None):
        self._show_docviewer()
        self.box_dict['docviewer'].read_docfile(fn, anchor)
        self.set_title("Solfege - %s" % fn)
    def display_html(self, html):#FIXME do in helpbrowser
        self._show_docviewer()
        self.box_dict['docviewer'].source(html)
    def initialise_exercise(self, teacher):
        """
        Create a Gui object for the exercise and add it to
        the box_dict dict.
        """
        assert teacher.m_exname not in self.box_dict
        n = utils.exercise_name_to_module_name(teacher.m_exname)
        self.box_dict[teacher.m_exname] = globals()[n].Gui(teacher, self)
        self.main_box.pack_start(self.box_dict[teacher.m_exname])
        if self.m_viewer:
            self.box_dict[self.m_viewer].hide()
        self.box_dict[n].show()
        self.m_viewer = n
    def on_key_press_event(self, widget, event):
        self.box_dict[self.m_viewer].on_key_press_event(widget, event)
    def open_preferences_window(self, widget=None):
        if not self.g_config_window:
            self.g_config_window = ConfigWindow(self.m_app)
            self.g_config_window.show()
        else:
            self.g_config_window.show()
    def quit_program(self, w=None):
        if self.g_learning_tree_editor:
            self.g_learning_tree_editor.close_window()
        self.m_app.quit_program()
        gtk.main_quit()
    def display_in_musicviewer(self, music):
        if not self.g_musicviewer_window:
            self.g_musicviewer_window = MusicViewerWindow(self)
            self.g_musicviewer_window.show()
        self.g_musicviewer_window.display_music(music)
    def close_musicviewer(self, widget=None):
        self.g_musicviewer_window.destroy()
        self.g_musicviewer_window = None
    def enter_test_mode(self):
        if 'enter_test_mode' not in dir(self.box_dict[self.m_viewer]):
            gu.dialog_ok(_("The '%s' exercise module does not support test yet." % self.m_viewer))
            return
        self.m_action_groups['NotExit'].set_sensitive(False)
        self.m_action_groups['Exercises'].set_sensitive(False)
        self.g = self.box_dict[self.m_viewer].g_notebook.get_nth_page(0)
        self.box_dict[self.m_viewer].g_notebook.get_nth_page(0).reparent(self.main_box)
        self.box_dict[self.m_viewer].g_notebook.hide()
        self.box_dict[self.m_viewer].enter_test_mode()
    def exit_test_mode(self):
        self.m_app.m_test_mode = False
        self.m_action_groups['NotExit'].set_sensitive(True)
        self.m_action_groups['Exercises'].set_sensitive(True)
        box = gtk.VBox()
        self.box_dict[self.m_viewer].g_notebook.insert_page(box, gtk.Label(_("Practise")), 0)
        self.g.reparent(box)
        self.box_dict[self.m_viewer].g_notebook.show()
        self.box_dict[self.m_viewer].g_notebook.get_nth_page(0).show()
        self.box_dict[self.m_viewer].g_notebook.set_current_page(0)
        self.box_dict[self.m_viewer].exit_test_mode()
    def new_training_set_editor(self, widget):
        dlg = TrainingSetDialog(self.m_app)
        dlg.show_all()
    def new_practisesheet_editor(self, widget):
        dlg = PractiseSheetDialog(self.m_app)
        dlg.show_all()
    def handle_lesson_id_crash(self):
        lessonfilegui.handle_lesson_id_crash(lessonfile.manager)

import locale
locale.setlocale(locale.LC_NUMERIC, "C")

# check_rcfile has to be called before and
# functions that use the cfg module.
app.check_rcfile()

cfg.set_bool('config/no_random', bool(options.no_random))

if splash_win:
    splash_win.show_progress("creating LessonfileManager")
lessonfile_manager = lessonfile.LessonFileManager(options.debug)
lessonfile.manager = lessonfile_manager
if splash_win:
    if not os.path.exists(statistics.DB.get_statistics_filename()):
        splash_win.show_progress("Importing old statistics:")
def f(s):
    splash_win.show_progress("Importing old statistics: %i%%" % s)
statistics.db = statistics.DB(f)

if splash_win:
    splash_win.show_progress("creating MainWin")

def start_app(datadir):
    global splash_win
    w = MainWin(options, datadir, lessonfile_manager)
    w.show()
    w.post_constructor()
    if splash_win:
        splash_win.destroy()
        splash_win = None

    def ef(t, value, traceback):
        if options.debug:
            msg = "ehooked:" + str(value)
        else:
            msg = str(value)
        if issubclass(t, lessonfile.LessonfileException):
            w.display_error_message(msg, str(t))
        elif issubclass(t, osutils.ExecutableDoesNotExist):
            if len(value.args) > 1:
                w.display_error_message2(value.args[0], "\n".join(value.args[1:]))
            else:
                w.display_error_message(msg, str(t))
        else:
            sys.__excepthook__(t, msg, traceback)
    if not options.disable_exception_handler:
        sys.excepthook = ef
    print time.time() - start_time
    gtk.main()

