# GNU Solfege - free ear training software # Copyright (C) 2007 Tom Cato Amundsen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin ST, Fifth Floor, Boston, MA 02110-1301 USA import gobject import gtk import os import time import pickle import filesystem import gu import configureoutput from dataparser import Dataparser, DataparserException import lessonfile # The definition of a training set will be stored in a file in the # ~/.solfege/trainingsets class TrainingSetDialog(gu.EditorDialogBase): fileformat_version = 1 STORE_LESSON_ID = 0 STORE_TITLE = 1 STORE_COUNT = 2 STORE_REPEAT = 3 STORE_DELAY = 4 def __init__(self, app): gu.EditorDialogBase.__init__(self) self.savedir = os.path.join(filesystem.user_data(), "trainingsets") self.set_default_size(800, 300) self.m_app = app # This VBox will have 2 parts. # 1: the tool bar # 2: another container widget that has the content of a file self.g_vbox = gtk.VBox() self.add(self.g_vbox) self.setup_toolbar() # self.g_settings_box = gtk.VBox() self.g_settings_box.set_border_width(6) self.g_vbox.pack_start(self.g_settings_box, False) self.g_output = {} self.g_output['midi'] = gtk.RadioButton(None, _("MIDI")) self.g_output['wav'] = gtk.RadioButton(self.g_output['midi'], _("WAV")) self.g_output['mp3'] = gtk.RadioButton(self.g_output['midi'], _("MP3")) self.g_output['ogg'] = gtk.RadioButton(self.g_output['midi'], _("OGG")) hbox = gtk.HBox() hbox.set_spacing(6) self.g_settings_box.pack_start(hbox) hbox.pack_start(gtk.Label(_("Preferred output format:")), False) for s in ('midi', 'wav', 'mp3', 'ogg'): hbox.pack_start(self.g_output[s], False) #### self.g_liststore = gtk.ListStore( gobject.TYPE_STRING, # lesson_id gobject.TYPE_STRING, # visible exercise name gobject.TYPE_INT, # count gobject.TYPE_INT, # repeat gobject.TYPE_INT) # delay self.g_treeview = gtk.TreeView(self.g_liststore) self.g_treeview.set_size_request(400, 100) self.g_treeview.connect('cursor-changed', self.on_treeview_cursor_changed) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Title"), renderer, text=self.STORE_TITLE) self.g_treeview.append_column(column) renderer = gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self.on_count_edited) column = gtk.TreeViewColumn(_("Count"), renderer, text=self.STORE_COUNT) self.g_treeview.append_column(column) renderer = gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self.on_repeat_edited) column = gtk.TreeViewColumn(_("Repeat"), renderer, text=self.STORE_REPEAT) self.g_treeview.append_column(column) renderer = gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self.on_delay_edited) column = gtk.TreeViewColumn(_("Delay"), renderer, text=self.STORE_DELAY) self.g_treeview.append_column(column) self.g_vbox.pack_start(self.g_treeview) self.init_empty_file() self.show_all() def on_treeview_cursor_changed(self, treeview): self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(True) def on_count_edited(self, renderer, path, text): self._edit_col(2, path, text) def on_repeat_edited(self, renderer, path, text): self._edit_col(3, path, text) def on_delay_edited(self, renderer, path, text): self._edit_col(4, path, text) def _edit_col(self, col_num, path, text): """ This method does the real work when on_XXXX_edited is called. """ try: i = int(text) except ValueError: i = None if i is not None: iter = self.g_liststore.get_iter_from_string(path) self.g_liststore.set_value(iter, col_num, i) def on_remove_lesson_clicked(self, *w): path, column = self.g_treeview.get_cursor() assert path iter = self.g_liststore.get_iter(path) if iter: if self.g_liststore.remove(iter): self.g_treeview.set_cursor(self.g_liststore.get_path(iter)) elif path[0] > 0: self.g_treeview.set_cursor((path[0]-1,)) else: self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(False) else: print "NOt delete because not iter" print "ITER:", iter def on_add_lesson_clicked(self, button): menu = self.create_learning_tree_menu() menu.popup(None, None, None, 1, 0) def on_select_exercise(self, item): """ This method is called when the user has selected an exercise to add. """ lesson_id = item.get_data('lesson_id') module = self.m_app.lessonfile_manager.get(lesson_id, 'module') if module not in ('harmonicinterval', 'melodicinterval', 'idbyname'): print "Only harmonicinterval, melodicinterval and idbyname module exercises are working now. Ignoring..." return if module == 'idbyname': fn = self.m_app.lessonfile_manager.get(lesson_id, 'filename') p = lessonfile.LessonfileCommon(fn) p.parse_file(fn) if not [q for q in p.m_questions if q['music'].is_mpd_parsable()]: gu.dialog_ok(_("This lesson file cannot be exported because some of the music in the file are not parsable by the mpd module.")) return self.m_changed = True self.g_liststore.append(( lesson_id, self.get_lessonfile_title(lesson_id), 3, 3, 4)) def get_lessonfile_title(self, lesson_id): """ Return a string we use the name the lesson file in the GUI. """ return self.m_app.lessonfile_manager.get(lesson_id, 'title') \ + " (%s)" % self.m_app.lessonfile_manager.get(lesson_id, 'filename') def create_learning_tree_menu(self): """ Create and return a gtk.Menu object that has submenus that let us select all lessons on the learning tree. """ def create_submenu(menudata, parent_name): """ Menudata is a dict. Key we will use: 'name' 'children': list of dict or string (or maybe both?). """ if isinstance(menudata, list): menu = gtk.Menu() for lesson_id in menudata: if self.m_app.lessonfile_manager.get(lesson_id, 'module') not in ('melodicinterval', 'harmonicinterval', 'idbyname'): continue # We don't want to add these lesson files because we know # that they cannot be exported. It would be better # to catch these with a more generit algorithm, but # then we would have to parse all the files, and that # would be too slow. if lesson_id in ( # melodic-interval-self-config "f62929dc-7122-4173-aad1-4d4eef8779af", # harmonic-interval-self-config "466409e7-9086-4623-aff0-7c27f7dfd13b", # the csound-fifth-* files: "b465c807-d7bf-4e3a-a6da-54c78d5b59a1", "aa5c3b18-664b-4e3d-b42d-2f06582f4135", "5098fb96-c362-45b9-bbb3-703db149a079", "3b1f57e8-2983-4a74-96da-468aa5414e5e", "a06b5531-7422-4ea3-8711-ec57e2a4ce22", "e67c5bd2-a275-4d9a-96a8-52e43a1e8987", "1cadef8c-859e-4482-a6c4-31bd715b4787", ): continue i = gtk.MenuItem( self.m_app.lessonfile_manager.get(lesson_id, 'title')) i.set_data('lesson_id', lesson_id) i.set_data('menus', parent_name) i.connect('activate', self.on_select_exercise) menu.append(i) return menu item = gtk.MenuItem(_(menudata['name'])) menu = gtk.Menu() for m in menudata['children']: i = gtk.MenuItem(_(m['name'])) menu.append(i) i.set_submenu(create_submenu(m['children'], parent_name + [_(m['name'])])) item.set_submenu(menu) return item menu = gtk.Menu() for m in self.m_app.m_ui.m_tree.m_menus: menu.append(create_submenu(m, [_(m['name'])])) menu.show_all() self._menu_hide_stuff(menu) return menu def _menu_hide_stuff(self, menu): """ Hide the menu if it has no menu items, or all menu items are hidden. """ for sub in menu.get_children(): assert isinstance(sub, gtk.MenuItem) if sub.get_submenu(): self._menu_hide_stuff(sub.get_submenu()) if not [c for c in sub.get_submenu().get_children() if c.get_property('visible')]: sub.hide() def init_empty_file(self): self.m_changed = False self.m_savetime = time.time() self.set_title(self._get_a_filename()) self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(False) def setup_toolbar(self): self.g_actiongroup.add_actions([ ('Export', gtk.STOCK_EXECUTE, _("Export"), None, None, self.on_export), ('Add', gtk.STOCK_ADD, None, None, None, self.on_add_lesson_clicked), ('Remove', gtk.STOCK_REMOVE, None, None, None, self.on_remove_lesson_clicked), ]) self.g_ui_manager.insert_action_group(self.g_actiongroup, 0) uixml = """ """ self.g_ui_manager.add_ui_from_string(uixml) self.g_vbox.pack_start(self.g_ui_manager.get_widget("/ExportToolbar"), False) self.g_ui_manager.get_widget("/ExportToolbar").set_style(gtk.TOOLBAR_BOTH) def on_show_help(self, widget): self.m_app.handle_href("trainingset-editor.html") def get_trainingset_filenames(self): #FIXME we need to know 100% sure that we will get #unicode strings from this method try: return os.listdir(os.path.join(filesystem.user_data(), "trainingsets")) except OSError, e: if e.errno != 2: print "warning: errno != 2" return [] def load_file(self, filename): p = Dataparser({'yes': True, 'no': False}, {}) p.parse_file(filename) s = p.globals.setdefault('output_format', 'midi') s = s.lower() if s in self.g_output: self.g_output[s].set_active(True) else: # MIDI is the default format self.g_output['midi'].set_active(True) self.m_filename = filename for lesson in p.blocklists.setdefault('lesson', []): self.g_liststore.append((lesson['lesson_id'], self.get_lessonfile_title(lesson['lesson_id']), lesson['count'], lesson['repeat'], lesson['delay'])) self.set_title(self.m_filename) def save(self): """ Save the file to a file named by self.m_filename """ assert self.m_filename f = open(self.m_filename, 'w') print >> f, "# Training set definition file for GNU Solfege %s" % configureoutput.VERSION_STRING print >> f, "\nfileformat_version = %i" % self.fileformat_version print >> f, "output_format = \"%s\"" % [k for k in self.g_output if self.g_output[k].get_active()][0] iter = self.g_liststore.get_iter_first() while iter: print >> f, "lesson {" lesson_id = self.g_liststore.get_value(iter, self.STORE_LESSON_ID) if self.m_app.lessonfile_manager.get(lesson_id, 'title'): print >> f, " # %s (%s)" % ( self.m_app.lessonfile_manager.get(lesson_id, 'title'), self.m_app.lessonfile_manager.get(lesson_id, 'filename') ) print >> f, ' lesson_id = "%s"' % lesson_id print >> f, ' count = %i' \ % self.g_liststore.get_value(iter, self.STORE_COUNT) print >> f, ' repeat = %i' \ % self.g_liststore.get_value(iter, self.STORE_REPEAT) print >> f, ' delay = %i' \ % self.g_liststore.get_value(iter, self.STORE_DELAY) print >> f, "}\n" iter = self.g_liststore.iter_next(iter) f.close() self.m_changed = False self.m_savetime = time.time() def on_export(self, widget): export_to = \ self.select_empty_directory(_("Select where to export the files")) if not export_to: return progress_dialog = gtk.Dialog(_("Exporting training set"), self, 0, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) progress_dialog.show() label = gtk.Label() label.set_markup('%s' % _("Export training set")) label.show() progress_dialog.vbox.pack_start(label, False) def _cancel(widget, response): self.m_app.m_abort_export = True progress_dialog.connect('response', _cancel) progress_bar = gtk.ProgressBar() progress_bar.show() progress_dialog.vbox.pack_start(progress_bar) # We have to make a version of the data without gtk widgets v = [] iter = self.g_liststore.get_iter_first() while iter: v.append({ 'lesson_id': \ self.g_liststore.get_value(iter, self.STORE_LESSON_ID), 'count': self.g_liststore.get_value(iter, self.STORE_COUNT), 'repeat': self.g_liststore.get_value(iter, self.STORE_REPEAT), 'delay': self.g_liststore.get_value(iter, self.STORE_DELAY), }) iter = self.g_liststore.iter_next(iter) output_format = [k for k in self.g_output if self.g_output[k].get_active()][0] progress_dialog.queue_draw() while gtk.events_pending(): gtk.main_iteration(0) time.sleep(0.1) while gtk.events_pending(): gtk.main_iteration(0) for prog in self.m_app.export_training_set(v, export_to, output_format): progress_bar.set_fraction(prog) while gtk.events_pending(): gtk.main_iteration(0) progress_dialog.destroy()