4
# $Id: Tree.pm,v 5.1 2004/09/08 21:07:25 eserte Exp $
7
# Copyright (C) 2001,2004 Slaven Rezic. All rights reserved.
8
# This package is free software; you can redistribute it and/or
9
# modify it under the same terms as Perl itself.
11
# Mail: slaven@rezic.de
12
# WWW: http://www.rezic.de/eserte/
15
package Tk::Pod::Tree;
19
Tk::Pod::Tree - list Pod file hierarchy
28
=head1 WIDGET-SPECIFIC OPTIONS
32
=item Name: B<-showcommand>
34
Specifies a callback for selecting a Pod module (Button-1 binding).
36
=item Name: B<-showcommand2>
38
Specifies a callback for selecting a Pod module in a different window
41
=item Name: B<-usecache>
43
True, if a cache of Pod modules should be created and used. The
50
The B<Tk::Pod::Tree> widget shows all available Perl Pod documentation
56
use vars qw($VERSION @ISA @POD %EXTRAPODDIR $FindPods $ExtraFindPods);
57
$VERSION = sprintf("%d.%02d", q$Revision: 5.1 $ =~ /(\d+)\.(\d+)/);
63
use Tk::Pod::FindPods;
67
Construct Tk::Widget 'PodTree';
71
use constant SEP => "/";
75
BEGIN { # Make a DEBUG constant very first thing...
77
} elsif(($ENV{'TKPODDEBUG'} || '') =~ m/^(\d+)/) { # untaint
79
*DEBUG = sub () { $debug };
85
######################################################################
87
struct '_PodEntry' => [
92
sub _PodEntry::create {
100
($uri =~ /^file:(.*)/)[0];
102
######################################################################
107
$EXTRAPODDIR{$_} = 1 for (@_);
111
my ($class,$mw) = @_;
112
$class->SUPER::ClassInit($mw);
113
$mw->bind($class, '<3>', ['PostPopupMenu', Ev('X'), Ev('Y')] )
114
if $Tk::VERSION > 800.014;
116
my $set_anchor_and_sel = sub {
120
$w->selectionSet($ent);
123
# Force callbacks to be treated as methods. This is done by putting
124
# the $widget reference at the beginning of the Tk::Callback array
125
my $inherited_cb = sub {
127
if (UNIVERSAL::isa($cb, "Tk::Callback")) {
128
my $new_cb = bless [$w, @$cb], 'Tk::Callback';
136
# Add functionality to some callbacks:
137
my $orig_home = $mw->bind($class, "<Home>");
138
$mw->bind($class, "<Home>" => sub {
140
$inherited_cb->($w, $orig_home);
141
$set_anchor_and_sel->($w, ($w->infoChildren)[0]);
143
my $orig_end = $mw->bind($class, "<End>");
144
$mw->bind($class, "<End>" => sub {
146
$inherited_cb->($w, $orig_end);
147
# get last opened entry
148
my $last = ($w->infoChildren)[-1];
149
while ($w->getmode($last) eq "close" && $w->infoChildren($last)) {
150
$last = ($w->infoChildren($last))[-1];
152
$set_anchor_and_sel->($w, $last);
154
my $orig_prior = $mw->bind($class, "<Prior>");
155
$mw->bind($class, "<Prior>" => sub {
157
$inherited_cb->($w, $orig_prior);
158
my $ent = $w->nearest(10); # XXX why 10?
159
return if !defined $ent;
160
$set_anchor_and_sel->($w, $ent);
162
my $orig_next = $mw->bind($class, "<Next>");
163
$mw->bind($class, "<Next>" => sub {
165
$inherited_cb->($w, $orig_next);
166
my $ent = $w->nearest($w->height - 10); # XXX why 10?
167
return if !defined $ent;
168
$set_anchor_and_sel->($w, $ent);
175
$args->{-separator} = SEP;
177
my $show_command = sub {
178
my($w, $cmd, $ent) = @_;
180
my $data = $w->info('data', $ent);
182
$w->Callback($cmd, $w, $data);
186
my $show_command_mouse = sub {
188
my $cmd = shift || '-showcommand';
191
my $ent = $w->GetNearest($Ev->y, 1);
192
return unless (defined $ent and length $ent);
194
my @info = $w->info('item',$Ev->x, $Ev->y);
195
if (defined $info[1] && $info[1] eq 'indicator') {
196
$w->Callback(-indicatorcmd => $ent, '<Arm>');
200
$show_command->($w, $cmd, $ent);
203
my $show_command_key = sub {
205
my $cmd = shift || '-showcommand';
207
my($ent) = $w->selectionGet;
208
return unless (defined $ent and length $ent);
210
if ($w->info('children', $ent)) {
214
$show_command->($w, $cmd, $ent);
217
$w->bind("<1>" => sub { $show_command_mouse->(shift) });
218
foreach (qw/space Return/) {
219
$w->bind("<$_>" => sub { $show_command_key->(shift) });
222
foreach (qw/2 Shift-1/) {
223
$w->bind("<$_>" => sub { $show_command_mouse->(shift, '-showcommand2') });
226
$w->SUPER::Populate($args);
229
$w->{Style}{'core'} = $w->ItemStyle('imagetext',
230
-foreground => '#006000',
231
-selectforeground => '#006000',
233
$w->{Style}{'site'} = $w->ItemStyle('imagetext',
234
-foreground => '#702000',
235
-selectforeground => '#702000',
237
$w->{Style}{'cpan'} = $w->ItemStyle('imagetext',
238
-foreground => '#000080',
239
-selectforeground => '#000080',
241
$w->{Style}{'folder'} = $w->ItemStyle('imagetext',
242
-foreground => '#606060',
243
-selectforeground => '#606060',
246
my $m = $w->Menu(-tearoff => $Tk::platform ne 'MSWin32');
247
eval { $w->menu($m) }; warn $@ if $@;
248
$m->command(-label => 'Reload', -command => sub {
249
$w->toplevel->Busy(-recurse => 1);
251
$w->Fill(-nocache => 1);
254
$w->toplevel->Unbusy(-recurse => 1);
257
$m->command(-label => 'Search...', -command => [$w, 'search_dialog']);
260
-showcommand => ['CALLBACK', undef, undef, undef],
261
-showcommand2 => ['CALLBACK', undef, undef, undef],
262
-usecache => ['PASSIVE', undef, undef, 1],
266
=head1 WIDGET METHODS
270
=item I<$tree>-E<gt>B<Fill>(?I<-nocache =E<gt> 1>?)
272
Find Pod modules and fill the tree widget. If I<-nocache> is
273
specified, then no cache will be used for loading.
275
A cache of Pod modules is written unless the B<-usecache>
276
configuration option of the widget is set to false.
285
my $usecache = ($w->cget('-usecache') && !$args{'-nocache'});
288
$FindPods = Tk::Pod::FindPods->new unless $FindPods;
289
my $pods = $FindPods->pod_find(-categorized => 1,
290
-usecache => $usecache,
293
if (keys %EXTRAPODDIR) {
294
$ExtraFindPods = Tk::Pod::FindPods->new unless $ExtraFindPods;
295
my $extra_pods = $ExtraFindPods->pod_find
297
-category => "local dirs",
298
-directories => [keys %EXTRAPODDIR],
301
while(my($k,$v) = each %$extra_pods) {
308
foreach (['perl', 'Perl language'],
309
['pragma', 'Pragmata'],
311
['script', 'Scripts'],
314
my($category, $title) = (ref $_ ? @$_ : ($_, $_));
315
next if $category_seen{$category};
317
$w->add($category, -text => $title);
319
my $hash = $pods->{$category};
320
foreach my $pod (sort keys %$hash) {
321
my $treepath = $category . SEP . $pod;
322
(my $title = $pod) =~ s|/|::|g;
323
$w->_add_parents($treepath);
325
my $loc = Tk::Pod::FindPods::module_location($hash->{$pod});
326
my $is = $w->{Style}{$loc};
327
my @entry_args = ($treepath,
329
-data => _PodEntry->create($hash->{$pod}),
330
($is ? (-style => $is) : ()),
332
if ($w->info('exists', $treepath)) {
333
$w->entryconfigure(@entry_args);
335
$w->add(@entry_args);
339
$category_seen{$category}++;
342
for(my $entry = ($w->info('children'))[0];
343
defined $entry && $entry ne "";
344
$entry = $w->info('next', $entry)) {
345
if ($w->info('children', $entry) ||
346
$w->entrycget($entry, -text) eq 'perlfunc') {
347
$w->folderentry($entry);
349
$w->entryconfigure($entry, -image => $w->Getimage("file"));
350
$w->hide('entry', $entry);
354
if ($w->cget('-usecache') && !$FindPods->has_cache) {
355
$FindPods->WriteCache;
363
$w->entryconfigure($entry, -image => $w->Getimage("folder"));
364
$w->setmode($entry, 'open');
365
if ($entry =~ m|/|) { # XXX SEP?
366
$w->hide('entry', $entry);
370
sub Filled { shift->{Filled} }
374
(my $parent = $entry) =~ s|/[^/]*$||; # XXX SEP?
375
return if $parent eq '';
376
do{warn "XXX Should not happen: $entry eq $parent";return} if $parent eq $entry;
377
return if $w->info('exists', $parent);
378
my @parent = split SEP, $parent;
379
my $title = join "::", @parent[1..$#parent];
380
$w->_add_parents($parent);
381
$w->add($parent, -text => $title,
382
($w->{Style}{'folder'} ? (-style => $w->{Style}{'folder'}) : ()));
387
(my $parent = $entry) =~ s|/[^/]+$||; # XXX SEP?
388
return if $parent eq '' || $parent eq $entry;
389
$w->_open_parents($parent);
393
=item I<$tree>-E<gt>B<SeePath>($path)
395
Move the anchor/selection and view to the given C<$path> and open
396
subtrees to make the C<$path> visible, if necessary.
402
my $fs_case_tolerant =
404
(File::Spec->can("case_tolerant") && File::Spec->case_tolerant)
406
if ($^O eq 'MSWin32') {
409
if ($fs_case_tolerant) {
412
DEBUG and warn "Call SeePath with $path\n";
413
return if !$w->Filled; # not yet filled
414
return if !$FindPods;
415
my $pods = $FindPods->pods;
418
my $see_treepath = sub {
419
my $treepath = shift;
421
$w->_open_parents($treepath);
422
$w->anchorSet($treepath);
424
$w->selectionSet($treepath);
428
foreach my $category (keys %$pods) {
429
foreach my $pod (keys %{ $pods->{$category} }) {
430
my $podpath = $pods->{$category}->{$pod};
431
$podpath = lc $podpath if $fs_case_tolerant;
432
if ($path eq $podpath) {
433
my $treepath = $category . SEP . $pod;
434
$see_treepath->($treepath);
439
DEBUG and warn "SeePath: cannot find $path in tree\n";
443
sub GetCurrentPodPath {
445
my $sel_entry = ($w->selectionGet)[0];
446
if (defined $sel_entry) {
447
my @c = split m{/}, $sel_entry;
449
my $pod = join "::", @c;
456
my $t = $w->Toplevel(-title => "Search");
458
$t->Label(-text => "Search module:")->pack(-side => "left");
463
require Tk::HistEntry;
464
Tk::HistEntry->VERSION(0.40);
465
$Entry = "HistEntry";
468
my $e = $t->$Entry(-textvariable => \$term)->pack(-side => "left");
469
if ($e->can('history') && $search_history) {
470
$e->history($search_history);
473
$e->bind("<Escape>" => sub { $t->destroy });
475
my $do_search = sub {
476
if ($e->can('historyAdd')) {
477
$e->historyAdd($term);
478
$search_history = [ $e->history ];
483
$e->bind("<Return>" => $do_search);
486
my $f = $t->Frame->pack(-fill => "x");
487
Tk::grid($f->Button(-text => "Search",
488
-command => $do_search,
490
$f->Button(-text => "Close",
491
-command => sub { $t->destroy },
500
my($entry) = ($w->info('selection'))[0];
501
if (!defined $entry) {
502
$entry = ($w->info('children'))[0];
503
return if (!defined $entry);
507
$entry = $w->info('next', $entry);
508
if (!defined $entry) {
514
$entry = ($w->info('children'))[0];
516
my $text = $w->entrycget($entry, '-text');
517
if ($text =~ /$rx/i) {
520
$p = $w->info('parent', $p);
528
$w->selectionSet($entry);
529
$w->anchorSet($entry);
537
my($w, $ent, $event) = @_;
538
my $podentry = $w->entrycget($ent, "-data");
539
my $file = $podentry && $podentry->file;
540
my $type = $podentry && $podentry->type;
542
# Dynamically create children for perlfunc entry
543
if (defined $type && $type =~ /^func_/ && !$w->info('children', $ent)) {
544
require Pod::Functions;
547
my($ent, $func) = @_;
548
my $podentry = _PodEntry->new;
549
$podentry->type("func");
550
$podentry->name($func);
551
(my $safe_name = $func) =~ s{[^a-zA-Z]}{_}g;
552
$ent = $ent . SEP . $safe_name;
553
$w->add($ent, -text => $func, -data => $podentry,
554
-style => $w->{Style}{'core'});
557
if ($type eq 'func_alphabetically') {
559
my @funcs = map { if (!defined $last_func || $last_func ne $_) {
568
map { @{ $Pod::Functions::Kinds{$_} } }
569
keys %Pod::Functions::Kinds;
570
for my $func (@funcs) {
571
$add_func->($ent, $func);
573
} else { # by category
574
for my $cat (sort keys %Pod::Functions::Kinds) {
575
(my $safe_name = $cat) =~ s{[^a-zA-Z]}{_}g;
576
my $ent = $ent . SEP . $safe_name;
577
$w->add($ent, -text => $cat, -style => $w->{Style}{'folder'});
578
my $funcs = $Pod::Functions::Kinds{$cat};
579
for my $func (@$funcs) {
580
$add_func->($ent, $func);
584
} elsif (defined $file && $file =~ /perlfunc\.pod$/ && !$w->info('children', $ent)) {
585
my($treepath, $podentry);
587
$treepath = $ent . SEP. "func_alphabetically";
588
$podentry = _PodEntry->new;
589
$podentry->type("func_alphabetically");
590
$w->add($treepath, -text => "Alphabetically", -data => $podentry,
591
-style => $w->{Style}{'folder'});
592
$w->folderentry($treepath);
594
$treepath = $ent . SEP. "func_by_category";
595
$podentry = _PodEntry->new;
596
$podentry->type("func_by_category");
597
$w->add($treepath, -text => "By category", -data => $podentry,
598
-style => $w->{Style}{'folder'});
599
$w->folderentry($treepath);
601
$w->SUPER::IndicatorCmd($ent, $event);
612
Tk::Tree(3), Tk::Pod(3), tkpod(1), Tk::Pod::FindPods(3).
616
Slaven Rezic <F<slaven@rezic.de>>
618
Copyright (c) 2001,2004 Slaven Rezic. All rights reserved. This program
619
is free software; you can redistribute it and/or modify it under the same
620
terms as Perl itself.