# vim: set fileencoding=utf-8 :
# GNU Solfege - free ear training software
# Copyright (C) 2009 Tom Cato Amundsen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
import logging
import os
import StringIO
import subprocess
import sys
import time
import weakref
import gobject
import gtk
if __name__ == '__main__':
from solfege import i18n
i18n.setup(".", "C")
import solfege.statistics
solfege.db = solfege.statistics.DB()
import solfege
from solfege import cfg
from solfege import filesystem
from solfege import gu
from solfege import frontpage
from solfege import lessonfile
from solfege import lessonfilegui
from solfege import osutils
class LessonFilePreviewWidget(gtk.VBox):
def __init__(self, model):
gtk.VBox.__init__(self)
self.m_model = model
self.set_size_request(200, 200)
l = gtk.Label()
l.set_alignment(0.0, 0.5)
l.set_markup("<b>Title:</b>")
self.pack_start(l, False)
self.g_title = gtk.Label()
self.g_title.set_alignment(0.0, 0.5)
self.pack_start(self.g_title, False)
l = gtk.Label()
l.set_alignment(0.0, 0.5)
l.set_markup("<b>Module:</b>")
self.pack_start(l, False)
self.g_module = gtk.Label()
self.g_module.set_alignment(0.0, 0.5)
self.pack_start(self.g_module, False)
l = gtk.Label()
l.set_alignment(0.0, 0.5)
l.set_markup("<b>Used in topcis:</b>")
self.pack_start(l, False)
self.g_topic_box = gtk.VBox()
self.pack_start(self.g_topic_box, False)
self.show_all()
def update(self, dlg):
fn = dlg.get_preview_filename()
if fn:
fn = gu.decode_filename(fn)
for child in self.g_topic_box.get_children():
child.destroy()
fn = lessonfile.mk_uri(fn)
try:
self.set_sensitive(True)
self.g_title.set_text(lessonfile.infocache.get(fn, 'title'))
self.g_module.set_text(lessonfile.infocache.get(fn, 'module'))
self.g_ok_button.set_sensitive(True)
for x in self.m_model.iterate_topics_for_file(fn):
l = gtk.Label(x)
l.set_alignment(0.0, 0.5)
self.g_topic_box.pack_start(l, False)
if not self.g_topic_box.get_children():
l = gtk.Label(u"-")
l.set_alignment(0.0, 0.5)
self.g_topic_box.pack_start(l, False)
except (lessonfile.InfoCache.FileNotFound,
lessonfile.InfoCache.FileNotLessonfile), e:
self.g_title.set_text(u'')
self.g_module.set_text(u'')
self.g_ok_button.set_sensitive(False)
self.set_sensitive(False)
self.show_all()
return True
class SelectLessonFileDialog(gtk.FileChooserDialog):
def __init__(self, parent):
gtk.FileChooserDialog.__init__(self, _("Select lesson file"),
parent=parent,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,))
self.set_select_multiple(True)
pv = LessonFilePreviewWidget(parent.m_model)
pv.g_ok_button = self.add_button("gtk-ok", gtk.RESPONSE_OK)
pv.g_ok_button.set_sensitive(False)
pv.show()
self.set_preview_widget(pv)
self.connect('selection-changed', pv.update)
def editor_of(obj):
"""
Return the toplevel page, the one that is a Editor object.
"""
p = obj
while not isinstance(p, Editor):
p = p.m_parent
return p
def parent_page(obj):
"""
Return the parent page of obj. Return None if this is the toplevel page.
"""
p = obj
while True:
p = p.m_parent
if isinstance(p, Page):
return p
if p is None:
return None
class Section(gtk.VBox):
"""
A section consists of a heading and a list of links.
self.g_link_box is a vbox that contains the links.
"""
def __init__(self, model, parent):
gtk.VBox.__init__(self)
self.m_model = model
self.m_parent = parent
assert isinstance(model, pd.LinkList)
hbox = gtk.HBox()
hbox.set_spacing(6)
self.pack_start(hbox, False)
# This is displayed and used when we edit the heading
self.g_heading_entry = gtk.Entry()
self.g_heading_entry.set_no_show_all(True)
hbox.pack_start(self.g_heading_entry)
self.g_heading = gtk.Label()
self.g_heading.set_alignment(0.0, 0.5)
# FIXME escape m_name
self.g_heading.set_markup("<b>%s</b>" % model.m_name)
hbox.pack_start(self.g_heading, False)
#
button_hbox = gtk.HBox()
button_hbox.set_spacing(0)
hbox.pack_start(button_hbox, False)
im = gtk.Image()
im.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
button = gtk.Button()
button.add(im)
button.connect('clicked', self.on_edit_heading)
button_hbox.pack_start(button, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
button = gtk.Button()
button.add(im)
button.connect('button-release-event', self.on_add)
button_hbox.pack_start(button, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
button = gtk.Button()
button.add(im)
button.connect('button-release-event', self.on_remove)
button_hbox.pack_start(button, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_CUT, gtk.ICON_SIZE_MENU)
b = gtk.Button()
b.add(im)
b.connect('clicked', self.on_cut)
button_hbox.pack_start(b, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_PASTE, gtk.ICON_SIZE_MENU)
b = gtk.Button()
b.add(im)
b.connect('clicked', self.on_paste, -1)
Editor.clipboard.register_paste_button(b, (pd.LinkList, pd.Page, unicode))
button_hbox.pack_start(b, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
self.g_move_down_btn = gtk.Button()
self.g_move_down_btn.add(im)
self.g_move_down_btn.connect('clicked',
self.m_parent.move_section_down, self)
button_hbox.pack_start(self.g_move_down_btn, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
self.g_move_up_btn = gtk.Button()
self.g_move_up_btn.add(im)
self.g_move_up_btn.connect('clicked',
self.m_parent.move_section_up, self)
button_hbox.pack_start(self.g_move_up_btn, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_MENU)
self.g_move_left_btn = gtk.Button()
self.g_move_left_btn.add(im)
self.g_move_left_btn.connect('clicked',
parent.m_parent.on_move_section_left, self)
button_hbox.pack_start(self.g_move_left_btn, False)
#
im = gtk.Image()
im.set_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_MENU)
self.g_move_right_btn = gtk.Button()
self.g_move_right_btn.add(im)
self.g_move_right_btn.connect('clicked',
parent.m_parent.on_move_section_right, self)
button_hbox.pack_start(self.g_move_right_btn, False)
#
self.g_link_box = gtk.VBox()
self.pack_start(self.g_link_box, False)
for link in self.m_model:
self.g_link_box.pack_start(self.create_linkrow(link))
# The button to click to add a new link
hbox = gtk.HBox()
self.pack_start(hbox)
def on_edit_heading(self, btn):
self.g_heading_entry.set_text(self.m_model.m_name)
self.g_heading_entry.show()
self.g_heading.hide()
self.g_heading_entry.grab_focus()
def finish_edit(entry):
self.g_heading_entry.disconnect(sid)
self.g_heading_entry.disconnect(keyup_id)
self.g_heading_entry.disconnect(keydown_sid)
self.m_model.m_name = entry.get_text()
self.g_heading.set_markup(u"<b>%s</b>" % entry.get_text())
self.g_heading_entry.hide()
self.g_heading.show()
sid = self.g_heading_entry.connect('activate', finish_edit)
def keydown(entry, event):
if event.keyval == gtk.keysyms.Tab:
finish_edit(entry)
keydown_sid = self.g_heading_entry.connect('key-press-event', keydown)
def keyup(entry, event):
if event.keyval == gtk.keysyms.Escape:
self.g_heading_entry.disconnect(sid)
self.g_heading_entry.disconnect(keyup_id)
self.g_heading_entry.hide()
self.g_heading.show()
return True
keyup_id = self.g_heading_entry.connect('key-release-event', keyup)
def on_add(self, btn, event):
menu = gtk.Menu()
item = gtk.MenuItem(_("Add link to new page"))
item.connect('activate', self.on_add_link_to_new_page)
menu.append(item)
item = gtk.MenuItem(_("Add link to exercise"))
item.connect('activate', self.on_add_link)
menu.append(item)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
def on_remove(self, btn, event):
self.m_parent.remove_section(self)
def on_add_link(self, btn):
if editor_of(self).m_filename:
open_dir = os.path.split(editor_of(self).m_filename)[0]
else:
open_dir = filesystem.user_data()
dlg = SelectLessonFileDialog(editor_of(self))
dlg.set_current_folder(open_dir)
while 1:
ret = dlg.run()
if ret in (gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT, gtk.RESPONSE_CANCEL):
break
else:
assert ret == gtk.RESPONSE_OK
for filename in dlg.get_filenames():
fn = gu.decode_filename(filename)
assert os.path.isabs(fn)
# If the file name is a file in a subdirectory below
# lessonfile.exercises_dir in the current working directory,
# then the file is a standard lesson file, and it will be
# converted to a uri scheme with:
fn = lessonfile.mk_uri(fn)
# Small test to check that the file actually is a lesson file.
try:
lessonfile.infocache.get(fn, 'title')
except lessonfile.infocache.FileNotLessonfile:
continue
self.m_model.append(fn)
self.g_link_box.pack_start(self.create_linkrow(fn), False)
break
dlg.destroy()
def on_add_link_to_new_page(self, menuitem):
page = pd.Page(_("Untitled%s") % "", [pd.Column()])
self.m_model.append(page)
self.g_link_box.pack_start(self.create_linkrow(page))
def create_linkrow(self, link_this):
hbox = gtk.HBox()
def ff(btn, page):
if id(page) in editor_of(self).m_page_mapping:
editor_of(self).show_page_id(id(page))
else:
if not page[0]:
page[0].append(pd.LinkList(link_this.m_name))
p = Page(page, parent_page(self))
p.show()
editor_of(self).add_page(p)
if isinstance(link_this, pd.Page):
linkbutton = gu.ClickableLabel(link_this.m_name)
linkbutton.connect('clicked', ff, link_this)
else:
try:
linkbutton = gu.ClickableLabel(lessonfile.infocache.get(link_this, 'title'))
linkbutton.set_tooltip_text(link_this)
except lessonfile.InfoCache.FileNotFound:
linkbutton = gu.ClickableLabel(_(u"%s was not found") % link_this)
linkbutton.make_warning()
hbox.pack_start(linkbutton)
linkbutton.connect('button-press-event', self.on_right_click_row, link_this)
hbox.show_all()
return hbox
def on_right_click_row(self, button, event, linked):
idx = self.m_model.index(linked)
if event.button == 3:
m = gtk.Menu()
item = gtk.ImageMenuItem(gtk.STOCK_DELETE)
item.connect('activate', self.on_delete_link, linked)
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_CUT)
item.connect('activate', self.on_cut_link, idx)
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_PASTE)
item.set_sensitive(bool(Editor.clipboard))
item.connect('activate', self.on_paste, idx)
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
item.connect('activate', self.on_edit_linktext, linked)
item.set_sensitive(bool(not isinstance(linked, basestring)))
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_GO_UP)
item.connect('activate', self.on_move_link_up, idx)
item.set_sensitive(bool(idx > 0))
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN)
item.connect('activate', self.on_move_link_down, idx)
item.set_sensitive(bool(idx < len(self.m_model) - 1))
m.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
item.set_sensitive(isinstance(linked, unicode))
item.connect('activate', self.on_edit_file, idx)
m.append(item)
m.show_all()
m.popup(None, None, None, event.button, event.time)
return True
def on_delete_link(self, menuitem, linked):
idx = self.m_model.index(linked)
if id(linked) in editor_of(self).m_page_mapping:
editor_of(self).destroy_window(id(linked))
self.g_link_box.get_children()[idx].destroy()
del self.m_model[idx]
def on_edit_linktext(self, menuitem, linked):
idx = self.m_model.index(linked)
# row is the hbox containing the linkbutton
row = self.g_link_box.get_children()[idx]
linkbutton = row.get_children()[0]
entry = gtk.Entry()
entry.set_text(linkbutton.get_label())
row.pack_start(entry)
linkbutton.hide()
entry.show()
entry.grab_focus()
def finish_edit(entry):
linkbutton.set_label(entry.get_text().decode("utf-8"))
linkbutton.get_children()[0].set_alignment(0.0, 0.5)
linkbutton.show()
self.m_model[idx].m_name = entry.get_text().decode("utf-8")
entry.destroy()
sid = entry.connect('activate', finish_edit)
def keydown(entry, event):
if event.keyval == gtk.keysyms.Tab:
finish_edit(entry)
entry.connect('key-press-event', keydown)
def keyup(entry, event):
if event.keyval == gtk.keysyms.Escape:
linkbutton.show()
entry.disconnect(sid)
entry.destroy()
return True
entry.connect('key-release-event', keyup)
def on_edit_file(self, menuitem, linked):
try:
try:
subprocess.call((cfg.get_string("programs/text-editor"),
lessonfile.uri_expand(self.m_model[linked])))
except OSError, e:
raise osutils.BinaryForProgramException("Text editor", cfg.get_string("programs/text-editor"), e)
except osutils.BinaryForProgramException, e:
solfege.win.display_error_message2(e.msg1, e.msg2)
def on_cut(self, btn):
self.m_parent.cut_section(self)
def on_cut_link(self, menuitem, idx):
Editor.clipboard.append(self.m_model[idx])
del self.m_model[idx]
self.g_link_box.get_children()[idx].destroy()
def on_paste(self, btn, idx):
assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
pobj = Editor.clipboard.pop()
if isinstance(pobj, pd.LinkList):
mobj = pd.Page(pobj.m_name, [pd.Column(pobj)])
else:
mobj = pobj
if idx == -1:
self.m_model.append(mobj)
self.g_link_box.pack_start(self.create_linkrow(mobj))
else:
self.m_model.insert(idx, mobj)
row = self.create_linkrow(mobj)
self.g_link_box.pack_start(row)
self.g_link_box.reorder_child(row, idx)
def on_move_link_up(self, btn, idx):
"""
Move the link one row up.
"""
assert idx > 0
self.m_model[idx], self.m_model[idx - 1] = self.m_model[idx - 1], self.m_model[idx]
self.g_link_box.reorder_child(self.g_link_box.get_children()[idx], idx - 1)
def on_move_link_down(self, btn, idx=None):
"""
Move the link one row down.
"""
self.m_model[idx], self.m_model[idx + 1] = self.m_model[idx + 1], self.m_model[idx]
self.g_link_box.reorder_child(self.g_link_box.get_children()[idx], idx + 1)
class Column(gtk.VBox):
def __init__(self, model, parent):
gtk.VBox.__init__(self)
self.set_spacing(gu.hig.SPACE_MEDIUM)
self.m_model = model
self.m_parent = parent
assert isinstance(model, pd.Column)
self.g_section_box = gtk.VBox()
self.g_section_box.set_spacing(gu.hig.SPACE_MEDIUM)
self.pack_start(self.g_section_box, False)
for section in model:
assert isinstance(section, pd.LinkList)
gui_section = Section(section, self)
self.g_section_box.pack_start(gui_section, False)
hbox = gtk.HBox()
self.pack_start(hbox, False)
b = gtk.Button(_("Add section"))
hbox.pack_start(b, False)
b.connect('clicked', self.on_add_section)
b = gtk.Button(stock=gtk.STOCK_PASTE)
b.connect('clicked', self.on_paste)
Editor.clipboard.register_paste_button(b, pd.LinkList)
hbox.pack_start(b, False)
def __del__(self):
logging.debug("Column.__del__")
def cut_section(self, section):
idx = self.g_section_box.get_children().index(section)
Editor.clipboard.append(self.m_model[idx])
del self.m_model[idx]
self.g_section_box.get_children()[idx].destroy()
def remove_section(self, section):
idx = self.g_section_box.get_children().index(section)
del self.m_model[idx]
self.g_section_box.get_children()[idx].destroy()
def on_add_section(self, btn):
# We write "Untitled%s" % "" instead of just "Untitled" here
# since "Untitled%s" is already translated in many languages.
section = pd.LinkList(_("Untitled%s" % ""))
self.m_model.append(section)
gui_section = Section(section, self)
self.g_section_box.pack_start(gui_section, False)
gui_section.show_all()
def move_section_down(self, widget, section):
idx = self.g_section_box.get_children().index(section)
if idx < len(self.g_section_box.get_children()) - 1:
self.g_section_box.reorder_child(section, idx + 1)
self.m_model[idx], self.m_model[idx + 1] \
= self.m_model[idx + 1], self.m_model[idx]
self.m_parent.update_buttons()
def move_section_up(self, widget, section):
idx = self.g_section_box.get_children().index(section)
if idx > 0:
self.g_section_box.reorder_child(section, idx - 1)
self.m_model[idx], self.m_model[idx - 1] \
= self.m_model[idx - 1], self.m_model[idx]
self.m_parent.update_buttons()
def on_paste(self, widget):
"""
Paste the clipboard as a new section to this column.
"""
assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
assert isinstance(Editor.clipboard[-1], pd.LinkList)
pobj = Editor.clipboard.pop()
self.m_model.append(pobj)
sect = Section(pobj, self)
sect.show_all()
self.g_section_box.pack_start(sect, False)
class Page(gtk.VBox):
def __init__(self, model, parent):
gtk.VBox.__init__(self)
self.m_model = model
self.m_parent = parent
sc = gtk.ScrolledWindow()
sc.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.pack_start(sc)
self.g_column_box = gtk.HBox()
self.g_column_box.set_spacing(gu.hig.SPACE_LARGE)
self.g_column_box.set_border_width(gu.hig.SPACE_SMALL)
# We pack column into this box
sc.add_with_viewport(self.g_column_box)
self.show_all()
if model:
self.update_from_model()
def __del__(self):
logging.debug("Page.__del__:", self.m_model.m_name)
def on_add_column(self, *btn):
column = pd.Column()
self.m_model.append(column)
gcol = Column(column, self)
gcol.show_all()
self.g_column_box.pack_start(gcol)
def on_move_section_left(self, button, section):
column_idx = self.g_column_box.get_children().index(section.m_parent)
section_idx = section.m_parent.g_section_box.get_children().index(section)
if column_idx > 0:
to_column = self.g_column_box.get_children()[column_idx - 1]
section.reparent(to_column.g_section_box)
section.m_parent = to_column
to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
self.m_model[column_idx - 1].append(self.m_model[column_idx][section_idx])
del self.m_model[column_idx][section_idx]
# Remove the right-most column if we moved the
# last section out of it.
if not self.g_column_box.get_children()[-1].g_section_box.get_children():
assert len(self.m_model[-1]) == 0
del self.m_model[-1]
self.g_column_box.get_children()[-1].destroy()
self.update_buttons()
def on_move_section_right(self, button, section):
# the column we move from
column_idx = self.g_column_box.get_children().index(section.m_parent)
section_idx = section.m_parent.g_section_box.get_children().index(section)
if column_idx == len(self.g_column_box.get_children()) - 1:
self.on_add_column()
to_column = self.g_column_box.get_children()[column_idx + 1]
section.reparent(to_column.g_section_box)
section.m_parent = to_column
to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
to_section_idx = len(self.m_model[column_idx + 1])
self.m_model[column_idx + 1].append(self.m_model[column_idx][section_idx])
del self.m_model[column_idx][section_idx]
self.update_buttons()
def update_from_model(self):
for child in self.g_column_box.get_children():
child.destroy()
for column in self.m_model:
self.g_column_box.pack_start(Column(column, self))
self.g_column_box.show_all()
self.update_buttons()
def update_buttons(self):
num_cols = len(self.g_column_box.get_children())
for col_idx, column in enumerate(self.g_column_box.get_children()):
num_sects = len(column.g_section_box.get_children())
for sect_idx, section in enumerate(column.g_section_box.get_children()):
section.g_move_up_btn.set_sensitive(sect_idx != 0)
section.g_move_down_btn.set_sensitive(sect_idx != num_sects -1)
section.g_move_left_btn.set_sensitive(col_idx != 0)
if [col for col in self.g_column_box.get_children() if not col.g_section_box.get_children()] and col_idx == num_cols - 1:
section.g_move_right_btn.set_sensitive(False)
else:
section.g_move_right_btn.set_sensitive(True)
class Clipboard(list):
def __init__(self, v=[]):
list.__init__(v)
self.m_paste_buttons = []
def pop(self, i=-1):
ret = list.pop(self, i)
self.update_buttons()
return ret
def append(self, obj):
list.append(self, obj)
self.update_buttons()
def register_paste_button(self, button, accepts_types):
button.set_sensitive(bool(self) and isinstance(self[-1], types))
self.m_paste_buttons.append((button, accepts_types))
def update_buttons(self):
for button, types in self.m_paste_buttons:
button.set_sensitive(bool(self) and isinstance(self[-1], types))
class Editor(gtk.Window, gu.EditorDialogBase):
savedir = os.path.join(filesystem.user_data(), u'exercises', u'user')
# The clipboard will be shared between all Editor instances
clipboard = Clipboard()
def __init__(self, filename=None):
gtk.Window.__init__(self)
logging.debug("fpeditor.Editor.__init__(%s)" % filename)
gu.EditorDialogBase.__init__(self, filename)
self.set_default_size(800, 600)
self.g_main_box = gtk.VBox()
self.add(self.g_main_box)
self.g_actiongroup.add_actions([
('GoBack', gtk.STOCK_GO_BACK, None, None, None, self.go_back),
])
self.setup_toolbar()
self.g_title_hbox = gtk.HBox()
self.g_title_hbox.set_spacing(gu.hig.SPACE_SMALL)
self.g_title_hbox.set_border_width(gu.hig.SPACE_SMALL)
label = gtk.Label()
label.set_markup(u"<b>%s</b>" % _("Front page title:"))
self.g_title_hbox.pack_start(label, False)
self.g_fptitle = gtk.Entry()
self.g_title_hbox.pack_start(self.g_fptitle)
self.g_main_box.pack_start(self.g_title_hbox, False)
# This dict maps the windows created for all pages belonging to
# the file.
self.m_page_mapping = {}
self.m_model = None
if filename:
self.load_file(filename)
else:
self.m_model = pd.Page(_("Untitled%s") % self.m_instance_number,
pd.Column())
self.set_not_modified()
self.add_page(Page(self.m_model, self))
self.clipboard.update_buttons()
self.show_all()
self.add_to_instance_dict()
self.g_fptitle.set_text(self.m_model.m_name)
self.g_fptitle.connect('changed', self.on_frontpage_title_changed)
def __del__(self):
logging.debug("fpeditor.Editor.__del__, filename=" % self.m_filename)
def add_page(self, page):
"""
Add and show the page.
"""
editor_of(self).m_page_mapping[id(page.m_model)] = page
self.g_main_box.pack_start(page)
self.show_page(page)
def show_page_id(self, page_id):
self.show_page(self.m_page_mapping[page_id])
def show_page(self, page):
"""
Hide the currently visible page, and show PAGE instead.
"""
try:
self.g_visible_page.hide()
except AttributeError:
pass
self.g_visible_page = page
page.show()
if isinstance(page.m_parent, Page):
self.g_title_hbox.hide()
else:
self.g_title_hbox.show()
self.g_ui_manager.get_widget("/Toolbar/GoBack").set_sensitive(
not isinstance(self.g_visible_page.m_parent, Editor))
def go_back(self, *action):
self.show_page(self.g_visible_page.m_parent)
def on_frontpage_title_changed(self, widget):
self.m_model.m_name = widget.get_text()
def setup_toolbar(self):
self.g_ui_manager.insert_action_group(self.g_actiongroup, 0)
uixml = """
<ui>
<toolbar name='Toolbar'>
<toolitem action='GoBack'/>
<toolitem action='New'/>
<toolitem action='Open'/>
<toolitem action='Save'/>
<toolitem action='SaveAs'/>
<toolitem action='Close'/>
<toolitem action='Help'/>
</toolbar>
<accelerator action='Close'/>
<accelerator action='New'/>
<accelerator action='Open'/>
<accelerator action='Save'/>
</ui>
"""
self.g_ui_manager.add_ui_from_string(uixml)
toolbar = self.g_ui_manager.get_widget("/Toolbar")
self.g_main_box.pack_start(toolbar, False)
self.g_main_box.reorder_child(toolbar, 0)
self.g_ui_manager.get_widget("/Toolbar").set_style(gtk.TOOLBAR_BOTH)
def destroy_window(self, window_id):
"""
Destroy the window with the id 'windowid' and all subwindows.
"""
def do_del(wid):
for key in [k for k in self.m_page_mapping.keys()
if id(parent_page(self.m_page_mapping[k]).m_model) == wid]:
do_del(key)
editor_of(self).m_page_mapping[wid].destroy()
del editor_of(self).m_page_mapping[wid]
do_del(window_id)
@staticmethod
def edit_file(fn):
if fn in Editor.instance_dict:
Editor.instance_dict[fn].present()
else:
try:
win = Editor(fn)
win.show()
except IOError, e:
gu.dialog_ok(_("Loading file '%(filename)s' failed: %(msg)s") %
{'filename': fn, 'msg': str(e).decode('utf8', 'replace')})
def load_file(self, filename):
"""
Load a file into a empty, newly created Editor object.
"""
assert self.m_model == None
self.m_model = pd.load_tree(filename, C_locale=True)
self.m_filename = filename
#
if not os.path.isabs(filename):
if not os.access(filename, os.W_OK):
m = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
gtk.BUTTONS_CLOSE, _("The default learning tree is write protected in your install. This is normal. If you want to edit a learning tree, you have to select one of the trees stored in .solfege/learningtrees in your home directory."))
m.run()
m.destroy()
self.set_not_modified()
self.set_title(self.m_filename)
def set_not_modified(self):
"""
Store the current state of the data in self.m_orig_dump so that
is_modified() will return False until we make new changes.
"""
io = StringIO.StringIO()
self.m_model.dump(io)
self.m_orig_dump = io.getvalue()
def is_modified(self):
"""
Return True if the data has changed since the last call to
set_not_modified()
"""
io = StringIO.StringIO()
self.m_model.dump(io)
s = io.getvalue()
return s != self.m_orig_dump
@property
def m_changed(self):
return self.is_modified()
def save(self, w=None):
assert self.m_filename
save_location = os.path.split(self.m_filename)[0] + os.sep
fh = pd.FileHeader(1, self.m_model)
fh.save_file(self.m_filename)
self.set_not_modified()
# We do test for solfege.win since it is not available during testing
if getattr(solfege, 'win', None):
solfege.win.on_frontpage_changed()
def on_show_help(self, *w):
return
def get_save_as_dialog(self):
dialog = gu.EditorDialogBase.get_save_as_dialog(self)
ev2 = gtk.EventBox()
ev2.set_name("DIALOGWARNING2")
ev = gtk.EventBox()
ev.set_border_width(gu.hig.SPACE_SMALL)
ev2.add(ev)
ev.set_name("DIALOGWARNING")
label = gtk.Label()
label.set_padding(gu.hig.SPACE_MEDIUM, gu.hig.SPACE_MEDIUM)
ev.add(label)
label.set_markup(_("<b>IMPORTANT:</b> Your front page file <b>must</b> be saved in a subdirectory below the directory named exercises. See the user manual for details."))
dialog.set_extra_widget(ev2)
ev2.show_all()
return dialog
if __name__ == '__main__':
gtk.link_button_set_uri_hook(lambda a, b: None)
e = Editor()
e.load_file("learningtrees/learningtree.txt")
gtk.main()
|