File Coverage

blib/lib/GTM.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              
2             package GTM;
3              
4             our $VERSION = "0.6";
5              
6 1     1   26853 use common::sense;
  1         11  
  1         6  
7              
8 1     1   1196 use utf8;
  1         11  
  1         6  
9 1     1   538 use Gtk2;
  0            
  0            
10             use Gtk2::SimpleMenu ();
11             use AnyEvent;
12             use AnyEvent::Util;
13             use File::HomeDir ();
14             use Gtk2::Ex::PodViewer ();
15             use POSIX qw(setsid _exit);
16              
17             =head1 NAME
18              
19             GTM - A gui frontend for the GT.M database
20              
21             =head1 SYNOPSIS
22              
23             gtm
24              
25             run the gtm frontend
26              
27             =head1 FILES
28              
29             ~/.gtmrc
30              
31             preferences (you can source it).
32              
33             =cut
34              
35             BEGIN {
36             use base 'Exporter';
37             our @EXPORT_OK = qw(set_busy output %override save_prefs);
38             our @EXPORT = ();
39             }
40              
41             use GTM::Run ();
42              
43             our %override;
44              
45             our ($gtm_version, $gtm_utf8);
46             our @gtm_variables = (qw/gtm_dist gtmroutines gtmgbldir gtm_log gtm_chset gtm_icu_version/);
47              
48             our %win_size;
49              
50             sub win_size ($$;$$) {
51             my ($w, $n, $x, $y) = @_;
52              
53             unless (exists $win_size{$n}) {
54             $win_size{$n} = [ $x || 960, $y || 600 ];
55              
56             }
57             $w->signal_connect (
58             size_allocate => sub {
59             $win_size{$n} = [ $_[1]->width, $_[1]->height ];
60             }
61             );
62             $w->resize (@{$win_size{$n}});
63              
64             }
65              
66             my $main_window;
67              
68             sub error_dialog ($@) {
69             my ($parent, @data) = @_;
70             my $dialog = new Gtk2::Dialog ("Program Error, \$\@ exception raised.", $parent, 'modal', OK => 42);
71             win_size ($dialog, "error_dialog", 670, 320);
72             $dialog->set_default_response (42);
73             my $sa = new_scrolled_textarea ();
74             $sa->set_size_request (660, 300);
75             scrollarea_output ($sa, join "", @data);
76             $dialog->vbox->add ($sa);
77             $dialog->show_all;
78             $dialog->run;
79             $dialog->destroy;
80             }
81              
82             sub gtm_doc ($$) {
83             my ($parent, $file) = @_;
84             my $dialog = new Gtk2::Dialog ("Documentation", $parent, 'modal', OK => 42);
85             $dialog->set_default_response (42);
86             my $pod = new Gtk2::Ex::PodViewer;
87             my $file = findfile ("GTM/$file");
88             $pod->load ($file);
89             $pod->set_size_request (660, 620);
90             $dialog->vbox->add ($pod);
91             $dialog->show_all;
92             $dialog->run;
93             $dialog->destroy;
94              
95             }
96              
97             sub new_scrolled_textarea () {
98             my $tv = new Gtk2::TextView;
99             my $s = new Gtk2::ScrolledWindow;
100             $s->add ($tv);
101             $tv->set_editable (0);
102             $tv->set_cursor_visible (0);
103             my $buffer = $tv->get_buffer;
104             my $end_mark = $buffer->create_mark ('end', $buffer->get_end_iter, 0);
105             $s->{end} = $end_mark;
106             $s->{tv} = $tv;
107             $s->can_focus (0);
108             $tv->can_focus (0);
109             my $font_desc = Gtk2::Pango::FontDescription->from_string ("monospace 10");
110             $tv->modify_font ($font_desc);
111             $s;
112             }
113              
114             sub scrollarea_clear ($) {
115             my $s = shift;
116             $s->{tv}->set_buffer (new Gtk2::TextBuffer);
117             my $buffer = $s->{tv}->get_buffer;
118             my $end_mark = $buffer->create_mark ('end', $buffer->get_end_iter, 0);
119             $s->{end} = $end_mark;
120             }
121              
122             sub scrollarea_output ($@) {
123             my ($sa, @d) = @_;
124             my $tv = $sa->{tv};
125             my $lines = join "", @d;
126             my $buf = $tv->get_buffer;
127             $buf->insert ($buf->get_end_iter, $lines);
128             $tv->scroll_to_mark ($sa->{end}, 0, 1, 0, 1);
129              
130             }
131              
132             my $rcfile = my_home File::HomeDir . "/.gtmrc";
133              
134             sub save_prefs () {
135             open my $fh, ">", $rcfile
136             or do { warn "can't create '$rcfile': $!"; return; };
137              
138             while (my ($k, $v) = each %win_size) {
139             print $fh "# win=$k w=$v->[0] h=$v->[1]\n";
140             }
141              
142             while (my ($k, $v) = each %override) {
143             $v =~ s/"/\\"/g;
144             print $fh "$k=\"$v\"\nexport $k\n\n";
145             }
146             }
147              
148             sub load_prefs () {
149             open my $fh, "<", $rcfile
150             or do { warn "can't open '$rcfile': $!"; return; };
151             while (my $line = <$fh>) {
152             if ($line =~ /^#\s+win=(\w+)\s+w=(\d+)\s+h=(\d+)$/) {
153             my ($window, $win_width, $win_height) = ($1, $2, $3);
154             $win_size{$window} = [ $win_width, $win_height ];
155             }
156             if ($line =~ /^(gtm\w+)=\"(.*)\"$/) {
157             my ($k, $v) = ($1, $2);
158             $v =~ s/\\"/"/g;
159             $override{$k} = $v;
160             }
161             }
162              
163             }
164              
165             # as you can see, i don't like xterm :)
166             # run update-alternatives --config x-terminal-emulator
167             # to set the default terminal type
168             sub run_console () {
169             my $pid = fork;
170             return unless $pid == 0;
171             local %ENV = (%ENV, %override);
172             setsid;
173             exec ($_, "-e", "$ENV{gtm_dist}/mumps", "-direct")
174             for (
175             qw/x-terminal-emulator urxvt
176             rxvt-unicode rxvt Eterm
177             konsole xterm/
178             );
179              
180             _exit (0);
181             }
182              
183             sub ident_file ($) {
184             my $f = shift;
185             open my $fh, "<", $f or return;
186             sysread $fh, my $buffer, 512;
187              
188             # dies ist der header comment UTF-8
189             # GT.M 09-FEB-2010 10:17:47
190              
191             return ("gtm-globals", $1)
192             if (
193             $buffer =~ m/ ^ (.*) \015? \012
194             GT\.M \s+
195             \d+ - [A-Z]{3} - \d{4} \s+
196             \d+ : \d+ : \d+
197             /sx
198             );
199              
200             # Cache for Windows NT^INT^dies ist die description^~Format=Cache.S~
201             # %RO on 08 Feb 2010 4:19 PM
202              
203             return ("cac-routines", $1)
204             if (
205             $buffer =~ m/ ^Cache \s+ for \s+ .*?
206             \^ .*? \^ (.*?) \^
207             .*? \015? \012
208             \% RO \s+ on \s+ \d+
209             /sx
210             );
211              
212             # dies ist die description~Format=5.S~
213             # 08 Feb 2010 4:17 PM Cache
214             return ("cac-globals", $1)
215             if (
216             $buffer =~ m/(.*?) ~Format= .*? \015? \012
217             \d+ \s+ [A-Z][a-z]{2} \s+ \d{4} \s+
218             \d+ : \d+ \s+ (?:AM|PM) \s+ Cache
219             /sx
220             );
221              
222             return ("msm-globals", $1)
223             if (
224             $buffer =~ m/ ^\s? \d+ : \d+ \s+ (?:AM|PM)
225             \s+ \d+ \- [A-Z]{3} \- \d+
226             \s+ \(MSM \s+ format \)
227             \015? \012 (.*?) \015? \012
228             /sx
229             );
230              
231             # 4:22 PM 8-FEB-10
232             # dies ist der header comment
233             return ("msm-routines", $1)
234             if (
235             $buffer =~ m/ ^\s? \d+ : \d+ \s+ (?:AM|PM)
236             \s+ \d+ \- [A-Z]{3} \- \d+
237             \015? \012 (.*?) \015? \012
238             /sx
239             );
240              
241             return;
242             }
243              
244             sub gtm_file_chooser ($$$$;$) {
245             my ($title, $parent, $action, $cb, $fcb) = @_;
246              
247             my $fc =
248             Gtk2::FileChooserDialog->new (
249             $title, $parent, $action,
250             'gtk-cancel' => 'cancel',
251             'gtk-ok' => 'ok',
252             );
253             if ($fcb) {
254             my $ff = new Gtk2::FileFilter;
255             $ff->add_custom (
256             "filename",
257             sub {
258             my $f = shift->{filename};
259             $fcb->($f);
260             }
261             );
262             $fc->add_filter ($ff);
263             }
264             if ($fc->run eq 'ok') {
265             $cb->($fc->get_filename);
266             }
267             $fc->destroy;
268              
269             }
270              
271             sub nice_globals (@) {
272             my $line;
273             my $o;
274             for my $g (@_) {
275             if (length ($line) + length ($g) > 78) {
276             $o .= "$line\n";
277             $line = "";
278             }
279             $line .= "^$g ";
280             $line .= " " while (length ($line) % 10);
281             }
282             $o .= "$line\n" if length ($line);
283             $o;
284              
285             }
286              
287             sub gsel_pattern ($$$) {
288             my ($ga, $gs, $pat) = @_;
289             my %g;
290              
291             $g{$_} = 0 for (@$ga);
292             $g{$_} = 1 for (@$gs);
293              
294             my $sel = 1 - $pat =~ s/^\-//s;
295              
296             if ($pat =~ /^([%A-Z][A-Z0-9]*) - ([%A-Z][A-Z0-9]*) /isx) {
297             my ($from, $to) = ($1, $2);
298             for my $g (@$ga) {
299             $g{$g} = $sel if $g ge $from && $g le $to;
300             }
301             } elsif ($pat =~ m|^/|) {
302             if ($pat =~ m|^/invert|) {
303             $g{$_} = 1 - $g{$_} for (keys %g);
304             }
305              
306             } else {
307             $pat =~ s/\?/\./sg;
308             $pat =~ s/\*/\.\*\?/sg;
309             $pat =~ s/[+\{]//sg;
310             $pat = "^$pat\$";
311             eval {
312             for my $g (@$ga)
313             {
314             $g{$g} = $sel if $g =~ m/$pat/ms;
315             }
316             };
317             warn "invalid pattern: $@" if $@;
318              
319             }
320              
321             @$gs = ();
322             for my $k (sort keys %g) {
323             push @$gs, $k if $g{$k};
324             }
325             }
326              
327             sub gtm_gsel1 (&) {
328             my $cb = shift;
329             my $lines;
330             my @ga;
331             gtm_run (
332             [qw[ mumps -direct ]],
333             ">" => \$lines,
334             "2>" => \$lines,
335             "<" => \"s x=\"^\%\" F H:x=\"\" W:\$D(\@x) x,! s x=\$O(\@x)\nH\n",
336             cb => sub {
337             push @ga, $1 while ($lines =~ m|^\^(.*)$|gm);
338             $cb->(@ga);
339             }
340             );
341             }
342              
343             sub gtm_gsel ($;$$) {
344             my ($parent, $cb, $glb) = @_;
345             my $on_entry;
346             my $dialog = new Gtk2::Dialog (
347             "Global selector", $parent, 'modal',
348             'gtk-cancel' => 0,
349             OK => 42
350             );
351             win_size ($dialog, "global_selector", 680, 320);
352             my ($f0, $f1) = (new Gtk2::Frame (), new Gtk2::Frame ("Selected Globals"));
353             $f0->set_border_width (5);
354             $f1->set_border_width (5);
355             my ($s0, $s1) = (new_scrolled_textarea(), new_scrolled_textarea());
356             $s0->set_size_request (660, 300);
357             $s1->set_size_request (660, 300);
358             my @globals;
359             my @selected = @$glb;
360             gtm_gsel1 (
361             sub {
362             @globals = @_;
363             scrollarea_output ($s0, nice_globals (@globals));
364             $f0->set_label (@globals . " globals available.");
365             }
366             );
367              
368             $f0->add ($s0);
369             $f1->add ($s1);
370             $dialog->vbox->add ($f0);
371             $dialog->vbox->add ($f1);
372              
373             my $hb = new Gtk2::HBox;
374             my $e = new Gtk2::Entry;
375             $e->signal_connect (
376             'activate' => sub {
377             $dialog->response (42) unless (length $e->get_text);
378             gsel_pattern (\@globals, \@selected, $e->get_text);
379             scrollarea_clear ($s1);
380             scrollarea_output ($s1, nice_globals (@selected));
381             $f1->set_label (@selected . " globals selected");
382             $e->set_text ("");
383             }
384             );
385              
386             if (!$on_entry++ && @selected) {
387             scrollarea_output ($s1, nice_globals (@selected)) if @selected;
388             $f1->set_label (@selected . " globals selected");
389             }
390              
391             my $b = new Gtk2::Button ("Global ^");
392             $b->signal_connect ('clicked' => sub { gtm_doc ($dialog, "global-selector.pod"); });
393              
394             $hb->pack_start ($b, 0, 0, 0);
395             $hb->add ($e);
396              
397             $dialog->vbox->add ($hb);
398              
399             $dialog->set_default_response (42);
400             $dialog->set_focus ($e);
401             $dialog->show_all;
402             if ($dialog->run == 42) {
403             @$glb = @selected if $glb;
404             $cb->(\@selected) if $cb;
405             }
406             $dialog->destroy;
407             }
408              
409             sub gtm_go_run ($$$$) {
410             my ($file, $mode, $hc, $globals) = @_;
411              
412             #$override{gtm_icu_version} = "";
413             my $h = new GTM::Run ([qw[mumps -direct]]);
414             $h->debug (0);
415             $mode = "ZWR" unless $mode eq "GO";
416             $h->expect (
417             qr/GTM\>/,
418             qr/^%.*/m,
419             sub {
420             die $_[1] if $_[2];
421             shift->write ("D ^\%GO\n");
422             },
423              
424             qr/ZGBLDIRACC/m,
425             qr/^Global \^/m,
426             sub {
427             my ($hdl, $data, $idx) = @_;
428             unless ($idx) {
429             $hdl->write ("\nHalt\n");
430             die "global selector $_[1]";
431             }
432             $hdl->write ("$_\n") for (@$globals);
433             $hdl->write ("\n");
434             },
435              
436             qr/^No globals selected/m,
437             qr/^Header Label:/m,
438             sub {
439             my ($hdl, $data, $idx) = @_;
440             if (!$idx) {
441             $hdl->write ("\nHalt\n");
442             die "no globals selected: $_[1]";
443             }
444             $hdl->write ("$hc\n");
445             },
446              
447             qr/ZWR:/, sub { shift->write ("$mode\n"); },
448              
449             qr//,
450             sub { shift->write ("$file\n"); },
451             );
452              
453             $h->expect (
454             qr//,
455             qr/GTM>/,
456             qr/.+(?=GTM>)/ms,
457             sub {
458             my ($hdl, $data, $idx) = @_;
459             if (!$idx) {
460             $hdl->write ("^\n\nHalt\n");
461             die "can't open file \"$file\"";
462             }
463             if ($idx == 2) {
464             output ($data);
465             } else {
466             $hdl->write ("\nHalt\n");
467             $hdl->close;
468             return;
469             }
470             },
471             );
472              
473             }
474              
475             sub gtm_go ($) {
476             my $parent = shift;
477             my @g = ();
478             my $dialog = new Gtk2::Dialog (
479             "Global Output (\%GO)", $parent, 'modal',
480             'gtk-cancel' => 0,
481             OK => 42
482             );
483             $dialog->set_default_response (42);
484              
485             my $gsel = new Gtk2::Button ("Global Selector");
486             $gsel->signal_connect (
487             clicked => sub {
488             gtm_gsel (
489             $dialog,
490             sub {
491             $gsel->set_label (sprintf "Global Selector - %d Globals selected", scalar @{$_[0]});
492             },
493             \@g
494             );
495             }
496             );
497             my $fe = new Gtk2::Entry;
498             my $prog = new Gtk2::Button ("File Selector");
499             $prog->signal_connect (
500             clicked => sub {
501             gtm_file_chooser ("Select output file", $dialog, "save", sub { $fe->set_text ($_[0]); });
502             }
503             );
504             my $box = new_text Gtk2::ComboBox;
505             my $hc = new Gtk2::Entry;
506             $box->append_text ($_) for (qw/ZWR GO/);
507             $box->set_active (0);
508              
509             my $hb0 = new Gtk2::HBox;
510             $hb0->add ($fe);
511             $hb0->add ($prog);
512             my $hb1 = new Gtk2::HBox;
513             my $l = new Gtk2::Label ("Header Label: ");
514             $hb1->add ($l);
515             $hb1->add ($hc);
516              
517             $dialog->vbox->add ($gsel);
518             $dialog->vbox->add ($hb0);
519             $dialog->vbox->add ($box);
520             $dialog->vbox->add ($hb1);
521              
522             $dialog->show_all;
523             if ($dialog->run == 42) {
524             my $hc = $hc->get_text;
525             my $file = $fe->get_text;
526             my $mode = $box->get_active_text;
527              
528             if (@g && length ($file)) {
529             eval { gtm_go_run ($file, $mode, $hc, \@g); };
530             error_dialog ($dialog, $@) if $@;
531             }
532              
533             }
534             $dialog->destroy;
535              
536             }
537              
538             sub gtm_backup () {
539             my $dir;
540             gtm_file_chooser (
541             "Select a target directory",
542             $main_window,
543             'select-folder',
544             sub {
545             $dir = $_[0];
546             return unless -d $dir;
547             gtm_run_out ([ "mupip", "backup", '*', $dir ]);
548             },
549             );
550              
551             }
552              
553             sub rr_msm ($$) {
554             my ($file, $dir) = @_;
555             open my $fh, "<", $file or do { warn "opening $file: $!\n"; return; };
556              
557             my ($lines, $cnt);
558             {
559             local $/;
560             $lines = <$fh>;
561             $lines =~ s/\015\012/\012/g;
562             }
563             while (
564             $lines =~ m/ ^ (\%?\w+) $
565             ( .*? \012 ) \012
566             /msgx
567             )
568             {
569             my ($f, $body) = ($1, $2);
570             $f =~ s/^\%/_/;
571             open my $out, ">", "$dir/$f.m" or die "opening $f.m: $!";
572             print $out $body;
573             ++$cnt;
574             output ("$f\n");
575             }
576             output ("Restored $cnt files...\n");
577             }
578              
579             sub rr_cache ($$) {
580             my ($file, $dir) = @_;
581             open my $fh, "<", $file or do { warn "opening $file: $!\n"; return; };
582              
583             my ($lines, $cnt);
584             {
585             local $/;
586             $lines = <$fh>;
587             $lines =~ s/\015\012/\012/g;
588             }
589             while (
590             $lines =~ m/ ^ (\%?\w+) \^ (?:INT|MAC|INC) \^ \d+ \^ \d+ , \d+ \^\d+ $
591             ( .*? \012 ) \012
592             /msgx
593             )
594             {
595             my ($f, $body) = ($1, $2);
596             $f =~ s/^\%/_/;
597             open my $out, ">", "$dir/$f.m" or die "opening $f.m: $!";
598             print $out $body;
599             ++$cnt;
600             output ("$f\n");
601             }
602             output ("Restored $cnt files...\n");
603             }
604              
605             sub gtm_rr ($$) {
606             my ($file, $dir) = @_;
607             if (!-d $dir) {
608             warn "not a directory: \"$dir\"\n";
609             return;
610             }
611             my ($type, $hc) = ident_file ($file);
612             unless ($type =~ m/routines$/) {
613             warn "$file: unsupported file format\n";
614             return;
615             }
616             output ("Restoring Files from file \"$file\" to directory \"$dir\"\n");
617             return $type eq "cac-routines"
618             ? rr_cache ($file, $dir)
619             : rr_msm ($file, $dir);
620              
621             }
622              
623             sub gtm_routine_restore () {
624              
625             my $dialog = new Gtk2::Dialog (
626             "Routine restore", $main_window, 'modal',
627             'gtk-cancel' => 0,
628             OK => 42
629             );
630             $dialog->set_default_response (42);
631             my $h0 = new Gtk2::HBox;
632             my $h1 = new Gtk2::HBox;
633             my $e0 = new Gtk2::Entry;
634             my $e1 = new Gtk2::Entry;
635             my $b0 = new Gtk2::Button ("choose file");
636             my $b1 = new Gtk2::Button ("choose output directory");
637             $e0->set_size_request (300, -1);
638             $e1->set_size_request (300, -1);
639             $b0->set_size_request (200, -1);
640             $b1->set_size_request (200, -1);
641              
642             $b0->signal_connect (
643             "clicked" => sub {
644             gtm_file_chooser (
645             "Select a MSM \%GS or Cache \%GO file",
646             $dialog, 'open',
647             sub { $e0->set_text ($_[0]); },
648             sub {
649             my ($i) = ident_file ($_[0]);
650             $i =~ m/routines$/;
651             }
652             ),
653             ;
654             }
655             );
656             $b1->signal_connect (
657             "clicked" => sub {
658             gtm_file_chooser ("Select a target directory", $dialog, 'select-folder', sub { $e1->set_text ($_[0]); },);
659             }
660             );
661             $h0->add ($e0);
662             $h1->add ($e1);
663             $h0->add ($b0);
664             $h1->add ($b1);
665              
666             $dialog->vbox->add ($h0);
667             $dialog->vbox->add ($h1);
668             $dialog->show_all;
669             if ($dialog->run == 42) {
670             my ($file, $dir) = ($e0->get_text, $e1->get_text);
671             gtm_rr ($file, $dir);
672             }
673             $dialog->destroy;
674             }
675              
676             sub filter_output (@) {
677             my $lines = join "", @_;
678             $lines =~ s/\nGTM\>\n//g;
679             output ($lines);
680             }
681              
682             sub gtm_gr ($) {
683             my $file = shift;
684             my ($type) = ident_file ($file);
685             if ($type !~ /globals$/) {
686             warn "$file: unsupported file format, terminating.\n";
687             return;
688             }
689             open my $fh, "<", $file
690             or do { warn "unable to open $file: $!\n"; return; };
691             my ($l0, $l1) = (scalar <$fh>, scalar <$fh>);
692             my $zwr = 0;
693             $zwr = 1 if ($l1 =~ /ZWR$/);
694             my $func = $zwr
695             ? sub {
696             my $l = <$fh>;
697             return "Halt\n" if length $l < 3;
698             "S $l";
699             }
700             : sub {
701             my ($g, $d) = (scalar <$fh>, scalar <$fh>);
702             $g =~ s/\015?\012//g;
703             $d =~ s/\015?\012//g;
704             $d =~ s/\"/\"\"/g;
705             return "Halt\n" if length ($g) < 2 || $g eq "*";
706             "S $g=\"$d\"\n";
707             };
708             gtm_run (
709             [qw|mumps -direct|],
710             ">" => sub { filter_output (@_); },
711             "2>" => sub { filter_output (@_); },
712             "<" => $func,
713             "cb" => sub { output ("Global restore ended.\n"); },
714             );
715              
716             }
717              
718             sub gtm_global_restore () {
719             my $dialog = new Gtk2::Dialog (
720             "Global restore", $main_window, 'modal',
721             'gtk-cancel' => 0,
722             OK => 42
723             );
724             $dialog->set_default_response (42);
725             my $h0 = new Gtk2::HBox;
726             my $e0 = new Gtk2::Entry;
727             my $b0 = new Gtk2::Button ("choose file");
728             $e0->set_size_request (300, -1);
729             $b0->set_size_request (200, -1);
730              
731             $b0->signal_connect (
732             "clicked" => sub {
733             gtm_file_chooser (
734             "Select a MSM \%GS or Cache \%GO file",
735             $dialog, 'open',
736             sub { $e0->set_text ($_[0]); },
737             sub {
738             my ($i) = ident_file ($_[0]);
739             $i =~ m/globals$/;
740             },
741             );
742             }
743             );
744             $h0->add ($e0);
745             $h0->add ($b0);
746              
747             $dialog->vbox->add ($h0);
748             $dialog->show_all;
749             if ($dialog->run == 42) {
750             my $file = $e0->get_text;
751             gtm_gr ($file);
752             }
753             $dialog->destroy;
754             }
755              
756             sub about_dialog () {
757             show_about_dialog Gtk2 (
758             $main_window,
759             "program-name" => 'GTM',
760             authors => [ 'Stefan Traby', ],
761             license => "This package is distributed under the same license as perl itself, i.e.\n"
762             . "either the Artistic License (COPYING.Artistic) or the GPLv2 (COPYING.GNU).",
763             copyright => "(c) 2010 by St.Traby ",
764             website => 'http://oesiman.de/gt.m/',
765             version => "v$VERSION",
766             comments => "",
767              
768             # artists => [ "Stefan Traby" ],
769             );
770             1;
771             }
772              
773             sub edit_environment (@) {
774             my $dialog = new Gtk2::Dialog (
775             "Customize environment", $main_window, 'modal',
776             'gtk-cancel' => 0,
777             OK => 42
778             );
779             $dialog->set_default_response (42);
780             my @vars = @_;
781             my $cnt = @vars;
782             my $t = new Gtk2::Table ($cnt + 1, 3, 0);
783             my $e0 = new Gtk2::Entry;
784             my $e1 = new Gtk2::Entry;
785             my $e2 = new Gtk2::Entry;
786             my $l0 = new Gtk2::Label ("Environment Variable");
787             my $l1 = new Gtk2::Label ("Environment Value");
788             my $l2 = new Gtk2::Label ("Environment Override");
789             $l1->set_size_request (400, -1);
790             $l2->set_size_request (400, -1);
791              
792             $t->attach_defaults ($l0, 0, 1, 0, 1);
793             $t->attach_defaults ($l1, 1, 2, 0, 1);
794             $t->attach_defaults ($l2, 2, 3, 0, 1);
795             my @entries;
796             for my $i (0 .. $cnt - 1) {
797             my $env = new Gtk2::Entry;
798             $env->set_editable (0);
799             $env->set_text ($vars[$i]);
800             $env->can_focus (0);
801             $t->attach_defaults ($env, 0, 1, $i + 1, $i + 2);
802              
803             my $val = new Gtk2::Entry;
804             $val->set_editable (0);
805             $val->can_focus (0);
806             my $v = $ENV{$vars[$i]};
807             unless (exists $ENV{$vars[$i]}) {
808             $v = '<<>>';
809             $val->modify_base ('GTK_STATE_NORMAL', new Gtk2::Gdk::Color (65535, 65535, 1000));
810             }
811             $val->set_text ($v);
812             $t->attach_defaults ($val, 1, 2, $i + 1, $i + 2);
813              
814             my $e = new Gtk2::Entry;
815             my $v = $override{$vars[$i]};
816             $e->set_text ($v);
817             $t->attach_defaults ($e, 2, 3, $i + 1, $i + 2);
818             $entries[$i] = $e;
819              
820             }
821             $dialog->vbox->add ($t);
822              
823             $dialog->show_all;
824             if ($dialog->run == 42) {
825             for (my $i = 0 ; $i < $cnt ; $i++) {
826             my $k = $vars[$i];
827             my $v = $entries[$i]->get_text;
828             delete $override{$k};
829             $override{$k} = $v if length $v;
830             }
831              
832             get_gtm_version ();
833             save_prefs;
834             }
835             $dialog->destroy;
836             }
837              
838             my $menu_tree = [
839             _File => {
840             item_type => '',
841             children => [
842             "_Routine Restore" => {
843             callback => sub { gtm_routine_restore; },
844             accelerator => 'F2',
845             },
846             "_Global Restore" => {
847             callback => sub { gtm_global_restore; },
848             accelerator => 'F3',
849             },
850             'Global _Output (%GO)' => {callback => sub { gtm_go ($main_window); },},
851              
852             Separator => {item_type => '',},
853             "_Console" => {
854             callback => sub { run_console; },
855             accelerator => 'C',
856             },
857             Separator => {item_type => '',},
858             E_xit => {
859             callback => sub { main_quit Gtk2; },
860             accelerator => 'X',
861             },
862             ],
863             },
864              
865             _Variables => {
866             item_type => '',
867             children => [
868             '_Edit all variables' => {callback => sub { edit_environment (@gtm_variables) },},
869             '_Clear all overrides' => {callback => sub { %override = (); save_prefs(); },},
870             Separator => {item_type => '',},
871             ],
872             },
873              
874             _Database => {
875             item_type => '',
876             children => [
877             '_Integrity check' => {
878             callback => sub { gtm_integ (); }
879             },
880             '_Rundown' => {
881             callback => sub {
882             gtm_rundown ();
883             },
884             accelerator => 'R'
885             },
886             Separator => {item_type => '',},
887             '_Freeze Database' => {
888             callback => sub {
889             gtm_freeze (1);
890             }
891             },
892             '_Thaw Database' => {
893             callback => sub {
894             gtm_freeze (0);
895             }
896             },
897             Separator => {item_type => '',},
898             '_Backup Database' => {
899             callback => sub {
900             gtm_backup();
901             }
902             },
903              
904             ],
905             },
906              
907             _Locks => {
908             item_type => '',
909             children => [
910             'Manage Locks' => {
911             callback => sub {
912             gtm_locks ();
913             }
914             },
915             ],
916             },
917             _Journal => {
918             item_type => '',
919             children => [
920             '_Enable\/switch Journal' => {
921             callback => sub {
922             gtm_journal (1);
923             }
924             },
925             '_Disable Journal' => {
926             callback => sub {
927             gtm_journal (0);
928             }
929             }
930             ],
931             },
932              
933             "_?" => {
934             item_type => '',
935             children => [
936             _About => {
937             callback => sub { about_dialog; },
938             accelerator => 'F1',
939             }
940             ],
941             },
942              
943             ];
944             for my $x (@gtm_variables) {
945             my $y = $x;
946             $y =~ s/_/__/g;
947             push @{$menu_tree->[3]{children}}, $y => {
948             callback => sub { edit_environment ($x); }
949             };
950             }
951              
952             #$buffer->signal_connect (insert_text => sub {
953             # $tv->scroll_to_mark($end_mark, 0, 1, 0, 1);
954             # }
955             # );
956              
957             my $main_scroll;
958              
959             sub output {
960             my $lines = join "", @_;
961             return unless length $lines;
962             scrollarea_output ($main_scroll, $lines);
963             }
964              
965             sub gtm_run ($@) {
966             set_busy (1);
967             local %ENV = (%ENV, %override);
968             my ($cmd, %rest) = @_;
969             if (ref $cmd eq "ARRAY") {
970             $cmd->[0] = "$ENV{gtm_dist}/$cmd->[0]" unless $cmd->[0] =~ m@^/@;
971             } else {
972             $cmd = "$ENV{gtm_dist}/$cmd" unless $cmd =~ m@^/@;
973             }
974             output "#" x 78 . "\n";
975             output "# running: ", ref $cmd eq "ARRAY" ? join " ", @$cmd : $cmd;
976             output "\n" . "#" x 78 . "\n";
977             my $cv = run_cmd ($cmd, %rest);
978             $cv->cb (
979             sub {
980             shift->recv
981             and do { warn "error running cmd: $!\n"; set_busy (0); return; };
982             $rest{cb}->() if exists $rest{cb};
983             set_busy (0);
984             }
985             );
986             }
987              
988             sub gtm_run_out (@) {
989             my ($cmd, %r) = (
990             shift,
991             ">" => sub { output (@_); },
992             "2>" => sub { output (@_); },
993             @_
994             );
995             gtm_run ($cmd, %r);
996             }
997              
998             sub get_gtm_version () {
999             my $lines;
1000             gtm_run (
1001             [qw[ mumps -direct ]],
1002             ">" => \$lines,
1003             "2>" => \$lines,
1004             "<" => \"Write \$C(26)_\$ZVersion_\$C(26)_\$ZCHset_\$C(26) Halt\n",
1005             cb => sub {
1006             output ("$lines\n");
1007             if ($lines =~ m/\x1a([^\x1a]+)\x1a([^\x1a]+)\x1a/ms) {
1008             $gtm_version = $1;
1009             $gtm_utf8 = 1;
1010             $gtm_utf8 = 0 if $2 eq "M";
1011             $main_window->set_title ("GT.M GUI v$VERSION ($gtm_version) UTF-8=$gtm_utf8");
1012             }
1013             }
1014             );
1015             }
1016              
1017             sub gtm_integ () {
1018              
1019             # gtm_run_out ([ qw[ mupip integ -full -noonline -reg * ]]);
1020             gtm_run_out ([qw[ mupip integ -noonline -reg * ]]);
1021             }
1022              
1023             sub gtm_rundown () {
1024             gtm_run_out ([qw[ mupip rundown /REG=* ]]);
1025             }
1026              
1027             sub gtm_freeze ($) {
1028             if ($_[0]) {
1029             gtm_run_out ([qw[ mupip freeze -on * ]]);
1030             } else {
1031             gtm_run_out ([qw[ mupip freeze -off * ]]);
1032             }
1033             }
1034              
1035             sub gtm_journal ($) {
1036             if ($_[0]) {
1037             gtm_run_out ([qw[ mupip SET -JOURNAL=ON,BEFORE_IMAGES -REGION * ]]);
1038             } else {
1039             gtm_run_out ([qw[ mupip SET -JOURNAL=OFF -REGION * ]]);
1040             }
1041             }
1042              
1043             sub remove_lock($$$) {
1044             my ($ref, $pid, $cb) = @_;
1045             gtm_run (
1046             [ "lke", "clear", "-pid=$pid", "-lock=$ref", "-nointeractive" ],
1047             ">" => sub { output (@_) },
1048             "2>" => sub { output (@_) },
1049             $cb ? (cb => $cb) : (),
1050             );
1051             }
1052              
1053             my @buttons;
1054              
1055             sub update_locks ($) {
1056             my $box = shift;
1057             my $lines;
1058             my $cv = gtm_run (
1059             [qw/lke show -all/],
1060             ">" => \$lines,
1061             "2>" => \$lines,
1062             cb => sub {
1063             output ("$lines\n");
1064             $box->remove ($_) for (@buttons);
1065             @buttons = ();
1066             while ($lines =~ m/^(.*)\s+Owned\s+by\s+PID=\s*(\d+)/mg) {
1067             my ($ref, $pid) = ($1, $2);
1068             my $b = new Gtk2::Button ("ref=$ref pid=$pid");
1069             $b->signal_connect (
1070             "clicked" => sub {
1071             remove_lock ($ref, $pid, sub { update_locks ($box) });
1072             }
1073             );
1074             push @buttons, $b;
1075             $box->pack_start ($b, 0, 0, 0);
1076             $b->show;
1077             }
1078             }
1079             );
1080             }
1081              
1082             sub gtm_locks() {
1083             @buttons = ();
1084             my $dialog = new Gtk2::Dialog ("Manage Locks", $main_window, 'modal', OK => 42);
1085             win_size ($dialog, "manage_locks", 200, 200);
1086             $dialog->set_default_response (42);
1087             my $button = new Gtk2::Button ("_Refresh");
1088             my $frame = new Gtk2::Frame ("Locks held");
1089             $frame->set_border_width (5);
1090             $frame->set_shadow_type ("etched-out");
1091             my $vbox = new Gtk2::VBox;
1092             $frame->add ($vbox);
1093             $button->signal_connect (clicked => sub { update_locks ($vbox); });
1094             $dialog->vbox->pack_start ($button, 0, 0, 0);
1095             $dialog->vbox->pack_start ($frame, 0, 0, 0);
1096             update_locks ($vbox);
1097             $dialog->show_all;
1098             $dialog->run;
1099             $dialog->destroy;
1100             }
1101              
1102             $SIG{__WARN__} = sub { output @_; };
1103              
1104             sub findfile {
1105             my @files = @_;
1106             file:
1107             for (@files) {
1108             for my $prefix (@INC, "/") {
1109             if (-f "$prefix/$_") {
1110             $_ = "$prefix/$_";
1111             next file;
1112             }
1113             }
1114             die "$_: file not found in \@INC\nINC=" . join ("\n", @INC);
1115             }
1116             wantarray ? @files : $files[0];
1117             }
1118              
1119             our $button;
1120              
1121             sub new () {
1122             my $menu = new Gtk2::SimpleMenu (menu_tree => $menu_tree);
1123             $main_scroll = new_scrolled_textarea();
1124             $main_window = new Gtk2::Window ('toplevel');
1125             $main_window->signal_connect (destroy => sub { main_quit Gtk2; });
1126             win_size ($main_window, "main_window", 960, 600);
1127             my $v = new Gtk2::VBox;
1128             $v->pack_start ($menu->{widget}, 0, 0, 0);
1129             $v->pack_start ($button, 0, 0, 0);
1130              
1131             $v->add ($main_scroll);
1132             $main_window->add ($v);
1133             $main_window->add_accel_group ($menu->{accel_group});
1134             load_prefs;
1135             set_busy (0);
1136             get_gtm_version();
1137             $main_window;
1138             }
1139              
1140             my $was_busy = 1;
1141             my $timer;
1142             my $counter = 0;
1143             my ($red, $green, $off);
1144             $button = new Gtk2::Button;
1145             $green = new_from_file Gtk2::Image (findfile ("GTM/images/ampel-green.png"));
1146             $red = new_from_file Gtk2::Image (findfile ("GTM/images/ampel-red.png"));
1147             $off = new_from_file Gtk2::Image (findfile ("GTM/images/ampel-off.png"));
1148              
1149             sub set_busy ($) {
1150             my $busy = shift;
1151             return if $was_busy == $busy;
1152             if ($busy == 0) {
1153             undef $timer;
1154             $button->set_image ($green);
1155             } else {
1156             $counter = 0;
1157             $timer = AnyEvent->timer (
1158             after => 0,
1159             interval => .25,
1160             cb => sub {
1161             $button->set_image (++$counter % 2 ? $red : $off);
1162             }
1163             );
1164             }
1165             $was_busy = $busy;
1166              
1167             }
1168              
1169             =head1 SEE ALSO
1170              
1171             L
1172              
1173             =head1 AUTHOR
1174              
1175             Stefan Traby
1176             http://oesiman.de/gt.m/
1177              
1178             =cut
1179              
1180             1;
1181