# 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 logging.handlers
from solfege import optionparser
import solfege
opt_parser = optionparser.SolfegeOptionParser()
options, args = opt_parser.parse_args()
if options.debug:
# We import all exercise modules here even though it is not necessary
# since the modules are loaded one by one when needed. But by importing
# all here, we catch SyntaxErrors at once.
from solfege.exercises import *
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
from solfege import winlang
from solfege import runtime
from solfege import buildinfo
from solfege.exceptiondialog import ExceptionDialog
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/>.
"""
from solfege import osutils
from solfege import application
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)
from solfege import utils
from solfege 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"))
from solfege import tracebackwindow
# redirect error messages to a window that will popup if
# something bad happens.
sys.stderr = tracebackwindow.TracebackWindow(options.show_gtk_warnings)
from solfege.configwindow import ConfigWindow
from solfege.mpd.musicdisplayer import MusicDisplayer
from solfege import gu
from solfege import cfg
from solfege import stock
from solfege import lessonfile
from solfege import lessonfilegui
from solfege import abstract
from solfege import frontpage
from solfege import fpeditor
from solfege.trainingsetdlg import TrainingSetDialog
from solfege.practisesheetdlg import PractiseSheetDialog
from solfege import filesystem
from solfege import statistics
class MusicViewerWindow(gtk.Dialog):
def __init__(self):
gtk.Dialog.__init__(self)
self.set_default_size(500, 300)
self.g_music_displayer = MusicDisplayer()
self.vbox.pack_start(self.g_music_displayer)
b = gu.bButton(self.action_area, _("Close"), solfege.win.close_musicviewer)
b.grab_focus()
self.connect('destroy', solfege.win.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):
"""
Base class for the classes that display the front page and the
page where we select exerises or tests.
"""
def __init__(self):
gtk.ScrolledWindow.__init__(self)
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self._on_focus_in_blocked = False
self.m_min_width = 400
self.m_min_height = 300
self.set_size_request(self.m_min_width, self.m_min_height)
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):
if self.m_linkbuttons:
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
def adjust_scrolledwin_size(self):
# We do set_size_request on the view to make the window so wide
# that we avoid a horizontal scroll bar.
w = self.g_box.size_request()[0]
# It should be possible to check if the vscrollbar is visible, and
# only reserve space for it. But I cannot get that to works. I find
# that self.get_vscrollbar().props.visible is updated only part
# of the time.
w += self.get_vscrollbar().size_request()[0]
# FIXME I don't understand why we have to add the extra
# two pixels to avoid the horizontal scrollbar in all cases.
w += self.style_get_property("scrollbar-spacing") + 2
if w < self.m_min_width:
w = self.m_min_width
self.set_size_request(w, self.m_min_height)
class ExerciseView(SelectWinBase):
max_exercise_label_width = int(gtk.gdk.Screen().get_width() * 0.90)
def __init__(self):
SelectWinBase.__init__(self)
app = solfege.app
self.g_box = gtk.VBox()
self.g_box.set_border_width(gu.hig.SPACE_MEDIUM)
self.add_with_viewport(self.g_box)
self.g_searchbox = gtk.HBox()
self.g_box.pack_start(self.g_searchbox, False, padding=gu.hig.SPACE_MEDIUM)
self.g_searchentry = gtk.Entry()
self.g_searchbox.pack_start(self.g_searchentry, True)
self.g_searchentry.connect('activate', self.on_search)
gu.bButton(self.g_searchbox, _("Search"), callback=self.on_search, expand=False)
self.show_all()
def _display_data(self, page, display_only_tests=False,
is_search_result=False, show_topics=False):
"""
display_only_tests should be True when we are browsing available tests.
This will validate statistics for each lesson and display the test result.
"""
if not is_search_result:
self.m_page = page
try:
self.g_hbox.destroy()
except AttributeError:
pass
self.g_hbox = gu.bHBox(self.g_box)
self.g_hbox.set_spacing(gu.hig.SPACE_MEDIUM)
label = None
for column in page:
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
assert isinstance(column, frontpage.Column)
first = True
vbox = gtk.VBox()
vbox.set_spacing(gu.hig.SPACE_MEDIUM)
self.g_hbox.pack_start(vbox, False)
for sect_id, linklist in enumerate(column):
if isinstance(linklist, frontpage.Paragraph):
label = gtk.Label(linklist.m_text)
label.set_line_wrap(True)
label.set_alignment(0.0, 0.5)
vbox.pack_start(label, False)
continue
assert isinstance(linklist, frontpage.LinkList)
if (display_only_tests
and not frontpage._TreeCommon.tests_in_sub(linklist)):
continue
linkbox = gtk.VBox()
vbox.pack_start(linkbox, False)
heading = gtk.Label("<b>%s</b>" % _no_xgettext(linklist.m_name))
heading.set_alignment(0.0, 0.5)
heading.set_use_markup(True)
linkbox.pack_start(heading, False)
for idx, link in enumerate(linklist):
if isinstance(self, TestsView) and not frontpage._TreeCommon.tests_in_sub(link):
continue
if isinstance(link, frontpage.Page):
label = gu.ClickableLabel(_no_xgettext(link.m_name))
label.connect('clicked', self.on_page_link_clicked, link)
else:
assert isinstance(link, unicode), type(link)
if display_only_tests:
solfege.db.validate_stored_statistics(link)
try:
labeltxt = lessonfile.infocache.get(link, 'title')
label = gu.ClickableLabel(_no_xgettext(labeltxt))
if show_topics:
topic = solfege.app.m_frontpage_data.get_topic_of_lesson_file(link)
if topic and topic not in labeltxt:
label.add_heading(topic)
label.connect('clicked', self.on_link_clicked, link)
except lessonfile.InfoCache.FileNotFound:
label = gu.ClickableLabel(_(u"%s was not found") % link)
label.make_warning()
except lessonfile.InfoCache.FileNotLessonfile:
label = gu.ClickableLabel(_(u"Failed to parse %s") % link)
label.make_warning()
if first:
label.set_data('first', True)
first = False
w = label.size_request()[0]
if w > self.max_exercise_label_width:
w = self.max_exercise_label_width
if isinstance(link, unicode):
txt = _(lessonfile.infocache.get(link, "title"))
else:
txt = link.m_name
label.set_tooltip_text(txt)
label.set_size_request(w / len(page), -1)
self.m_linkbuttons.append(label)
label.connect('focus-in-event', self.on_focus_in)
if display_only_tests:
box = gtk.HBox()
box.pack_start(label)
if isinstance(link, unicode):
passed, result = solfege.db.get_test_status(link)
if passed == True:
box.pack_start(gtk.Label(_("passed, %.1f%%") % (result * 100)))
if passed == False:
box.pack_start(gtk.Label(_("failed, %.1f%%") % (result * 100)))
# do nothing if passed == None since
linkbox.pack_start(box, True)
else:
linkbox.pack_start(label, True)
sizegroup.add_widget(label)
if label:
label.set_data('last', True)
self.g_hbox.show_all()
self.adjust_scrolledwin_size()
self.get_vadjustment().value = 0.0
def on_page_link_clicked(self, btn, link):
self.g_searchbox.hide()
self.display_data(link)
def on_link_clicked(self, w, filename):
solfege.app.practise_lessonfile(filename)
def display_search_result(self, searchfor, result, result_C, display_only_tests=False):
self._display_data(
frontpage.Page(u'',
frontpage.Column([
frontpage.LinkList(
_("Search results for %s:" % self.g_searchentry.get_text()),
result),
frontpage.LinkList(
_("C-locale search results for %s:" % self.g_searchentry.get_text()),
result_C),
])))
def on_end_practise(self, w=None):
pass
def _search(self, substring, C_locale, only_tests):
"""
substring - the string to search for
C_locale - True if we should search in the untranslated titles
only_tests - True if we should only return exercises that have
tests
"""
logging.debug("search: '%s'" % substring)
match_func = {
False: lambda filename: substring in _no_xgettext(lessonfile.infocache.get(filename, 'title')).lower(),
True: lambda filename: substring in lessonfile.infocache.get(filename, 'title').lower()
}[C_locale]
test_filter = {
False: lambda filename: True,
True: lambda filename: lessonfile.infocache.get(filename, 'test')
}[C_locale]
page = frontpage.Page(listitems=frontpage.Column())
cur_topic = None
# the last topic appended to the page
last_topic = None
found = set()
for child in self.m_page.iterate_flattened():
if isinstance(child, frontpage.LinkList):
cur_topic = child
if isinstance(child, unicode):
try:
if (match_func(child) and test_filter(child)) and child not in found:
if cur_topic != last_topic:
page[0].append(frontpage.LinkList(cur_topic.m_name))
last_topic = cur_topic
found.add(child)
page[0][-1].append(child)
except lessonfile.infocache.InfoCacheException:
# Ignore missing lesson files and files with errors
pass
return page
class FrontPage(ExerciseView):
def display_data(self, data, display_only_tests=False,
is_search_result=False, show_topics=False):
self._display_data(data, display_only_tests, is_search_result,
show_topics)
self.g_searchentry.grab_focus()
def on_search(self, *button):
search_for = self.g_searchentry.get_text().lower()
logging.debug("FrontPage.on_search '%s'" % search_for)
page = self._search(search_for, False, False)
page_C = self._search(search_for, True, False)
page_C.m_name = _('Search untranslated lesson titles')
if not page_C.is_empty():
page[0].append(frontpage.LinkList(_('Too few matches?'),
listitems=[page_C]))
self.display_data(page, is_search_result=True)
class TestsView(ExerciseView):
def on_link_clicked(self, w, filename):
solfege.app.test_lessonfile(filename)
def display_data(self, data=None, is_search_result=False, show_topics=False):
self._display_data(data, True, is_search_result, show_topics)
self.g_searchentry.grab_focus()
def on_search(self, *button):
search_for = self.g_searchentry.get_text().lower()
page = self._search(search_for, False, True)
page_C = self._search(search_for, True, True)
page_C.m_name = u'Search exercises without translating them'
if not page_C.is_empty():
page[0].append(frontpage.LinkList(_('Too few matches?'),
listitems=[page_C]))
self.display_data(page, is_search_result=True)
class SearchView(ExerciseView):
def __init__(self):
ExerciseView.__init__(self)
page = """FileHeader(1,
Page(u'', [
Column([
Paragraph(u'Search the exercise titles of all lesson files found by the program, not just the active front page with sub pages.'),
]),
])
)"""
p = frontpage.parse_tree(page)
self.display_data(frontpage.parse_tree(page))
self.g_searchentry.show()
def display_data(self, data):
self._display_data(data)
self.g_searchentry.grab_focus()
def on_search(self, *button):
search_for = self.g_searchentry.get_text().lower()
lessonfile.infocache.update_modified_files()
self.display_search_result(self.g_searchentry.get_text(), [
filename for filename in lessonfile.infocache._data
if search_for in _(lessonfile.infocache.get(filename, 'title')).lower()
], [
filename for filename in lessonfile.infocache._data
if search_for in lessonfile.infocache.get(filename, 'title').lower()
])
class MainWin(gtk.Window, cfg.ConfigUtils):
default_front_page = os.path.join(lessonfile.exercises_dir, 'learningtree.txt')
debug_front_page = os.path.join(lessonfile.exercises_dir, 'debugtree.txt')
def __init__(self, options, datadir):
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)
pixbuf = self.render_icon('solfege-icon', gtk.ICON_SIZE_DIALOG)
gtk.window_set_default_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('delete-event', self.quit_program)
self.connect('key_press_event', self.on_key_press_event)
self.g_about_window = 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.g_ui_manager = gtk.UIManager()
self.m_action_groups = {
'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.on_frontpage_changed()
self.display_frontpage()
def get_view(self):
"""
Return the view that is currently visible.
Raise KeyError if no view has yet been added.
"""
return self.box_dict[self.m_viewer]
def add_view(self, view, name):
"""
Hide the current view.
Add and view the new view.
"""
assert name not in self.box_dict
if self.m_viewer:
self.get_view().hide()
self.box_dict[name] = view
self.main_box.pack_start(self.box_dict[name])
self.box_dict[name].show()
self.m_viewer = name
def show_view(self, name):
"""
Return False if the view does not exist.
Hide the current visible view, show the view named 'name' and
return True.
"""
if name not in self.box_dict:
return False
self.get_view().hide()
self.m_viewer = name
self.box_dict[name].show()
return True
def change_frontpage(self, filename):
"""
Change to a different frontpage file.
"""
self.set_string('app/frontpage', filename)
self.on_frontpage_changed()
def on_frontpage_changed(self, *v):
"""
Load the frontpage data from the file specified by the config
variable "app/frontpage"
"""
filename = self.get_string("app/frontpage")
if filename == self.debug_front_page and not solfege.app.m_options.debug:
self.set_string("app/frontpage", self.default_front_page)
filename = self.default_front_page
if not os.path.isfile(filename):
filename = self.default_front_page
try:
solfege.app.m_frontpage_data = frontpage.load_tree(filename)
except Exception, e:
if splash_win:
splash_win.hide()
solfege.app.m_frontpage_data = frontpage.load_tree(self.default_front_page)
self.set_string('app/frontpage', self.default_front_page)
gu.dialog_ok(_("Loading front page '%s' failed. Using default page." % filename),
secondary_text = "\n".join(traceback.format_exception(*sys.exc_info())))
if splash_win:
splash_win.show()
self.display_frontpage()
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')),
('FrontPagesMenu', None, _('Sele_ct Front Page')),
('TheoryIntervals', None, _('_Intervals'), None, None,
lambda o: solfege.app.handle_href('theory-intervals.html')),
('TreeEditor', None, _('_Edit Front Page'), None, None,
self.do_tree_editor),
('ExportTrainingSet', None, _(u'E_xport Exercises to Audio Files'), None, None,
self.new_training_set_editor),
('EditPractiseSheet', None, _(u'Ear Training Test Pri_ntout'), None, None,
self.new_practisesheet_editor),
('OpenPreferencesWindow', 'gtk-preferences', None, '<ctrl>F12', None,
self.open_preferences_window),
('HelpMenu', None, _('_Help')),
('Search', 'gtk-search', _('_Search Exercises'), '<ctrl>F', None,
self.on_search_all_exercises),
('FrontPage', None, _('_Front Page'), 'F5', None,
lambda w: self.display_frontpage()),
('TestsPage', None, _('_Tests Page'), 'F6', None,
lambda w: self.display_testpage()),
('RecentExercises', None, _('_Recent Exercises'), 'F7', None,
self.display_recent_exercises),
('RecentTests', None, _('_Recent Tests'), 'F8', None,
self.display_recent_tests),
('UserExercises', None, _('_User Exercises'), 'F9', None,
self.display_user_exercises),
('HelpHelp', 'gtk-help', _('_Help on the current exercise'), 'F1', None,
lambda o: solfege.app.please_help_me()),
('HelpTheory', None, _('_Music theory on the current exercise'), 'F3', None, lambda o: solfege.app.show_exercise_theory()),
('HelpIndex', None, _('_User manual'), None, None,
lambda o: solfege.app.handle_href('index.html')),
('HelpShowPathInfo', None, _('_File locations'), None,
None, self.show_path_info),
('HelpOnline', None, _('_Mailing lists, web page etc.'), None, None,
lambda o: solfege.app.handle_href('online-resources.html')),
('HelpDonate', None, _('_Donate'), None, None,
lambda o: solfege.app.handle_href('http://www.solfege.org/Main/Donate')),
('HelpReportingBugs', None, _('Reporting _bugs'), None, None,
lambda o: solfege.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())
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_frontpage_menu(self):
"""
Create, or update if already existing, the submenu that let the
user choose which front page file to display.
"""
if self.m_frontpage_merge_id:
self.g_ui_manager.remove_ui(self.m_frontpage_merge_id)
actions = []
old_dir = None
s = "<menubar name='Menubar'><menu action='FileMenu'><menu action='FrontPagesMenu'>"
for fn in frontpage.get_front_pages_list(solfege.app.m_options.debug):
if not frontpage.may_be_frontpage(fn):
continue
cur_dir = os.path.split(fn)[0]
if old_dir != cur_dir:
s += '<separator name="sep@%s"/>' % fn
old_dir = cur_dir
s += "<menuitem action='%s'/>\n" % fn
if not self.m_action_groups['NotExit'].get_action(fn):
actions.append((fn, None, lessonfile.infocache.frontpage.get(fn, 'title'), None, fn,
lambda o, f=fn: self.change_frontpage(f)))
else:
action = self.m_action_groups['NotExit'].get_action(fn)
action.props.label = lessonfile.infocache.frontpage.get(fn, 'title')
s += "</menu></menu></menubar>"
self.m_action_groups['NotExit'].add_actions(actions)
self.m_frontpage_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."""
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)
# statistics.sqlite
# win32 solfegerc
# win32 langenviron.txt
box2.pack_start(gu.hig_label_widget(_("Solfege application data:"), gtk.Label(filesystem.app_data()), sizegroup))
box2.pack_start(gu.hig_label_widget(_("Solfege user data:"), gtk.Label(filesystem.user_data()), sizegroup))
box2.pack_start(gu.hig_label_widget(_("Solfege config file:"), gtk.Label(filesystem.rcfile()), sizegroup))
box2.pack_start(gu.hig_label_widget(_("Solfege installation directory:"), gtk.Label(os.getcwdu()), sizegroup))
box2.pack_start(gu.hig_label_widget(_("User manual in HTML format:"), gtk.Label(os.path.join(os.getcwdu(), "help")), sizegroup))
box2.pack_start(gu.hig_label_widget("gtk:", gtk.Label(str(gtk)), sizegroup))
box2.pack_start(gu.hig_label_widget("PYTHONHOME", gtk.Label(os.environ.get('PYTHONHOME', 'Not defined')), 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):
"""
Open a learning tree editor editing the current front page.
"""
fpeditor.Editor.edit_file(self.get_string("app/frontpage"))
def post_constructor(self):
global splash_win
self.m_frontpage_merge_id = None
self.create_frontpage_menu()
self.g_ui_manager.add_ui_from_file("help-menu.xml")
if solfege.app.m_sound_init_exception is not None:
if splash_win:
splash_win.destroy()
splash_win = None
solfege.app.display_sound_init_error_message(solfege.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'))
# 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.makedirs(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/FrontPagesMenu")
item.connect('activate', lambda s: self.create_frontpage_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 activate_exercise(self, module, urlobj=None):
self.show_view(module)
# We need this test because not all exercises use a notebook.
if self.get_view().g_notebook:
if urlobj and urlobj.action in ['practise', 'config', 'statistics']:
self.get_view().g_notebook.set_current_page(
['practise', 'config', 'statistics'].index(urlobj.action))
else:
self.get_view().g_notebook.set_current_page(0)
self.set_title("Solfege - " + self.get_view().m_t.m_P.header.title)
def display_docfile(self, fn, anchor):
"""
Display the HTML file named by fn in the help browser window.
"""
for lang in solfege.app.m_userman_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))
def display_user_exercises(self, w):
col = frontpage.Column()
page = frontpage.Page(_('User exercises'), col)
curdir =None
for filename in lessonfile.infocache.iter_user_files():
dir, fn = os.path.split(filename)
if dir != curdir:
curdir = dir
linklist = frontpage.LinkList(dir)
col.append(linklist)
linklist.append(filename)
# Move ~/.solfege/exercises/user/lesson-files first in the list
# since we refer to this directory in the user manual
for idx, c in enumerate(col):
if c[0].startswith(os.path.join(filesystem.user_data(), u"exercises", u"user", u"lesson-files")):
col.insert(0, c)
del col[idx+1]
break
else:
linklist = frontpage.LinkList(os.path.join(filesystem.user_data(), u"exercises", u"user", u"lesson-files"))
col.insert(0, linklist)
if os.path.isdir(filesystem.user_lessonfiles()):
linklist = None
# Added just to be nice with people not moving their files from
# pre 3.15.3 location:
for filename in os.listdir(filesystem.user_lessonfiles()):
if not linklist:
linklist = frontpage.LinkList(filesystem.user_lessonfiles())
linklist.append(os.path.join(filesystem.user_lessonfiles(), filename))
# only display the linklist if there are any files.
if linklist:
col.append(linklist)
self.display_frontpage(page)
def display_recent_exercises(self, w):
data = frontpage.Page(_('Recent exercises'),
[frontpage.Column(
[frontpage.LinkList(_('Recent exercises'),
solfege.db.recent(8))])])
self.display_frontpage(data, show_topics=True)
self.get_view().g_searchbox.hide()
def display_recent_tests(self, w):
data = frontpage.Page(_('Recent tests'),
[frontpage.Column(
[frontpage.LinkList(_('Recent tests'),
solfege.db.recent_tests(8))])])
self.display_testpage(data, show_topics=True)
self.get_view().g_searchbox.hide()
def display_testpage(self, data=None, show_topics=False):
"""
Display the front page of the data in solfege.app.m_frontpage_data
"""
self.set_title("GNU Solfege - tests")
if not self.show_view('testspage'):
self.add_view(TestsView(), 'testspage')
self.get_view().g_searchbox.show()
if not data:
data = solfege.app.m_frontpage_data
self.get_view().display_data(data, show_topics=show_topics)
def on_search_all_exercises(self, widget=None):
self.set_title("GNU Solfege")
if not self.show_view('searchview'):
self.add_view(SearchView(), 'searchview')
def display_frontpage(self, data=None, show_topics=False):
"""
Display the front page of the data in solfege.app.m_frontpage_data
"""
self.set_title("GNU Solfege")
if not self.show_view('frontpage'):
self.add_view(FrontPage(), 'frontpage')
self.get_view().g_searchbox.show()
if not data:
data = solfege.app.m_frontpage_data
self.get_view().display_data(data, show_topics=show_topics)
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
self.get_view().hide()
m = __import__("solfege.exercises.%s" % teacher.m_exname, fromlist=("solfege.exercises.%s" % teacher.m_exname,), level=0)
self.add_view(m.Gui(teacher), teacher.m_exname)
def on_key_press_event(self, widget, event):
self.get_view().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.g_config_window.show()
else:
self.g_config_window.show()
def quit_program(self, *w):
can_quit = True
for dlg in gu.EditorDialogBase.instance_dict.values():
if dlg.close_window():
dlg.destroy()
else:
can_quit = False
break
if can_quit:
solfege.app.quit_program()
gtk.main_quit()
else:
return True
def display_in_musicviewer(self, music):
if not self.g_musicviewer_window:
self.g_musicviewer_window = MusicViewerWindow()
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.get_view()):
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.g = self.get_view().g_notebook.get_nth_page(0)
self.get_view().g_notebook.get_nth_page(0).reparent(self.main_box)
self.get_view().g_notebook.hide()
self.get_view().enter_test_mode()
def exit_test_mode(self):
solfege.app.m_test_mode = False
self.m_action_groups['NotExit'].set_sensitive(True)
box = gtk.VBox()
self.get_view().g_notebook.insert_page(box, gtk.Label(_("Practise")), 0)
self.g.reparent(box)
self.get_view().g_notebook.show()
self.get_view().g_notebook.get_nth_page(0).show()
self.get_view().g_notebook.set_current_page(0)
self.get_view().exit_test_mode()
def new_training_set_editor(self, widget):
dlg = TrainingSetDialog()
dlg.show_all()
def new_practisesheet_editor(self, widget):
dlg = PractiseSheetDialog()
dlg.show_all()
import locale
locale.setlocale(locale.LC_NUMERIC, "C")
# check_rcfile has to be called before and
# functions that use the cfg module.
application.check_rcfile()
cfg.set_bool('config/no_random', bool(options.no_random))
lessonfile.infocache = lessonfile.InfoCache()
if splash_win:
if not os.path.exists(statistics.DB.get_statistics_filename()):
splash_win.show_progress(_("Importing old statistics"))
def f(s):
if splash_win:
splash_win.show_progress(s)
solfege.db = statistics.DB(f)
if splash_win:
splash_win.show_progress(_("Creating application window"))
def start_app(datadir):
global splash_win
solfege.app = application.SolfegeApp(options)
solfege.win = w = MainWin(options, datadir)
solfege.app.setup_sound()
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
# We parse all lesson files when we are idle to save a half a
# second the first time the user searches all lesson files using
# Ctrl-F.
lessonfile.infocache.parse_all_files(True)
gtk.main()
|