# vim: set fileencoding=utf-8 :
# 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/>.

from __future__ import absolute_import

import os
import sys

import gobject
import gtk

from solfege import cfg
from solfege import gu
from solfege import languages
from solfege import mpd
from solfege import notenamespinbutton
from solfege import osutils
from solfege import soundcard
from solfege import utils
from solfege import winlang
from solfege.instrumentselector import nInstrumentSelector, InstrumentConfigurator

import solfege

if sys.platform == 'win32':
    try:
        from solfege.soundcard import winmidi
    except ImportError, e:
        print >> sys.stderr, "Loading winmidi.pyd failed:"
        print >> sys.stderr, e
        winmidi = None

class ConfigWindow(gtk.Dialog, cfg.ConfigUtils):
    def on_destroy(self, widget, e):
        solfege.win.g_config_window.destroy()
        solfege.win.g_config_window = None
    def __init__(self):
        gtk.Dialog.__init__(self, _("GNU Solfege Preferences"),
             solfege.win, 0,
             (gtk.STOCK_HELP, gtk.RESPONSE_HELP, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
        cfg.ConfigUtils.__init__(self, 'configwindow')
        self.connect('response', self.apply_and_close)
        # We do this so that the window is only hidden when the
        # user click on the close button provided by the window manager.
        self.connect('delete-event', self.on_destroy)#lambda w, e: True)
        self.set_default_size(400, 400)
        self.g_notebook = gtk.Notebook()
        self.vbox.pack_start(self.g_notebook)
        
        ########
        # midi #
        ########
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("Instruments")))

        vbox, category_vbox = gu.hig_category_vbox(_("Tempo"))
        page_vbox.pack_start(vbox, False)
        sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)

        tempo_hbox = gtk.HBox(spacing=6)
        self.g_default_bpm = gu.nSpinButton('config', 'default_bpm',
            gtk.Adjustment(self.get_int('config/default_bpm'), 10, 500, 1, 10))
        self.g_arpeggio_bpm = gu.nSpinButton('config', 'arpeggio_bpm',
            gtk.Adjustment(self.get_int('config/arpeggio_bpm'), 10, 500, 1, 10))
        for text, widget in [(_("_Default:") ,self.g_default_bpm), 
                             (_("A_rpeggio:") ,self.g_arpeggio_bpm)]:
            label = gtk.Label(text)
            label.set_alignment(0.0, 0.5)
            label.set_use_underline(True)
            tempo_hbox.pack_start(label, expand=False)
            label.set_mnemonic_widget(widget)
            tempo_hbox.pack_start(widget, expand=False)
            label = gtk.Label(_("BPM"))
            label.set_alignment(0.0, 0.5)
            label.set_tooltip_text(_("Beats per minute"))
            tempo_hbox.pack_start(label, expand=True)        
        category_vbox.pack_start(tempo_hbox, False)

        box, category_vbox = gu.hig_category_vbox(_("Preferred Instrument"))
        page_vbox.pack_start(box, False)
        self.g_instrsel = nInstrumentSelector('config',
                        'preferred_instrument', sizegroup)
        category_vbox.pack_start(self.g_instrsel, False)

        box, category_vbox = gu.hig_category_vbox(_("Chord Instruments"))
        page_vbox.pack_start(box, False)
        self.g_instrument_configurator  \
              = InstrumentConfigurator("config", 3,
                    _("Use different instruments for chords and harmonic intervals"))
        category_vbox.pack_start(self.g_instrument_configurator, False)

        vbox, category_box = gu.hig_category_vbox(_("Percussion Instruments"))
        page_vbox.pack_start(vbox, False)
        category_box.pack_start(gu.hig_label_widget(
            _("Count in:"),
            gu.PercussionNameButton("config", "countin_perc", "Claves"),
            sizegroup, True, True))
        category_box.pack_start(gu.hig_label_widget(
            _("Rhythm:"),
            gu.PercussionNameButton("config", "rhythm_perc", "Side Stick"),
            sizegroup, True, True))
        ########
        # user #
        ########
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("User")))

        box, category_vbox = gu.hig_category_vbox(_("User's Vocal Range"))
        page_vbox.pack_start(box, False)
        sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)

        self.g_highest_singable = notenamespinbutton.NotenameSpinButton(
            self.get_string('user/highest_pitch'))
        box = gu.hig_label_widget(_("Highest pitch:"),
                                  self.g_highest_singable,
                                  sizegroup)
        category_vbox.pack_start(box)

        self.g_lowest_singable = notenamespinbutton.NotenameSpinButton(
            self.get_string('user/lowest_pitch'))
        box = gu.hig_label_widget(_("Lowest pitch:"),
                                  self.g_lowest_singable,
                                  sizegroup)
        category_vbox.pack_start(box)
        notenamespinbutton.nNotenameRangeController(
                  self.g_lowest_singable, self.g_highest_singable,
                  mpd.LOWEST_NOTENAME, mpd.HIGHEST_NOTENAME,
                  'user', 'lowest_pitch', 'highest_pitch')


        box, category_vbox = gu.hig_category_vbox(_("Sex"))
        page_vbox.pack_start(box, False)
        self.g_sex_male = gtk.RadioButton(None, _("_Male"))
        self.g_sex_male.connect('toggled', lambda w: self.set_string('user/sex', 'male'))
        category_vbox.pack_start(self.g_sex_male, False)
        self.g_sex_female = gtk.RadioButton(self.g_sex_male, _("_Female or child"))
        self.g_sex_female.connect('toggled', lambda w: self.set_string('user/sex', 'female'))
        category_vbox.pack_start(self.g_sex_female, False)
        if self.get_string('user/sex') == 'female':
            self.g_sex_female.set_active(True)
        #####################
        # external programs #
        #####################
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("External Programs")))

        box, category_vbox = gu.hig_category_vbox(_("Converters"))
        page_vbox.pack_start(box, False)
        sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)

        # midi_to_wav
        self.g_wav_convertor = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_wav_convertor.get_model().append(("timidity",))
        #
        self.g_wav_convertor_options = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_wav_convertor_options.get_model().append(("-Ow %(in)s -o %(out)s",))
        #
        category_vbox.pack_start(
            gu.hig_label_widget(_("MIDI to WAV:"),
               (self.g_wav_convertor, self.g_wav_convertor_options),
               sizegroup, True, True))
        #
        self.g_wav_convertor.child.set_text(
            self.get_string("app/midi_to_wav_cmd"))
        self.g_wav_convertor_options.child.set_text(
            self.get_string("app/midi_to_wav_cmd_options"))
        self.g_wav_convertor.connect('changed',
            lambda w: self.set_string('app/midi_to_wav_cmd',
                w.child.get_text()))
        self.g_wav_convertor_options.connect('changed',
            lambda w: self.set_string('app/midi_to_wav_cmd_options',
                w.child.get_text()))
        # wav_to_mp3
        self.g_mp3_convertor = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_mp3_convertor.get_model().append(("lame",))
        #
        self.g_mp3_convertor_options = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_mp3_convertor_options.get_model().append(("%(in)s %(out)s",))
        #
        category_vbox.pack_start(
            gu.hig_label_widget(_("WAV to MP3:"),
            (self.g_mp3_convertor, self.g_mp3_convertor_options),
            sizegroup, True, True))
        #
        self.g_mp3_convertor.child.set_text(
            self.get_string("app/wav_to_mp3_cmd"))
        self.g_mp3_convertor_options.child.set_text(
            self.get_string("app/wav_to_mp3_cmd_options"))
        self.g_mp3_convertor.connect('changed', 
            lambda w: self.set_string('app/wav_to_mp3_cmd',
                w.child.get_text()))
        self.g_mp3_convertor_options.connect('changed', 
            lambda w: self.set_string('app/wav_to_mp3_cmd_options',
                w.child.get_text()))
        # wav_to_ogg
        self.g_ogg_convertor = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_ogg_convertor.get_model().append(("oggenc",))
        #
        self.g_ogg_convertor_options = gtk.ComboBoxEntry(
            gtk.ListStore(gobject.TYPE_STRING))
        self.g_ogg_convertor_options.get_model().append(("%(in)s",))
        #
        category_vbox.pack_start(
            gu.hig_label_widget(_("WAV to OGG:"),
            (self.g_ogg_convertor, self.g_ogg_convertor_options),
            sizegroup, True, True))
        #
        self.g_ogg_convertor.child.set_text(
            self.get_string("app/wav_to_ogg_cmd"))
        self.g_ogg_convertor_options.child.set_text(
            self.get_string("app/wav_to_ogg_cmd_options"))
        self.g_ogg_convertor.connect('changed', 
            lambda w: self.set_string('app/wav_to_ogg_cmd',
                w.child.get_text()))
        self.g_ogg_convertor_options.connect('changed', 
            lambda w: self.set_string('app/wav_to_ogg_cmd_options',
                w.child.get_text()))

        self.add_gui_for_external_programs(page_vbox)
        ########
        # Misc #
        ########
        sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)

        box, category_vbox = gu.hig_category_vbox(_("Miscellaneous"))
        page_vbox.pack_start(box, False)

        # CSound_("Musical MIDI Accompaniment:")
        for bin, label, bins in (
            ("csound", _("CSound:"), osutils.find_csound_executables()),
            ("mma", "MMA:", osutils.find_mma_executables(
                cfg.get_list("app/win32_ignore_drives"))),
            ("lilypond-book", _("Lilypond-book:"),
             osutils.find_progs(("lilypond-book", "lilypond-book.py"))),
            ("latex", "Latex:", osutils.find_progs(("latex",))),
            ("text-editor", "Text editor:", osutils.find_progs(("sensible-editor", "gvim", "gedit", "emacs", "notepad.exe"))),
            ):
            liststore = gtk.ListStore(gobject.TYPE_STRING)
            for p in bins:
                liststore.append((p,))
            cbox_entry = gtk.ComboBoxEntry(liststore)
            cbox_entry.child.set_text(self.get_string("programs/%(bin)s" % locals()))
            def csound_changed_cb(widget, bin):
                self.set_string('programs/%s' % bin, widget.child.get_text())
                widget.warning.props.visible = not bool(
                    osutils.find_progs((cfg.get_string('programs/%s' % bin),)))
            cbox_entry.warning = gtk.Image()
            cbox_entry.warning.set_tooltip_text(_("Not found. Much of GNU Solfege will run fine without this program. You will get a message when the program is required, and the user manual will explain what you need it for."))
            cbox_entry.warning.set_from_stock(gtk.STOCK_DIALOG_WARNING,
                                              gtk.ICON_SIZE_SMALL_TOOLBAR)
            box = gu.hig_label_widget(label,
                                      [cbox_entry, cbox_entry.warning],
                                      sizegroup, True, True)
            category_vbox.pack_start(box)
            cbox_entry.warning.props.no_show_all = True
            csound_changed_cb(cbox_entry, bin)
            cbox_entry.connect('changed', csound_changed_cb, bin)

        #######
        # Gui #
        #######
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("Interface")))

        box, category_vbox = gu.hig_category_vbox(_("Interface"))
        page_vbox.pack_start(box, False)

        self.g_mainwin_user_resizeable = gu.nCheckButton('gui',
          'mainwin_user_resizeable', _("_Resizeable main window"))
        category_vbox.pack_start(self.g_mainwin_user_resizeable, False)

        # Combobox to select language
        hbox = gtk.HBox()
        hbox.set_spacing(6)
        label = gtk.Label()
        label.set_text_with_mnemonic(_("Select _language:"))
        hbox.pack_start(label, False)
        self.g_language = gtk.combo_box_new_text()
        for n in languages.languages:
            self.g_language.append_text(n)
        label.set_mnemonic_widget(self.g_language)
        if sys.platform == 'win32':
            lang = winlang.win32_get_langenviron()
            if lang in languages.languages:
                idx = languages.languages.index(lang)
            elif lang == 'C':
                idx = languages.C_locale_idx
            else:
                idx = 0
        else:
            lang = cfg.get_string('app/lc_messages')
            if lang in languages.languages:
                idx = languages.languages.index(lang)
            elif lang == 'C':
                idx = languages.C_locale_idx
            else:
                idx = 0
        self.g_language.set_active(idx)
        def f(combobox):
            if combobox.get_active() == languages.C_locale_idx:
                lang = "C"
            else:
                lang = languages.languages[combobox.get_active()]
            cfg.set_string('app/lc_messages', lang)
            if sys.platform == 'win32':
                if combobox.get_active():
                    winlang.win32_put_langenviron(lang)
                else:
                    winlang.win32_put_langenviron(None)
        self.g_language.connect_after('changed', f)
        hbox.pack_start(self.g_language, False)
        category_vbox.pack_start(hbox)
        l = gtk.Label(_("You have to restart the program for the language change to take effect."))
        l.set_alignment(0.0, 0.5)
        category_vbox.pack_start(l)
        #### idtone accelerators
        box, category_vbox = gu.hig_category_vbox(_("Identify tone keyboard accelerators"))
        page_vbox.pack_start(box, False)

        self.g_idtone_accels = gtk.ListStore(gobject.TYPE_STRING,
            gobject.TYPE_STRING)
        notenames = ('c', 'cis', 'd', 'dis', 'e', 'f', 'fis',
                     'g', 'gis', 'a', 'ais', 'b')
        for notename in notenames:
            self.g_idtone_accels.append((
                solfege.mpd.MusicalPitch.new_from_notename(notename).get_user_notename(),
                cfg.get_string('idtone/tone_%s_ak' % notename)))
        self.g_treeview = gtk.TreeView(self.g_idtone_accels)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_("Note name"), renderer, text=0)
        self.g_treeview.append_column(column)
        renderer = gtk.CellRendererAccel()
        renderer.set_property('editable', True)
        def acc_ff(renderer, path, accel_key, accel_mods, hw_key):
            is_unique = True
            for notename in notenames:
                if (notename != notenames[int(path[0])]
                    and cfg.get_string('idtone/tone_%s_ak' % notename) == unichr(accel_key)):
                    is_unique = False
                    break
            if not is_unique:
                gu.dialog_ok(_("The accelerator in use for the tone “%s”. You have to choose another key.") % solfege.mpd.MusicalPitch.new_from_notename(notename).get_user_notename(), parent=self, msgtype=gtk.MESSAGE_ERROR)
                return
            it = self.g_idtone_accels.get_iter(path)
            cfg.set_string('idtone/tone_%s_ak' % notenames[int(path[0])], unichr(accel_key))
            self.g_idtone_accels.set(it, 1, unichr(accel_key))
            return True
        renderer.connect('accel-edited', acc_ff)
        column = gtk.TreeViewColumn(_i("keyboard|Key"), renderer, text=1)
        self.g_treeview.append_column(column)
        category_vbox.pack_start(self.g_treeview)
        ############
        # Practise #
        ############
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("Practise")))
        box, category_vbox = gu.hig_category_vbox(_("Practise"))
        page_vbox.pack_start(box, False)

        self.g_picky_on_new_question = gu.nCheckButton('config', 'picky_on_new_question', _("_Not allow new question before the old is solved"))
        category_vbox.pack_start(self.g_picky_on_new_question, False)

        self.g_autorepeat_if_wrong = gu.nCheckButton('config', 'auto_repeat_question_if_wrong_answer', _("_Repeat question if the answer was wrong"))
        category_vbox.pack_start(self.g_autorepeat_if_wrong, False)

        self.g_expert_mode = gu.nCheckButton('gui', 
                'expert_mode', _("E_xpert mode"))
        self.g_expert_mode.connect('toggled', solfege.app.reset_exercise)
        category_vbox.pack_start(self.g_expert_mode, False)


        #########
        # sound #
        #########
        if sys.platform == 'win32':
            self.create_win32_sound_page()
        else:
            self.create_linux_sound_page()
        self.show_all()
    def add_gui_for_external_programs(self, page_vbox):
        box, category_vbox = gu.hig_category_vbox(_("Audio File Players"))
        page_vbox.pack_start(box, False)
        format_info = {}
        if sys.platform != 'win32':
            format_info = {'wav': {
                 # testfile is a file in lesson-files/share
                 'testfile': 'fifth-small-220.00.wav',
                 'label': _("WAV:"),
                 # players is a list of tuples. The tuple has to or more 
                 # items. The first is the binary, and the rest is possible
                 # sets of command line options that might work.
                 # '/path/to/player', 'comandline', '
                 'players': [
                        ('gst-launch', 'playbin uri=file://%s',
                                       'filesrc location=%s ! wavparse ! alsasink'),
                        ('play', ''),
                        ('aplay', ''),
                        ('esdplay', ''),
                 ],
                }
            }
        format_info['midi'] = {
             'testfile': 'fanfare.midi',
             'label': _("MIDI:"),
             'players': [
                         ('gst-launch', 'playbin uri=file://%s',
                                        'filesrc location=exercises/standard/lesson-files/share/fanfare.midi ! wildmidi ! alsasink'),
                         ('timidity', '-idqq %s'),
                         ('drvmidi', ''),
                         ('playmidi', ''),
             ],
            }
        format_info['mp3'] = {
             'testfile': 'fanfare.mp3',
             'label': _("MP3:"),
             'players': [
                        ('gst-launch', 'playbin uri=file://%s',
                                       'filesrc location=%s ! mad ! alsasink'),
                        ('mpg123', ''),
                        ('alsaplayer', ''),
             ],
            }
        format_info['ogg'] = {
             'testfile': 'fanfare.ogg',
             'label': _("OGG:"),
             'players': [
                        ('gst-launch', 'playbin uri=file://%s',
                                       'filesrc location=%s ! oggdemux ! vorbisdec ! audioconvert ! alsasink'),
                        ('ogg123', ''), 
                        ('alsaplayer', ''),
             ],
            }
        sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
        for formatid, format in format_info.items():
            cmd_liststore = gtk.ListStore(gobject.TYPE_STRING)
            for player_data in format['players']:
                cmd_liststore.append((player_data[0],))
            cmd_combo = gtk.ComboBoxEntry(cmd_liststore)
            cmd_combo.set_tooltip_text(_("Enter the name of the program. An absolute path is required only if the executable is not found on the PATH."))
            cmd_combo.opts = gtk.combo_box_entry_new_text()
            cmd_combo.opts.set_tooltip_text(_("The command line options required. Write %%s where you want the name of the file to be played. Or omit it to have it added to the end of the string.") % ())
            cmd_combo.child.set_text(cfg.get_string('sound/%s_player' % formatid))
            cmd_combo.opts.child.set_text(cfg.get_string('sound/%s_player_options' % formatid))
            for s in format['players']:
                if s[0] == cmd_combo.child.get_text():
                    for o in s[1:]:
                        cmd_combo.opts.append_text(o)
            
            def _changed(widget, formatid):
                if widget.get_active() != -1:
                    widget.opts.get_model().clear()
                    format = format_info[formatid]
                    for player_options in format['players'][widget.get_active()][1:]:
                        widget.opts.append_text(player_options)
                    widget.opts.child.set_text(format['players'][widget.get_active()][1])
                self.set_string('sound/%s_player' % formatid,
                                widget.child.get_text())
                widget.testbutton.set_sensitive(bool(
                    osutils.find_progs((widget.child.get_text(),))))

            def _opts_changed(widget, formatid):
                self.set_string('sound/%s_player_options' % formatid,
                                widget.child.get_text())
            cmd_combo.connect('changed', _changed, formatid)
            cmd_combo.opts.connect('changed', _opts_changed, formatid)
            testbutton = gtk.Button(_("Test"))
            testbutton.set_tooltip_text(_("This button is clickable only if the binary is found."))
            cmd_combo.testbutton = testbutton
            testbutton.connect('clicked', self.test_XXX_player,
                           formatid, format['testfile'])
            testbutton.set_sensitive(bool(
                    osutils.find_progs((cmd_combo.child.get_text(),))))
            box = gu.hig_label_widget(format['label'],
                                      [cmd_combo, cmd_combo.opts, testbutton],
                                      sizegroup, True, True)
            category_vbox.pack_start(box)
    def test_XXX_player(self, w, typeid, testfile):
        try:
            soundcard.play_mediafile(typeid, os.path.join('exercises/standard/lesson-files/share', testfile))
        except osutils.BinaryBaseException, e:
            solfege.win.display_error_message2(e.msg1, e.msg2)
    def create_linux_sound_page(self):
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("Sound Setup")))
        #############
        # midi setup
        #############
        box, category_vbox = gu.hig_category_vbox(_("Sound Setup"))
        page_vbox.pack_start(box)

        self.g_fakesynth_radio = gu.RadioButton(None, _("_No sound"), None)
        category_vbox.pack_start(self.g_fakesynth_radio, False)

        hbox = gu.bHBox(category_vbox, False)
        self.g_device_radio = gu.RadioButton(self.g_fakesynth_radio,
              _("Use _device"), None)
        hbox.pack_start(self.g_device_radio, False)

        liststore = gtk.ListStore(gobject.TYPE_STRING)
        for s in ('/dev/sequencer', '/dev/sequencer2', '/dev/music'):
            liststore.append((s,))
        self.g_device_file = gtk.ComboBoxEntry(liststore)
        self.g_device_file.child.set_text(self.get_string('sound/device_file'))
        self.g_synth_num = gtk.SpinButton(gtk.Adjustment(0, 0, 100, 1, 1),
                             digits=0)
        self.g_synth_num.set_value(self.get_int('sound/synth_number'))
        hbox.pack_start(self.g_device_file, False)
        hbox.pack_start(self.g_synth_num, False)

        ###
        hbox = gu.bHBox(category_vbox, False)
        self.g_midiplayer_radio = gu.RadioButton(self.g_fakesynth_radio,
             _("Use _external MIDI player"), None)
        hbox.pack_start(self.g_midiplayer_radio, False)

        if self.get_string("sound/type") == "external-midiplayer":
            self.g_midiplayer_radio.set_active(True)
        elif self.get_string("sound/type") == "sequencer-device":
            self.g_device_radio.set_active(True)
        else:
            self.g_fakesynth_radio.set_active(True)

        hbox = gtk.HButtonBox()
        category_vbox.pack_start(hbox)
        gu.bButton(category_vbox, _("_Test"), self.on_apply_and_play_test_sound)

    def create_win32_sound_page(self):
        page_vbox = gu.hig_dlg_vbox()
        self.g_notebook.append_page(page_vbox, gtk.Label(_("MIDI Setup")))
        #############
        # midi setup
        #############
        box, category_vbox = gu.hig_category_vbox(_("Sound Setup"))
        page_vbox.pack_start(box)
        txt = gtk.Label(_("""Solfege has two ways to play MIDI files. It is recommended to use Windows multimedia output. An external MIDI player can be useful if your soundcard lacks a hardware synth, in which case you have to use a program like timidity to play the music."""))
        txt.set_line_wrap(1)
        txt.set_justify(gtk.JUSTIFY_FILL)
        txt.set_alignment(0.0, 0.0)
        category_vbox.pack_start(txt, False)

        self.g_fakesynth_radio = gu.RadioButton(None, _("_No sound"), None)
        category_vbox.pack_start(self.g_fakesynth_radio, False)

        hbox = gu.bHBox(category_vbox, False)
        self.g_device_radio = gu.RadioButton(self.g_fakesynth_radio,
              _("_Windows multimedia output:"), None)
        self.g_synth = gtk.combo_box_new_text()
        if winmidi:
            for devname in winmidi.output_devices():
                #FIXME workaround of the bug
                # http://code.google.com/p/solfege/issues/detail?id=12
                if devname is None:
                    self.g_synth.append_text("FIXME bug #12")
                else:
                    self.g_synth.append_text(devname)
        self.g_synth.set_active(self.get_int('sound/synth_number') + 1)
        hbox.pack_start(self.g_device_radio, False)
        hbox.pack_start(self.g_synth, False)

        hbox = gu.bHBox(category_vbox, False)
        self.g_midiplayer_radio = gu.RadioButton(self.g_fakesynth_radio,
             _("Use _external MIDI player"), None)
        hbox.pack_start(self.g_midiplayer_radio, False)

        if self.get_string("sound/type") == "external-midiplayer":
            self.g_midiplayer_radio.set_active(True)
        elif self.get_string("sound/type") == "winsynth":
            self.g_device_radio.set_active(True)
        else:
            self.g_fakesynth_radio.set_active(True)

        gu.bButton(category_vbox, _("_Test"), self.on_apply_and_play_test_sound)
    def set_gui_from_config(self):
        if self.get_string("sound/type") == "fake-synth":
            self.g_fakesynth_radio.set_active(True)
        elif self.get_string("sound/type") == "external-midiplayer":
            self.g_midiplayer_radio.set_active(True)
        else:
            assert self.get_string("sound/type") in ("winsynth", "sequencer-device")
            self.g_device_radio.set_active(True)
        return -1
    def apply_and_close(self, w, response):
        if response ==  gtk.RESPONSE_DELETE_EVENT:
            self.set_gui_from_config()
        elif response == gtk.RESPONSE_HELP:
            solfege.app.handle_href("preferences-window.html")
            return
        else:
            if self.on_apply() == -1:
                self.set_gui_from_config()
                return
        self.hide()
    def on_apply_and_play_test_sound(self, *w):
        if self.on_apply() != -1:
            self.set_gui_from_config()
            self.play_midi_test_sound()
    def play_midi_test_sound(self):
        try:
            utils.play_music(r"""
            \staff\relative c{
              c16 e g c e, g c e g, c e g c4
            }
            \staff{
              c4 e g8 e c4
            }
            """, 130, 0, 100)
        # Here we are only cathing exceptions we know the MidiFileSynth
        # can raise. Maybe we should catch something from the Sequencer
        # synths too?
        except osutils.BinaryBaseException, e:
            solfege.win.display_error_message2(e.msg1, e.msg2)
    def on_apply(self, *v):
        """Returns -1 if sound init fails."""
        if soundcard.synth:
            soundcard.synth.close()
        if self.g_midiplayer_radio.get_active():
            soundcard.initialise_external_midiplayer(
                  cfg.get_string("sound/midi_player"))
            soundcard.synth.error_report_cb = solfege.win.display_error_message
        elif self.g_device_radio.get_active():
            try:
                if sys.platform == 'win32':
                    soundcard.initialise_winsynth(self.g_synth.get_active() - 1)
                else:
                    soundcard.initialise_devicefile(
                        self.g_device_file.child.get_text(),
                        self.g_synth_num.get_value_as_int())
            except (soundcard.SoundInitException, OSError, ImportError), e:
                solfege.app.display_sound_init_error_message(e)
                return -1
        else: # no sound
            assert self.g_fakesynth_radio.get_active()
            soundcard.initialise_using_fake_synth(0)
        if self.g_midiplayer_radio.get_active():
            self.set_string("sound/type", "external-midiplayer")
        elif self.g_device_radio.get_active():
            if sys.platform == "win32":
                self.set_string("sound/type", "winsynth")
            else:
                self.set_string("sound/type", "sequencer-device")
        else:
            assert self.g_fakesynth_radio.get_active()
            self.set_string("sound/type", "fake-synth")
        if sys.platform != 'win32':
            self.set_string("sound/device_file", self.g_device_file.child.get_text())
        if soundcard.synth.m_type_major not in ('Midifile', 'Fake'):
            self.set_int("sound/synth_number", soundcard.synth.m_devnum)
            # we set the spin just in case m_devnum was changed by the
            # soundcard setup code, if it was out of range
            if sys.platform != 'win32':
                self.g_synth_num.set_value(soundcard.synth.m_devnum)
            else:
                self.g_synth.set_active(soundcard.synth.m_devnum + 1)
