File Coverage

blib/lib/MIDI/XML/Editor.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package MIDI::XML::Editor;
2 1     1   12539 use strict;
  1         3  
  1         40  
3 1     1   28 use 5.006;
  1         4  
  1         39  
4 1     1   441 use Tk 800.000;
  0            
  0            
5             use Tk::Tree;
6             use Carp;
7             #use XML::DOM;
8             #use XML::Parser;
9             #use Class::ISA;
10             use MIDI::XML;
11              
12             our @ISA = qw();
13              
14             our @EXPORT = qw();
15             our @EXPORT_OK = qw();
16              
17             our $VERSION = 0.01;
18              
19             =head1 NAME
20              
21             MIDI::XML::Editor - Module for editing MIDI XML Document objects.
22              
23             =head1 DESCRIPTION
24              
25              
26              
27             =cut
28              
29             #===============================================================================
30              
31             sub _bind_message {
32             my ($self, $widget, $msg) = @_;
33             $widget->bind('', sub { $self->{'_status_msg'} = $msg;});
34             $widget->bind('', sub { $self->{'_status_msg'} = "" ; });
35             }
36              
37             #===============================================================================
38              
39             sub _tree_click {
40             my $self = shift @_;
41             my $path = shift @_;
42            
43             print "$path clicked";
44             my $tree_nodes = $self->{'_tree_nodes'};
45             if(exists($tree_nodes->{$path})) {
46             if($path =~ /^t\.[a-z]+\.\d+$/) {
47             my ($time,$denom_ticks,$divs) = @{$tree_nodes->{$path}};
48             print " = ($time,$denom_ticks,$divs)";
49             }
50             }
51             print "\n";
52             }
53              
54             #===============================================================================
55              
56             sub _refresh ($) {
57             my $self = shift @_;
58             my $document = shift @_;
59            
60             my $tree_nodes = {};
61             $self->{'_document'} = $document;
62             my $model = $document->getDocumentElement();
63             $self->{'_model'} = $model;
64             $self->{'_format'} = $model->Format();
65             $self->{'_ticksPerBeat'} = $model->TicksPerBeat();
66             $self->{'_trackCount'} = $model->TrackCount();
67             $self->{'_timestampType'} = $model->TimestampType(),
68             $self->{'_tree_nodes'} = $tree_nodes;
69              
70             my $tree = $self->{'_tree'};
71              
72             $tree->delete('all');
73             $tree->add('h', -text => 'header');
74             $tree->add('t', -text => 'tracks');
75             my @tracks = $model->getElementsByTagName('Track');
76             my $tno = 'a';
77             my $measures = $document->measures();
78             foreach my $track (@tracks) {
79             my $tname = "track $tno";
80             my $tn = $track->name();
81             $tname = $tn if(defined($tn));
82             $tree->add("t.$tno", -text => $tname);
83             $tree->close("t.$tno");
84             my $mno=1;
85             foreach my $measure (@{$measures}) {
86             my $path = "t.$tno.$mno";
87             $tree->add($path, -text => "Meas $mno");
88             $mno++;
89             $tree_nodes->{$path} = $measure;
90             }
91             $tno++;
92             }
93             $tree->autosetmode( );
94             my $t_no = 'a';
95             foreach my $track (@tracks) {
96             $tree->close("t.$t_no");
97             $t_no++;
98             }
99             }
100              
101             #===============================================================================
102              
103             sub _file_parse {
104             my $self = shift @_;
105             my $source = shift @_;
106             my $document = MIDI::XML->parsefile($source);
107              
108             $self->_refresh($document);
109             }
110            
111             #===============================================================================
112              
113             sub _file_read {
114             my $self = shift @_;
115             my $source = shift @_;
116            
117             my $pretty = $self->{'_pretty'};
118             my $document = MIDI::XML->readfile($source,$pretty);
119              
120             $self->_refresh($document);
121             }
122            
123             #===============================================================================
124              
125             sub _file_new {
126             my $self = shift @_;
127            
128             my $source = ''
129             . ''
130             . ''
131             . ' 1'
132             . ' 2'
133             . ' 384'
134             . ' Absolute'
135             . ' '
136             . ' '
137             . ' 0'
138             . ' Track 0'
139             . ' '
140             . ' '
141             . ' 0'
142             . ' '
143             . ' '
144             . ' '
145             . ' '
146             . ' '
147             . ' 0'
148             . ' Track 1'
149             . ' '
150             . ' '
151             . '';
152              
153             my $document = MIDI::XML->parse($source);
154              
155             $self->_refresh($document);
156            
157             }
158            
159             #===============================================================================
160              
161             sub _file_open {
162             my $self = shift @_;
163            
164             my $source = $self->{'_main_w'}->getOpenFile();
165             $self->{'_xml_source'} = $source;
166             $self->{'_midi_source'} = undef;
167             $self->_file_parse($source);
168             $self->{'_save_b'}->configure(-state => 'normal');
169             $self->{'_status_msg'} = "File $source opened.";
170             }
171            
172             #===============================================================================
173              
174             sub _file_save {
175             my $self = shift @_;
176            
177             my $source = $self->{'_xml_source'};
178             $source = $self->{'_main_w'}->getSaveFile() unless (defined($source));
179             $self->{'_document'}->printToFile($source);
180             $self->{'_status_msg'} = "File $source saved.";
181             }
182            
183             #===============================================================================
184              
185             sub _file_save_as {
186             my $self = shift @_;
187            
188             my $source = $self->{'_main_w'}->getSaveFile();
189             $self->{'_xml_source'} = $source;
190             $self->{'_document'}->printToFile($source);
191             $self->{'_status_msg'} = "File saved as $source.";
192             }
193             #===============================================================================
194              
195             sub _file_import {
196             my $self = shift @_;
197             my $source = $self->{'_main_w'}->getOpenFile();
198             $self->{'_midi_source'} = $source;
199             $self->{'_xml_source'} = undef;
200             $self->_file_read($source);
201            
202             }
203            
204             #===============================================================================
205              
206             sub _file_export {
207             my $self = shift @_;
208             my $source = $self->{'_main_w'}->getSaveFile();
209             $self->{'_midi_source'} = $source;
210             $self->{'_document'}->writefile($source);
211            
212             }
213            
214             #===============================================================================
215              
216             sub _file_close {
217             my $self = shift @_;
218             print "File Close\n";
219            
220             }
221            
222             #===============================================================================
223              
224             sub _file_exit {
225             exit;
226             }
227            
228             #===============================================================================
229             # Create the menu items for the File menu.
230              
231             sub _file_menuitems {
232             my $self = shift @_;
233              
234             return
235             [
236             ['command', '~New', '-accelerator'=>'Ctrl-n', '-command' => sub {$self->_file_new; }],
237             '',
238             ['command', '~Open', '-accelerator'=>'Ctrl-o', '-command' => sub {$self->_file_open; }],
239             '',
240             ['command', '~Save', '-accelerator'=>'Ctrl-s', '-command' => sub {$self->_file_save; }],
241             ['command', 'S~ave As ...', '-accelerator'=>'Ctrl-a', '-command' => sub {$self->_file_save_as; }],
242             '',
243             ['command', '~Import ...', '-accelerator'=>'Ctrl-i', '-command' => sub {$self->_file_import; }],
244             ['command', '~Export ...', '-accelerator'=>'Ctrl-e', '-command' => sub {$self->_file_export; }],
245             '',
246             ['command', '~Close', '-accelerator'=>'Ctrl-w', '-command' => sub {$self->_file_close; }],
247             '',
248             ['command', '~Quit', '-accelerator'=>'Ctrl-q', '-command' => sub {$self->_file_exit; }],
249             ];
250              
251             }
252              
253             #===============================================================================
254              
255             sub _edit_fix_lyrics {
256             my $self = shift @_;
257            
258             # my @tracks = $model->getElementsByTagName('Track');
259             my $model = $self->{'_model'};
260             my @lyrics = $model->getElementsByTagName('Lyric');
261             foreach my $lyric (@lyrics) {
262             my $text = $lyric->text();
263             if ($text =~ s/-$//) {
264             $lyric->text($text);
265             }
266             elsif ($text =~ / $/) {
267             } else {
268             $lyric->text("$text ");
269             }
270             }
271             }
272              
273             #===============================================================================
274             # Create the menu items for the Edit menu.
275              
276             sub _edit_menuitems {
277             my $self = shift @_;
278             [
279             ['command', '~Fix Lyrics', '-command' => sub {$self->_edit_fix_lyrics; }],
280             ['command', 'Preferences ...'],
281             ];
282             }
283              
284             #===============================================================================
285              
286             sub _insert_measures {
287             my $self = shift @_;
288            
289             my $document = $self->{'_document'};
290             my $model = $self->{'_model'};
291             my @tracks = $model->getElementsByTagName('Track');
292             my $measures = $document->measures();
293             foreach my $track (@tracks) {
294             my @events = $track->getElementsByTagName('Event');
295             my $e_abs = 0;
296             my $e = 0;
297             my $mno = 0;
298             # foreach my $measure (@{$measures}) {
299             while (defined($measures->[$mno]) and $e <= $#events) {
300             my $measure = $measures->[$mno];
301             my $m_abs = $measure->[0];
302             my $event = $events[$e];
303             # print "$m_abs <= $e_abs\n";
304             if ($m_abs <= $e_abs) {
305             $mno++;
306             my $data = "type=\"measure\" time=\"$m_abs\" number=\"$mno\"";
307             my $pi = $document->createProcessingInstruction('midi-xml', $data);
308             my $prev = $event->getPreviousSibling();
309             $event = $events[$e-1] if ($e > 0); # and $m_abs == $e_abs
310             $track->insertBefore($pi,$event);
311             if ($prev->getNodeType == 3) {
312             $track->insertBefore($document->createTextNode($prev->getNodeValue()),$event);
313             }
314             } else {
315             # while ($m_abs > $e_abs) {
316             if ($e <= $#events) {
317             $event = $events[$e];
318             my $timestamp = $event->Timestamp;
319             my $value = $timestamp->value();
320             my $tsclass = ref($timestamp);
321             if ($tsclass eq 'MIDI::XML::Delta') {
322             $e_abs += $value;
323             } elsif ($tsclass eq 'MIDI::XML::Absolute') {
324             $e_abs = $value;
325             } else {
326             print "\$tsclass = $tsclass\n";
327             }
328             } else {
329             $e_abs = $m_abs;
330             }
331             $e++;
332             # }
333             }
334             }
335             }
336             }
337              
338             #===============================================================================
339             # Create the menu items for the Insert menu.
340              
341             sub _insert_menuitems {
342             my $self = shift @_;
343             [
344             ['command', '~Measures', '-accelerator'=>'Ctrl-m', '-command' => sub {$self->_insert_measures; }],
345             ];
346             }
347              
348             #===============================================================================
349              
350             sub _help_version {
351             print "MIDI::XML::Editor Version $VERSION\n";
352             }
353              
354             #===============================================================================
355              
356             sub _help_about {
357             print "Help About\n";
358             }
359              
360             #===============================================================================
361             # Create the menu items for the Help menu.
362              
363             sub _help_menuitems {
364             my $self = shift @_;
365             [
366             ['command', 'Version', '-command' => sub {$self->_help_version;}],
367             '',
368             ['command', 'About', '-command' => sub {$self->_help_about;}],
369             ];
370             }
371              
372              
373             #===============================================================================
374              
375             =head2 $Object = MIDI::XML::Document->new();
376              
377             Create a new MIDI::XML::Document object.
378              
379             =cut
380              
381             sub new() {
382             my $class = shift;
383             $class = ref($class) || $class;
384            
385             my $self = {};
386             bless $self,$class;
387            
388             $self->{'_status_msg'} = "";
389             $self->{'_title'} = 'MIDI XML Editor';
390             $self->{'_pretty'} = 1;
391             $self->{'_format'} = 99;
392             $self->{'_ticksPerBeat'} = 1384;
393             $self->{'_trackCount'} = 99;
394             $self->{'_timestampType'} ='Absolute_',
395            
396             my $main_w = MainWindow->new();
397             $self->{'_main_w'} = $main_w;
398             # $main_w->configure(-width => 600, -height => 800,);
399             $main_w->title($self->{'_title'});
400              
401             #-------------------------------------------------------------------------------
402             my $menu_f = $main_w->Frame(
403             -relief => 'groove',
404             -bd => 2,
405             )->grid(
406             "-",
407             -sticky => "nsew",
408             );
409            
410             #$menu_f->Button(-text => "Exit", -command => sub { exit; } )->
411             # pack(-side => 'right');
412             #$menu_f->Button(-text => "Save", -command => \&save_file)->
413             # pack(-side => 'right', -anchor => 'e');
414             #$menu_f->Button(-text => "Load", -command => \&load_file)->
415             # pack(-side => 'right', -anchor => 'e');
416            
417             my $file = $menu_f->Menubutton(qw/-text File -underline 0/,
418             -menuitems => $self->_file_menuitems);
419             my $edit = $menu_f->Menubutton(qw/-text Edit -underline 0/,
420             -menuitems => $self->_edit_menuitems);
421             my $insert = $menu_f->Menubutton(qw/-text Insert -underline 0/,
422             -menuitems => $self->_insert_menuitems);
423             my $help = $menu_f->Menubutton(qw/-text Help -underline 0/,
424             -menuitems => $self->_help_menuitems);
425              
426             # In Unix the Help menubutton is right justified.
427              
428             $file->pack(qw/-side left/);
429             $edit->pack(qw/-side left/);
430             $insert->pack(qw/-side left/);
431             $help->pack(qw/-side right/);
432              
433              
434             # my $menubar = $menu_f->Menu(-type => 'menubar');
435             # $menu_f->configure(-menu => $menubar);
436              
437             # map {$menubar->cascade( -label => '~' . $_->[0], -menuitems => $_->[1] )}
438             # ['File', _file_menuitems],
439             # ['Edit', _edit_menuitems],
440             # ['Help', _help_menuitems];
441              
442             # $self->{'_menu_f'} = $menu_f;
443             # $menu_f->Label(
444             # -textvariable => \$self->{'_status_msg'},
445             # )->pack(
446             # -side => 'bottom',
447             # -fill => 'x'
448             # );
449              
450             #-------------------------------------------------------------------------------
451             my $east_f = $main_w->Frame(
452             # -relief => 'groove',
453             # -bd => 2,
454             -width => 480,
455             -height => 600,
456             );
457             $self->{'_east_f'} = $east_f;
458              
459             #-------------------------------------------------------------------------------
460             my $tree_f = $main_w->Frame(
461             -relief => 'groove',
462             -bd => 2,
463             )->grid(
464             $east_f,
465             -sticky => "nsew",
466             );
467             $self->{'_tree_f'} = $tree_f;
468              
469             #-------------------------------------------------------------------------------
470             my $status_f = $main_w->Frame(
471             -relief => 'groove',
472             -bd => 2,
473             )->grid(
474             "-",
475             -sticky => "nsew",
476             );
477             $self->{'_status_f'} = $status_f;
478             my $status_l = $status_f->Label(
479             -textvariable => \$self->{'_status_msg'},
480             )->pack(
481             -side => 'left',
482             -fill => 'x'
483             );
484             $self->{'_status_l'} = $status_l;
485             #-------------------------------------------------------------------------------
486             my $object_f = $east_f->Frame(
487             -relief => 'groove',
488             -bd => 2,
489             -width => 480,
490             -height => 600,
491             )->pack(
492             -side => 'top',
493             -fill => 'both',
494             -expand => 1,
495             );
496             $self->{'_object_f'} = $object_f;
497            
498             my $midifile_f = $object_f->Frame(
499             -relief => 'flat',
500             -bd => 2,
501             -width => 480,
502             -height => 600,
503             )->grid(
504             -sticky => "nsew",
505             );
506             $self->{'_midifile_f'} = $midifile_f;
507            
508             foreach my $item (
509             ['Format', \$self->{'_format'}],
510             ['TicksPerBeat', \$self->{'_ticksPerBeat'}],
511             ['TrackCount', \$self->{'_trackCount'}],
512             ['TimestampType', \$self->{'_timestampType'}],
513             ) {
514             my $ltxt = $item->[0] . ':';
515             my $f = $midifile_f->Frame(
516             -width => 400,
517             );
518             my $e = $midifile_f->Entry( -relief => 'groove',
519             -state => 'disabled',
520             -textvariable => $item->[1],
521             -width => 10,
522             -background => '#FFFFFF',
523             -highlightbackground => '#FFFFFF',
524             -insertbackground => '#FFFFFF',
525             -state => 'normal',
526             );
527             my $l = $midifile_f->Label(
528             -text => $ltxt,
529             -width => 16,
530             -anchor => 'w',
531             )->grid(
532             $e,
533             $f,
534             -sticky => "w",
535             );
536             }
537             my $f = $midifile_f->Frame(
538             -width => 480,
539             -height => 600,
540             )->grid('-','-');
541              
542             #-------------------------------------------------------------------------------
543             my $button_f = $east_f->Frame(
544             -relief => 'groove',
545             -bd => 2,
546             )->pack(
547             -side => 'bottom',
548             -fill => 'x',
549             );
550             $self->{'_button_f'} = $button_f;
551              
552             my $open_b = $button_f->Button(
553             -text => "Open",
554             -command => sub { $self->_file_open(); },
555             );
556             $self->{'_open_b'} = $open_b;
557             $self->_bind_message($open_b, 'Press to open file.');
558            
559             my $save_b = $button_f->Button(
560             -text => "Save",
561             -command => sub { $self->_file_save_as(); },
562             -state => 'disabled',
563             );
564             $self->{'_save_b'} = $save_b;
565             $self->_bind_message($save_b, 'Press to save file.');
566            
567             my $exit_b = $button_f->Button(
568             -text => "Exit",
569             -command => sub { $self->_file_exit(); },
570             );
571             $self->{'_exit_b'} = $exit_b;
572             $self->_bind_message($exit_b, 'Press to exit editor.');
573             $open_b->grid(
574             $save_b,
575             $exit_b,
576             -padx => 2,
577             -pady => 2,
578             );
579              
580             my $tree = $tree_f->Scrolled(
581             "Tree",
582             -width => 32,
583             # -height => 600,
584             -command => sub {$self->_tree_click(@_);},
585             )->pack(
586             -fill => 'both',
587             -expand => 1,
588             );
589             $self->{'_tree'} = $tree;
590              
591             foreach (qw/header track track.one track.one.m1 track.one.m2 track.one.m3 track.two track.three track.four/) {
592             $tree->add($_, -text => $_);
593             }
594              
595             $tree->autosetmode( );
596              
597             MainLoop;
598             return $self;
599             }
600              
601