1
# vim: set fileencoding=utf-8 :
2
# GNU Solfege - free ear training software
3
# Copyright (C) 2009 Tom Cato Amundsen
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
from __future__ import absolute_import
32
if __name__ == '__main__':
33
from solfege import i18n
35
import solfege.statistics
36
solfege.db = solfege.statistics.DB()
39
from solfege import cfg
40
from solfege import filesystem
41
from solfege import gu
42
from solfege import frontpage as pd
43
from solfege import lessonfile
44
from solfege import lessonfilegui
45
from solfege import osutils
47
class LessonFilePreviewWidget(gtk.VBox):
48
def __init__(self, model):
49
gtk.VBox.__init__(self)
51
self.set_size_request(200, 200)
53
l.set_alignment(0.0, 0.5)
54
l.set_markup("<b>Title:</b>")
55
self.pack_start(l, False)
56
self.g_title = gtk.Label()
57
self.g_title.set_alignment(0.0, 0.5)
58
self.pack_start(self.g_title, False)
60
l.set_alignment(0.0, 0.5)
61
l.set_markup("<b>Module:</b>")
62
self.pack_start(l, False)
63
self.g_module = gtk.Label()
64
self.g_module.set_alignment(0.0, 0.5)
65
self.pack_start(self.g_module, False)
67
l.set_alignment(0.0, 0.5)
68
l.set_markup("<b>Used in topcis:</b>")
69
self.pack_start(l, False)
70
self.g_topic_box = gtk.VBox()
71
self.pack_start(self.g_topic_box, False)
73
def update(self, dlg):
74
fn = dlg.get_preview_filename()
76
fn = gu.decode_filename(fn)
77
for child in self.g_topic_box.get_children():
79
fn = lessonfile.mk_uri(fn)
81
self.set_sensitive(True)
82
self.g_title.set_text(lessonfile.infocache.get(fn, 'title'))
83
self.g_module.set_text(lessonfile.infocache.get(fn, 'module'))
84
self.g_ok_button.set_sensitive(True)
85
for x in self.m_model.iterate_topics_for_file(fn):
87
l.set_alignment(0.0, 0.5)
88
self.g_topic_box.pack_start(l, False)
89
if not self.g_topic_box.get_children():
91
l.set_alignment(0.0, 0.5)
92
self.g_topic_box.pack_start(l, False)
93
except (lessonfile.InfoCache.FileNotFound,
94
lessonfile.InfoCache.FileNotLessonfile), e:
95
self.g_title.set_text(u'')
96
self.g_module.set_text(u'')
97
self.g_ok_button.set_sensitive(False)
98
self.set_sensitive(False)
102
class SelectLessonFileDialog(gtk.FileChooserDialog):
103
def __init__(self, parent):
104
gtk.FileChooserDialog.__init__(self, _("Select lesson file"),
106
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,))
107
self.set_select_multiple(True)
108
pv = LessonFilePreviewWidget(parent.m_model)
109
pv.g_ok_button = self.add_button("gtk-ok", gtk.RESPONSE_OK)
110
pv.g_ok_button.set_sensitive(False)
112
self.set_preview_widget(pv)
113
self.connect('selection-changed', pv.update)
118
Return the toplevel page, the one that is a Editor object.
121
while not isinstance(p, Editor):
125
def parent_page(obj):
127
Return the parent page of obj. Return None if this is the toplevel page.
132
if isinstance(p, Page):
137
class Section(gtk.VBox):
139
A section consists of a heading and a list of links.
140
self.g_link_box is a vbox that contains the links.
142
def __init__(self, model, parent):
143
gtk.VBox.__init__(self)
145
self.m_parent = parent
146
assert isinstance(model, pd.LinkList)
149
self.pack_start(hbox, False)
150
# This is displayed and used when we edit the heading
151
self.g_heading_entry = gtk.Entry()
152
self.g_heading_entry.set_no_show_all(True)
153
hbox.pack_start(self.g_heading_entry)
154
self.g_heading = gtk.Label()
155
self.g_heading.set_alignment(0.0, 0.5)
156
# FIXME escape m_name
157
self.g_heading.set_markup("<b>%s</b>" % model.m_name)
158
hbox.pack_start(self.g_heading, False)
160
button_hbox = gtk.HBox()
161
button_hbox.set_spacing(0)
162
hbox.pack_start(button_hbox, False)
164
im.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
165
button = gtk.Button()
167
button.connect('clicked', self.on_edit_heading)
168
button_hbox.pack_start(button, False)
171
im.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
172
button = gtk.Button()
174
button.connect('button-release-event', self.on_add)
175
button_hbox.pack_start(button, False)
178
im.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
179
button = gtk.Button()
181
button.connect('button-release-event', self.on_remove)
182
button_hbox.pack_start(button, False)
185
im.set_from_stock(gtk.STOCK_CUT, gtk.ICON_SIZE_MENU)
188
b.connect('clicked', self.on_cut)
189
button_hbox.pack_start(b, False)
192
im.set_from_stock(gtk.STOCK_PASTE, gtk.ICON_SIZE_MENU)
195
b.connect('clicked', self.on_paste, -1)
196
Editor.clipboard.register_paste_button(b, (pd.LinkList, pd.Page, unicode))
197
button_hbox.pack_start(b, False)
200
im.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU)
201
self.g_move_down_btn = gtk.Button()
202
self.g_move_down_btn.add(im)
203
self.g_move_down_btn.connect('clicked', self.on_move_down)
204
button_hbox.pack_start(self.g_move_down_btn, False)
207
im.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU)
208
self.g_move_up_btn = gtk.Button()
209
self.g_move_up_btn.add(im)
210
self.g_move_up_btn.connect('clicked', self.on_move_up)
211
button_hbox.pack_start(self.g_move_up_btn, False)
214
im.set_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_MENU)
215
self.g_move_left_btn = gtk.Button()
216
self.g_move_left_btn.add(im)
217
self.g_move_left_btn.connect('clicked',
218
parent.m_parent.on_move_section_left, self)
219
button_hbox.pack_start(self.g_move_left_btn, False)
222
im.set_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_MENU)
223
self.g_move_right_btn = gtk.Button()
224
self.g_move_right_btn.add(im)
225
self.g_move_right_btn.connect('clicked',
226
parent.m_parent.on_move_section_right, self)
227
button_hbox.pack_start(self.g_move_right_btn, False)
229
self.g_link_box = gtk.VBox()
230
self.pack_start(self.g_link_box, False)
231
for link in self.m_model:
232
self.g_link_box.pack_start(self.create_linkrow(link))
233
# The button to click to add a new link
235
self.pack_start(hbox)
236
def on_move_down(self, btn):
237
self.m_parent.move_section_down(self)
238
def on_move_up(self, btn):
239
self.m_parent.move_section_up(self)
240
def on_edit_heading(self, btn):
241
self.g_heading_entry.set_text(self.m_model.m_name)
242
self.g_heading_entry.show()
243
self.g_heading.hide()
244
self.g_heading_entry.grab_focus()
245
def finish_edit(entry):
246
self.g_heading_entry.disconnect(sid)
247
self.g_heading_entry.disconnect(keyup_id)
248
self.g_heading_entry.disconnect(keydown_sid)
249
self.m_model.m_name = entry.get_text()
250
self.g_heading.set_markup(u"<b>%s</b>" % entry.get_text())
251
self.g_heading_entry.hide()
252
self.g_heading.show()
253
sid = self.g_heading_entry.connect('activate', finish_edit)
254
def keydown(entry, event):
255
if event.keyval == gtk.keysyms.Tab:
257
keydown_sid = self.g_heading_entry.connect('key-press-event', keydown)
258
def keyup(entry, event):
259
if event.keyval == gtk.keysyms.Escape:
260
self.g_heading_entry.disconnect(sid)
261
self.g_heading_entry.disconnect(keyup_id)
262
self.g_heading_entry.hide()
263
self.g_heading.show()
265
keyup_id = self.g_heading_entry.connect('key-release-event', keyup)
266
def on_add(self, btn, event):
268
item = gtk.MenuItem(_("Add link to new page"))
269
item.connect('activate', self.on_add_link_to_new_page)
271
item = gtk.MenuItem(_("Add link to exercise"))
272
item.connect('activate', self.on_add_link)
275
menu.popup(None, None, None, event.button, event.time)
276
def on_remove(self, btn, event):
277
self.m_parent.remove_section(self)
278
def on_add_link(self, btn):
279
if editor_of(self).m_filename:
280
open_dir = os.path.split(editor_of(self).m_filename)[0]
282
open_dir = filesystem.user_data()
283
dlg = SelectLessonFileDialog(editor_of(self))
284
dlg.set_current_folder(open_dir)
287
if ret in (gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT, gtk.RESPONSE_CANCEL):
290
assert ret == gtk.RESPONSE_OK
291
for filename in dlg.get_filenames():
292
fn = gu.decode_filename(filename)
293
assert os.path.isabs(fn)
294
# If the file name is a file in a subdirectory below
295
# lessonfile.exercises_dir in the current working directory,
296
# then the file is a standard lesson file, and it will be
297
# converted to a uri scheme with:
298
fn = lessonfile.mk_uri(fn)
299
# Small test to check that the file actually is a lesson file.
301
lessonfile.infocache.get(fn, 'title')
302
except lessonfile.infocache.FileNotLessonfile:
304
self.m_model.append(fn)
305
self.g_link_box.pack_start(self.create_linkrow(fn), False)
308
def on_add_link_to_new_page(self, menuitem):
309
page = pd.Page(_("Untitled%s") % "", [pd.Column()])
310
self.m_model.append(page)
311
self.g_link_box.pack_start(self.create_linkrow(page))
312
def create_linkrow(self, link_this):
315
if id(page) in editor_of(self).m_page_mapping:
316
editor_of(self).show_page_id(id(page))
319
page[0].append(pd.LinkList(link_this.m_name))
320
p = Page(page, parent_page(self))
322
editor_of(self).add_page(p)
323
if isinstance(link_this, pd.Page):
324
linkbutton = gu.ClickableLabel(link_this.m_name)
325
linkbutton.connect('clicked', ff, link_this)
328
linkbutton = gu.ClickableLabel(lessonfile.infocache.get(link_this, 'title'))
329
linkbutton.set_tooltip_text(link_this)
330
except lessonfile.InfoCache.FileNotFound:
331
linkbutton = gu.ClickableLabel(_(u"«%s» was not found") % link_this)
332
linkbutton.make_warning()
334
hbox.pack_start(linkbutton)
335
linkbutton.connect('button-press-event', self.on_right_click_row, link_this)
338
def on_right_click_row(self, button, event, linked):
339
idx = self.m_model.index(linked)
340
if event.button == 3:
342
item = gtk.ImageMenuItem(gtk.STOCK_DELETE)
343
item.connect('activate', self.on_delete_link, linked)
345
item = gtk.ImageMenuItem(gtk.STOCK_CUT)
346
item.connect('activate', self.on_cut_link, idx)
348
item = gtk.ImageMenuItem(gtk.STOCK_PASTE)
349
item.set_sensitive(bool(Editor.clipboard))
350
item.connect('activate', self.on_paste, idx)
352
item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
353
item.connect('activate', self.on_edit_linktext, linked)
354
item.set_sensitive(bool(not isinstance(linked, basestring)))
356
item = gtk.ImageMenuItem(gtk.STOCK_GO_UP)
357
item.connect('activate', self.on_move_up, idx)
358
item.set_sensitive(bool(idx > 0))
360
item = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN)
361
item.connect('activate', self.on_move_down, idx)
362
item.set_sensitive(bool(idx < len(self.m_model) - 1))
364
item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
365
item.set_sensitive(isinstance(linked, unicode))
366
item.connect('activate', self.on_edit_file, idx)
369
m.popup(None, None, None, event.button, event.time)
371
def on_delete_link(self, menuitem, linked):
372
idx = self.m_model.index(linked)
373
if id(linked) in editor_of(self).m_page_mapping:
374
editor_of(self).destroy_window(id(linked))
375
self.g_link_box.get_children()[idx].destroy()
376
del self.m_model[idx]
377
def on_edit_linktext(self, menuitem, linked):
378
idx = self.m_model.index(linked)
379
# row is the hbox containing the linkbutton
380
row = self.g_link_box.get_children()[idx]
381
linkbutton = row.get_children()[0]
383
entry.set_text(linkbutton.get_label())
384
row.pack_start(entry)
388
def finish_edit(entry):
389
linkbutton.set_label(entry.get_text().decode("utf-8"))
390
linkbutton.get_children()[0].set_alignment(0.0, 0.5)
392
self.m_model[idx].m_name = entry.get_text().decode("utf-8")
394
sid = entry.connect('activate', finish_edit)
395
def keydown(entry, event):
396
if event.keyval == gtk.keysyms.Tab:
398
entry.connect('key-press-event', keydown)
399
def keyup(entry, event):
400
if event.keyval == gtk.keysyms.Escape:
402
entry.disconnect(sid)
405
entry.connect('key-release-event', keyup)
406
def on_edit_file(self, menuitem, linked):
409
subprocess.call((cfg.get_string("programs/text-editor"),
410
lessonfile.uri_expand(self.m_model[linked])))
412
raise osutils.BinaryForProgramException("Text editor", cfg.get_string("programs/text-editor"), e)
413
except osutils.BinaryForProgramException, e:
414
solfege.win.display_error_message2(e.msg1, e.msg2)
415
def on_cut(self, btn):
416
self.m_parent.cut_section(self)
417
def on_cut_link(self, menuitem, idx):
418
Editor.clipboard.append(self.m_model[idx])
419
del self.m_model[idx]
420
self.g_link_box.get_children()[idx].destroy()
421
def on_paste(self, btn, idx):
422
assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
423
pobj = Editor.clipboard.pop()
424
if isinstance(pobj, pd.LinkList):
425
mobj = pd.Page(pobj.m_name, [pd.Column(pobj)])
429
self.m_model.append(mobj)
430
self.g_link_box.pack_start(self.create_linkrow(mobj))
432
self.m_model.insert(idx, mobj)
433
row = self.create_linkrow(mobj)
434
self.g_link_box.pack_start(row)
435
self.g_link_box.reorder_child(row, idx)
436
def on_move_up(self, btn, idx):
438
Move the link one row up.
441
self.m_model[idx], self.m_model[idx - 1] = self.m_model[idx - 1], self.m_model[idx]
442
self.g_link_box.reorder_child(self.g_link_box.get_children()[idx], idx - 1)
443
def on_move_down(self, btn, idx):
445
Move the link one row down.
447
self.m_model[idx], self.m_model[idx + 1] = self.m_model[idx + 1], self.m_model[idx]
448
self.g_link_box.reorder_child(self.g_link_box.get_children()[idx], idx + 1)
451
class Column(gtk.VBox):
452
def __init__(self, model, parent):
453
gtk.VBox.__init__(self)
454
self.set_spacing(gu.hig.SPACE_MEDIUM)
456
self.m_parent = parent
457
assert isinstance(model, pd.Column)
458
self.g_section_box = gtk.VBox()
459
self.g_section_box.set_spacing(gu.hig.SPACE_MEDIUM)
460
self.pack_start(self.g_section_box, False)
461
for section in model:
462
assert isinstance(section, pd.LinkList)
463
gui_section = Section(section, self)
464
self.g_section_box.pack_start(gui_section, False)
466
self.pack_start(hbox, False)
467
b = gtk.Button(_("Add section"))
468
hbox.pack_start(b, False)
469
b.connect('clicked', self.on_add_section)
470
b = gtk.Button(stock=gtk.STOCK_PASTE)
471
b.connect('clicked', self.on_paste)
472
Editor.clipboard.register_paste_button(b, pd.LinkList)
473
hbox.pack_start(b, False)
475
logging.debug("Column.__del__")
476
def cut_section(self, section):
477
idx = self.g_section_box.get_children().index(section)
478
Editor.clipboard.append(self.m_model[idx])
479
del self.m_model[idx]
480
self.g_section_box.get_children()[idx].destroy()
481
def remove_section(self, section):
482
idx = self.g_section_box.get_children().index(section)
483
del self.m_model[idx]
484
self.g_section_box.get_children()[idx].destroy()
485
def on_add_section(self, btn):
486
# We write "Untitled%s" % "" instead of just "Untitled" here
487
# since "Untitled%s" is already translated in many languages.
488
section = pd.LinkList(_("Untitled%s" % ""))
489
self.m_model.append(section)
490
gui_section = Section(section, self)
491
self.g_section_box.pack_start(gui_section, False)
492
gui_section.show_all()
493
def move_section_down(self, section):
494
idx = self.g_section_box.get_children().index(section)
495
if idx < len(self.g_section_box.get_children()) - 1:
496
self.g_section_box.reorder_child(section, idx + 1)
497
self.m_model[idx], self.m_model[idx + 1] \
498
= self.m_model[idx + 1], self.m_model[idx]
499
self.m_parent.update_buttons()
500
def move_section_up(self, section):
501
idx = self.g_section_box.get_children().index(section)
503
self.g_section_box.reorder_child(section, idx - 1)
504
self.m_model[idx], self.m_model[idx - 1] \
505
= self.m_model[idx - 1], self.m_model[idx]
506
self.m_parent.update_buttons()
507
def on_paste(self, widget):
509
Paste the clipboard as a new section to this column.
511
assert Editor.clipboard, "Paste buttons should be insensitive when the clipboard is empty."
512
assert isinstance(Editor.clipboard[-1], pd.LinkList)
513
pobj = Editor.clipboard.pop()
514
self.m_model.append(pobj)
515
sect = Section(pobj, self)
517
self.g_section_box.pack_start(sect, False)
520
class Page(gtk.VBox):
521
def __init__(self, model, parent):
522
gtk.VBox.__init__(self)
524
self.m_parent = parent
525
sc = gtk.ScrolledWindow()
526
sc.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
528
self.g_column_box = gtk.HBox()
529
self.g_column_box.set_spacing(gu.hig.SPACE_LARGE)
530
self.g_column_box.set_border_width(gu.hig.SPACE_SMALL)
531
# We pack column into this box
532
sc.add_with_viewport(self.g_column_box)
535
self.update_from_model()
537
logging.debug("Page.__del__:", self.m_model.m_name)
538
def on_add_column(self, *btn):
540
self.m_model.append(column)
541
gcol = Column(column, self)
543
self.g_column_box.pack_start(gcol)
544
def on_move_section_left(self, button, section):
545
column_idx = self.g_column_box.get_children().index(section.m_parent)
546
section_idx = section.m_parent.g_section_box.get_children().index(section)
548
to_column = self.g_column_box.get_children()[column_idx - 1]
549
section.reparent(to_column.g_section_box)
550
section.m_parent = to_column
551
to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
552
self.m_model[column_idx - 1].append(self.m_model[column_idx][section_idx])
553
del self.m_model[column_idx][section_idx]
554
# Remove the right-most column if we moved the
555
# last section out of it.
556
if not self.g_column_box.get_children()[-1].g_section_box.get_children():
557
assert len(self.m_model[-1]) == 0
559
self.g_column_box.get_children()[-1].destroy()
560
self.update_buttons()
561
def on_move_section_right(self, button, section):
562
# the column we move from
563
column_idx = self.g_column_box.get_children().index(section.m_parent)
564
section_idx = section.m_parent.g_section_box.get_children().index(section)
565
if column_idx == len(self.g_column_box.get_children()) - 1:
567
to_column = self.g_column_box.get_children()[column_idx + 1]
568
section.reparent(to_column.g_section_box)
569
section.m_parent = to_column
570
to_column.g_section_box.set_child_packing(section, False, False, 0, gtk.PACK_START)
571
to_section_idx = len(self.m_model[column_idx + 1])
572
self.m_model[column_idx + 1].append(self.m_model[column_idx][section_idx])
573
del self.m_model[column_idx][section_idx]
574
self.update_buttons()
575
def update_from_model(self):
576
for child in self.g_column_box.get_children():
578
for column in self.m_model:
579
self.g_column_box.pack_start(Column(column, self))
580
self.g_column_box.show_all()
581
self.update_buttons()
582
def update_buttons(self):
583
num_cols = len(self.g_column_box.get_children())
584
for col_idx, column in enumerate(self.g_column_box.get_children()):
585
num_sects = len(column.g_section_box.get_children())
586
for sect_idx, section in enumerate(column.g_section_box.get_children()):
587
section.g_move_up_btn.set_sensitive(sect_idx != 0)
588
section.g_move_down_btn.set_sensitive(sect_idx != num_sects -1)
589
section.g_move_left_btn.set_sensitive(col_idx != 0)
590
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:
591
section.g_move_right_btn.set_sensitive(False)
593
section.g_move_right_btn.set_sensitive(True)
596
class Clipboard(list):
597
def __init__(self, v=[]):
599
self.m_paste_buttons = []
601
ret = list.pop(self, i)
602
self.update_buttons()
604
def append(self, obj):
605
list.append(self, obj)
606
self.update_buttons()
607
def register_paste_button(self, button, accepts_types):
608
button.set_sensitive(bool(self) and isinstance(self[-1], types))
609
self.m_paste_buttons.append((button, accepts_types))
610
def update_buttons(self):
611
for button, types in self.m_paste_buttons:
612
button.set_sensitive(bool(self) and isinstance(self[-1], types))
615
class Editor(gtk.Window, gu.EditorDialogBase):
616
savedir = os.path.join(filesystem.user_data(), u'exercises', u'user')
617
# The clipboard will be shared between all Editor instances
618
clipboard = Clipboard()
619
def __init__(self, filename=None):
620
gtk.Window.__init__(self)
621
logging.debug("fpeditor.Editor.__init__(%s)" % filename)
622
gu.EditorDialogBase.__init__(self, filename)
623
self.set_default_size(800, 600)
624
self.g_main_box = gtk.VBox()
625
self.add(self.g_main_box)
626
self.g_actiongroup.add_actions([
627
('GoBack', gtk.STOCK_GO_BACK, None, None, None, self.go_back),
630
self.g_title_hbox = gtk.HBox()
631
self.g_title_hbox.set_spacing(gu.hig.SPACE_SMALL)
632
self.g_title_hbox.set_border_width(gu.hig.SPACE_SMALL)
634
label.set_markup(u"<b>%s</b>" % _("Front page title:"))
635
self.g_title_hbox.pack_start(label, False)
636
self.g_fptitle = gtk.Entry()
637
self.g_title_hbox.pack_start(self.g_fptitle)
638
self.g_main_box.pack_start(self.g_title_hbox, False)
639
# This dict maps the windows created for all pages belonging to
641
self.m_page_mapping = {}
644
self.load_file(filename)
646
self.m_model = pd.Page(_("Untitled%s") % self.m_instance_number,
648
self.set_not_modified()
649
self.add_page(Page(self.m_model, self))
650
self.clipboard.update_buttons()
652
self.add_to_instance_dict()
653
self.g_fptitle.set_text(self.m_model.m_name)
654
self.g_fptitle.connect('changed', self.on_frontpage_title_changed)
656
logging.debug("fpeditor.Editor.__del__, filename=" % self.m_filename)
657
def add_page(self, page):
659
Add and show the page.
661
editor_of(self).m_page_mapping[id(page.m_model)] = page
662
self.g_main_box.pack_start(page)
664
def show_page_id(self, page_id):
665
self.show_page(self.m_page_mapping[page_id])
666
def show_page(self, page):
668
Hide the currently visible page, and show PAGE instead.
671
self.g_visible_page.hide()
672
except AttributeError:
674
self.g_visible_page = page
676
if isinstance(page.m_parent, Page):
677
self.g_title_hbox.hide()
679
self.g_title_hbox.show()
680
self.g_ui_manager.get_widget("/Toolbar/GoBack").set_sensitive(
681
not isinstance(self.g_visible_page.m_parent, Editor))
682
def go_back(self, *action):
683
self.show_page(self.g_visible_page.m_parent)
684
def on_frontpage_title_changed(self, widget):
685
self.m_model.m_name = widget.get_text()
686
def setup_toolbar(self):
687
self.g_ui_manager.insert_action_group(self.g_actiongroup, 0)
690
<toolbar name='Toolbar'>
691
<toolitem action='GoBack'/>
692
<toolitem action='New'/>
693
<toolitem action='Open'/>
694
<toolitem action='Save'/>
695
<toolitem action='SaveAs'/>
696
<toolitem action='Close'/>
697
<toolitem action='Help'/>
699
<accelerator action='Close'/>
700
<accelerator action='New'/>
701
<accelerator action='Open'/>
702
<accelerator action='Save'/>
705
self.g_ui_manager.add_ui_from_string(uixml)
706
toolbar = self.g_ui_manager.get_widget("/Toolbar")
707
self.g_main_box.pack_start(toolbar, False)
708
self.g_main_box.reorder_child(toolbar, 0)
709
self.g_ui_manager.get_widget("/Toolbar").set_style(gtk.TOOLBAR_BOTH)
710
def destroy_window(self, window_id):
712
Destroy the window with the id 'windowid' and all subwindows.
715
for key in [k for k in self.m_page_mapping.keys()
716
if id(parent_page(self.m_page_mapping[k]).m_model) == wid]:
718
editor_of(self).m_page_mapping[wid].destroy()
719
del editor_of(self).m_page_mapping[wid]
723
if fn in Editor.instance_dict:
724
Editor.instance_dict[fn].present()
730
gu.dialog_ok(_("Loading file '%(filename)s' failed: %(msg)s") %
731
{'filename': fn, 'msg': str(e).decode('utf8', 'replace')})
732
def load_file(self, filename):
734
Load a file into a empty, newly created Editor object.
736
assert self.m_model == None
737
self.m_model = pd.load_tree(filename, C_locale=True)
738
self.m_filename = filename
740
if not os.path.isabs(filename):
741
if not os.access(filename, os.W_OK):
742
m = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
743
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."))
746
self.set_not_modified()
747
self.set_title(self.m_filename)
748
def set_not_modified(self):
750
Store the current state of the data in self.m_orig_dump so that
751
is_modified() will return False until we make new changes.
753
io = StringIO.StringIO()
754
self.m_model.dump(io)
755
self.m_orig_dump = io.getvalue()
756
def is_modified(self):
758
Return True if the data has changed since the last call to
761
io = StringIO.StringIO()
762
self.m_model.dump(io)
764
return s != self.m_orig_dump
767
return self.is_modified()
768
def save(self, w=None):
769
assert self.m_filename
770
save_location = os.path.split(self.m_filename)[0] + os.sep
771
fh = pd.FileHeader(1, self.m_model)
772
fh.save_file(self.m_filename)
773
self.set_not_modified()
774
# We do test for solfege.win since it is not available during testing
775
if getattr(solfege, 'win', None):
776
solfege.win.on_frontpage_changed()
777
def on_show_help(self, *w):
779
def get_save_as_dialog(self):
780
dialog = gu.EditorDialogBase.get_save_as_dialog(self)
782
ev2.set_name("DIALOGWARNING2")
784
ev.set_border_width(gu.hig.SPACE_SMALL)
786
ev.set_name("DIALOGWARNING")
788
label.set_padding(gu.hig.SPACE_MEDIUM, gu.hig.SPACE_MEDIUM)
790
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."))
791
dialog.set_extra_widget(ev2)
795
if __name__ == '__main__':
796
gtk.link_button_set_uri_hook(lambda a, b: None)
798
e.load_file("learningtrees/learningtree.txt")