File Coverage

blib/lib/CPANPLUS/Shell/Tk.pm
Criterion Covered Total %
statement 11 16 68.7
branch 1 2 50.0
condition n/a
subroutine 5 5 100.0
pod n/a
total 17 23 73.9


line stmt bran cond sub pod time code
1             package CPANPLUS::Shell::Tk;
2              
3             #-------------------------------------------------------------------------------
4              
5             =head1 NAME
6              
7             CPANPLUS::Shell::Tk - Frontend for CPANPLUS using Tk
8              
9             =head1 SYNOPSIS
10              
11             To use CPANPLUS with the Tk GUI do:
12              
13             perl -MCPANPLUS -e 'shell(Tk)'
14              
15             =head1 WARNING
16              
17             This is very early beta!
18              
19             It may not do what you want it to do and it may break your CPANPLUS
20             configuration.
21              
22             Use it accordingly!
23              
24             =head1 GUI
25              
26             The GUI is divided into three parts:
27              
28             =over 2
29              
30             =item Infowindow on top
31              
32             The Infowindow shows the current Perl version.
33             It may show other interesting info in future.
34              
35             =item Modulelist on the left
36              
37             In the left window there are three tabs that show a search dialog with result,
38             a list of installed modules and a list of modules in need of an update.
39              
40             =item Workwindow
41              
42             The window on the right shows different things depending on what you are doing
43             at the moment.
44              
45             It shows basic information on the module when you select one in the list to the
46             left.
47              
48             It shows the POD for the module when you select this from the right-click
49             popup menu in the list.
50              
51             It shows the command history with editing facility when selected from the menu.
52              
53             And it show this POD when you select 'Help' from the Help menu.
54              
55             =back
56              
57             =head1 USAGE
58              
59             =head2 Searching
60              
61             You can search for a module or for an author.
62             Select which type of search you want to do in the dropdown listbox.
63              
64             Your search is always case sensitive but you can use perl regexen
65             as search value.
66              
67             =head2 Working with Modules
68              
69             When you click on the modules in the listbox on the left you get basic
70             information on the selected module.
71              
72             When you right click on the module you get a popup menu which lets you do
73             the following:
74              
75             =over 2
76              
77             =item Install
78              
79             Install the newest version of this module from CPAN.
80              
81             =item Uninstall
82              
83             Remove the module from your disk.
84              
85             =item Fetch
86              
87             Fetch the module from CPAN but do nothing else.
88              
89             =item Extract
90              
91             Fetch the module if necessary and extract it in your .cpanplus directory.
92              
93             =item Make
94              
95             Fetch the module if necessary, extract and build it in your .cpanplus
96             directory.
97              
98             =item Pod
99              
100             Display the POD of the module if it is installed.
101              
102             =back
103              
104             =head2 Changing the Configuration
105              
106             Via the Config menu you can change the configuration of CPANPLUS.
107              
108             =over 2
109              
110             =item CPANPLUS
111              
112             Change CPANPLUS config like default shell, debug level and so on.
113              
114             =item Package sources
115              
116             Edit the list of package sources.
117              
118             =back
119              
120             =head2 Perl
121              
122             You can view the entire Perl configuration using 'show full config'.
123              
124             You can restart CPANPLUS::Shell::Tk with another Perl version installed
125             on your disk.
126              
127             Currently this only works for *NIX like environments and even here it might
128             not pick the right perl binaries.
129              
130             =head2 History
131              
132             Every command you execute on a module will be logged in a history.
133              
134             You can edit and save that history to a file.
135              
136             That file can be used to perform automatic installation with
137             CPANPLUS::Shell::Batch (not yet released :-).
138              
139             =head1 AUTHOR
140              
141             Bernd Dulfer
142              
143             =head1 COPYRIGHT
144              
145             (C) Bernd Dulfer
146              
147             This library is free software; you can redistribute it and/or modify
148             it under the same terms as Perl itself.
149              
150             =head1 TODO
151              
152             In no particular order.
153              
154             =over 2
155              
156             =item More documentation!
157              
158             =item Cleanup the dialogs.
159              
160             =item Configure LWP.
161              
162             =item Configuration of this module (windowsize and position, ...).
163              
164             =item Restart with new perl platform independent.
165              
166             =item Move up/down entries in package sources
167              
168             =back
169              
170             =cut
171              
172             #-------------------------------------------------------------------------------
173              
174 1     1   24535 use strict;
  1         3  
  1         58  
175              
176             BEGIN {
177 1     1   6 use vars qw( @ISA $VERSION );
  1         4  
  1         85  
178 1     1   23 @ISA = qw( CPANPLUS::Shell::_Base);
179 1         89 $VERSION = '0.02';
180             }
181              
182             #---- perl 5.005_03 does not support warnings, so we mock things up here
183             BEGIN {
184             eval {
185 1         30 require warnings;
186 1 50   1   2 } or do {
187 0         0 *warnings::import = *warnings::unimport = sub {};
  0         0  
188 0         0 $INC{'warnings.pm'} = 'Faked!';
189             };
190             }
191              
192 1     1   448 use CPANPLUS::Backend;
  0            
  0            
193             use CPANPLUS::I18N;
194              
195             use Tk;
196             use Tk::Adjuster;
197             use Tk::Text;
198             use Tk::ROText;
199             use Tk::NoteBook;
200             use Tk::MListbox;
201             use Tk::BrowseEntry;
202             use Tk::FileSelect;
203             use Tk::Pod::Text;
204             use Tk::Splashscreen;
205             use Tk::Dialog;
206             use Config;
207             use File::Find;
208              
209              
210             #------------------------------------------------------------------------
211             # constructor
212             #
213             sub new {
214             my $proto = shift;
215             my $class = ref($proto) || $proto;
216              
217             ### Will call the _init constructor in Internals.pm ###
218             my $self = $class->SUPER::_init( brand => 'cpan' );
219              
220              
221             return $self;
222             }
223              
224              
225             #------------------------------------------------------------------------
226             # run the shell
227             #
228             sub shell {
229             my $self = shift;
230              
231             my $MW = $self->_setup_main();
232             $MW->withdraw;
233              
234             my $splash = $MW->Splashscreen(-milliseconds => 5000, -background => 'blue');
235             my $text = 'Initializing CPANPLUS backend . . .';
236              
237             #---- find splash image
238             my $splashfile = 'CPANPLUS/Shell/cpanplus.ppm';
239             my $realname;
240             foreach my $prefix (@INC) {
241             $realname = "$prefix/$splashfile";
242             if (-f $realname) {
243             last;
244             }
245             $realname = undef;
246             }
247              
248             #---- show splashscreen with image or with text
249             if ($realname) {
250             $splash->Label(-image => $MW->Photo(-file => $realname), -background => 'blue', -foreground => 'yellow')->pack(-side => 'top', -fill => 'both', -expand => 1, -padx => 10, -pady => 10);
251             } else {
252             $splash->Label(-text => "CPUI $VERSION",-font => '{Helvetica} -20 {bold}', -background => 'blue', -foreground => 'yellow')->pack(-side => 'top', -fill => 'both', -expand => 1, -padx => 10, -pady => 10);
253             }
254             $splash->Label(-textvariable => \$text, -background => 'blue', -foreground => 'yellow')->pack(-side => 'top', -fill => 'both', -expand => 1, -padx => 10, -pady => 10);
255             $splash->Splash;
256             $splash->update;
257              
258             #---- create Backend object
259             my $CP = new CPANPLUS::Backend;
260             $splash->update;
261             $self->{CP} = $CP;
262              
263             #---- setup menus
264             $self->_setup_menu();
265              
266             #---- gather installed modules
267             $text = 'Looking for installed modules . . .';
268             $splash->update;
269             my $rv = $CP->installed;
270             $self->{INSTALLED} = $rv->rv();
271              
272             #---- gather modules not up to date
273             $text = 'Looking for modules to be updated . . .';
274             $splash->update;
275             $rv = $CP->uptodate(modules => [keys %{$self->{INSTALLED}}]);
276             $self->{NOT_UPTODATE} = $rv->rv();
277             delete $self->{NOT_UPTODATE}->{$_} foreach map { $self->{NOT_UPTODATE}->{$_}->{uptodate} ? $_ : () } keys %{$self->{NOT_UPTODATE}};
278              
279             #---- setup main window
280             $text = 'Setting up main window . . .';
281             $splash->update;
282             $self->_setup_contents();
283              
284             #---- if available install inputhandler
285             if ($self->{CP}->can('set_input_handler')) {
286             $self->{CP}->set_input_handler( sub { $self->_get_input } );
287             }
288              
289             #---- take off
290             $splash->Destroy;
291             $self->{MW}->deiconify;
292              
293             MainLoop;
294             }
295              
296              
297             #------------------------------------------------------------------------
298             # use by cpui -H Tk
299             #
300             sub help {
301             print "
302             Tk user interface for CPANPLUS
303              
304             No help available at the moment.
305              
306             Start the gui and try.
307             \n";
308             }
309              
310             #------------------------------------------------------------------------
311             # setup main window
312             #
313             sub _setup_main {
314             my $self = shift;
315              
316             my $MW = MainWindow->new;
317             $MW->title('CPANPLUS');
318             $MW->minsize(100,100);
319             $MW->geometry('900x600+1+1');
320              
321             $self->{MW} = $MW;
322              
323             return $MW;
324             }
325              
326             #------------------------------------------------------------------------
327             # setup menu
328             #
329             sub _setup_menu {
330             my $self = shift;
331             my $MW = $self->{MW};
332             my $CP = $self->{CP};
333              
334             my $menubar = $MW->Frame(-relief => 'raised', -bd => 1);
335             $menubar->pack(-side => 'top', -fill => 'x');
336              
337             my $filemenu = $menubar->Menubutton(qw/-tearoff 0 -text File -pady -1 -underline 0 -menuitems/ =>
338             [
339             [Button => 'Exit', -command => [\&_exit_ui, $self]],
340             ])->pack(-side => 'left');
341              
342             my $configmenu = $menubar->Menubutton(qw/-tearoff 0 -text Config -pady -1 -underline 0 -menuitems/ =>
343             [
344             # [Button => 'cpui', -command => \&_config_cpui],
345             [Button => 'CPANPLUS', -command => [\&_config_cpanplus, $self]],
346             [Button => 'package sources', -command => [\&_config_sources, $self]],
347             ])->pack(-side => 'left');
348              
349             my $perlmenu = $menubar->Menubutton(qw/-tearoff 0 -text Perl -pady -1 -underline 0 -menuitems/ =>
350             [
351             [Button => 'show full config', -command => [\&_perl_config, $self]],
352             $^O =~ /win/i ? () : [Button => 'start with other version', -command => [\&_perl_restart, $self]], # not for win32 at the moment
353             ])->pack(-side => 'left');
354              
355             my $histmenu = $menubar->Menubutton(qw/-tearoff 0 -text History -pady -1 -underline 0 -menuitems/ =>
356             [
357             [Button => 'show', -command => [\&_show_history, $self]],
358             [Button => 'load', -command => [\&_load_history, $self]],
359             [Button => 'save', -command => [\&_save_history, $self]],
360             ])->pack(-side => 'left');
361              
362             my $helpmenu = $menubar->Menubutton(qw/-tearoff 0 -text Help -underline 0 -menuitems/ =>
363             [
364             [Button => 'Help', -command => [\&_help, $self]],
365             [Button => 'About', -command => [\&_about, $self]],
366             ])->pack(-side => 'right');
367             }
368              
369             #------------------------------------------------------------------------
370             # _setup_contents creates the contents of the main window by calling several methods
371             #
372             sub _setup_contents {
373             my $self = shift;
374             my $MW = $self->{MW};
375             my $CP = $self->{CP};
376              
377             $self->_setup_cpanplus_callbacks;
378              
379             my ($topframe, $leftframe, $rightframe) = $self->_setup_frames;
380              
381             $self->_setup_perl_info($topframe);
382             $self->_setup_left_frame($leftframe);
383             $self->_setup_right_frame($rightframe);
384             }
385              
386             #------------------------------------------------------------------------
387             # _setup_cpanplus_callbacks installs callbacks in the Backend to recieve
388             # print and error messages and put them into the right frame
389             #
390             sub _setup_cpanplus_callbacks {
391             my $self = shift;
392             my $MW = $self->{MW};
393             my $CP = $self->{CP};
394              
395             my $eo = $CP->error_object;
396             $eo->set('ECALLBACK' => sub { $self->{INFO}->insert('end', "ERROR: $_[0]\n"); $self->{INFO}->see('end'); $MW->update });
397             $eo->set('ICALLBACK' => sub { $self->{INFO}->insert('end', "$_[0]\n"); $self->{INFO}->see('end'); $MW->update });
398             }
399              
400             #------------------------------------------------------------------------
401             # setup top, left and right frame with the adjusters
402             # the frame setup will be done in three methods called in _setup_contents
403             #
404             sub _setup_frames {
405             my $self = shift;
406             my $MW = $self->{MW};
407             my $CP = $self->{CP};
408              
409             my $topframe = $MW->Frame;
410             my $hadjuster = $MW->Adjuster;
411             my $bottomframe = $MW->Frame;
412             $topframe->pack(-side => 'top',
413             -fill => 'x',
414             -expand => 1,
415             );
416             $hadjuster->packAfter($topframe, -side => 'top');
417             $bottomframe->pack(-side => 'top',
418             -fill => 'both',
419             -expand => 1,
420             );
421              
422             my $leftframe = $bottomframe->Frame(
423             -background => 'white',
424             );
425             my $vadjuster = $bottomframe->Adjuster;
426             my $rightframe = $bottomframe->Frame(
427             -background => 'white',
428             );
429             $leftframe->pack(-side => 'left',
430             -fill => 'both',
431             -expand => 1,
432             );
433             $vadjuster->packAfter($leftframe, -side => 'left');
434             $rightframe->pack(-side => 'left',
435             -fill => 'both',
436             -expand => 1,
437             );
438              
439             return $topframe, $leftframe, $rightframe;
440             }
441              
442             #------------------------------------------------------------------------
443             # setup the top frame, showing info about the perl version
444             #
445             sub _setup_perl_info {
446             my ($self, $topframe) = @_;
447              
448             my $perlinfo = $topframe->Scrolled('ROText',
449             -scrollbars => 'osow',
450             -height => 5,
451             -background => 'white',
452             -font => '{Helvetica} -12 {normal}',
453             );
454             $perlinfo->Subwidget("yscrollbar")->configure(-width => 6);
455             $perlinfo->Subwidget("xscrollbar")->configure(-width => 6);
456             $perlinfo->pack(-fill => 'both', -expand => 1);
457             $perlinfo->insert('end', "Perl\n\n");
458             $perlinfo->insert('end', "Version:\t\t" . $Config{version} . "\n");
459             $perlinfo->insert('end', "Architecture:\t" . $Config{archname} . "\n");
460             }
461              
462             #------------------------------------------------------------------------
463             # left frame
464             # search, installed and update tab
465             #
466             sub _setup_left_frame {
467             my $self = shift;
468             my $leftframe = shift;
469             my $MW = $self->{MW};
470             my $CP = $self->{CP};
471              
472             my $left = $leftframe->NoteBook(
473             -background => 'white',
474             );
475             my $search_tab = $left->add('search', -label => 'Search');
476             my $installed_tab = $left->add('installed', -label => 'Installed');
477             my $update_tab = $left->add('update', -label => 'Update');
478             $left->pack(-fill => 'both', -expand => 1);
479              
480             $self->_setup_update_tab($update_tab);
481             $self->_setup_installed_tab($installed_tab);
482             $self->_setup_search_tab($search_tab);
483              
484             }
485              
486             #------------------------------------------------------------------------
487             # setup listbox with modules not up to date
488             #
489             sub _setup_update_tab {
490             my $self = shift;
491             my $update_tab = shift;
492             my $MW = $self->{MW};
493             my $CP = $self->{CP};
494              
495             my $update = $update_tab->Scrolled('MListbox',
496             -scrollbars => 'osow',
497             -selectmode => 'extended',
498             -moveable => 0,
499             -background => 'white',
500             );
501             $update->Subwidget("yscrollbar")->configure(-width => 6);
502             $update->Subwidget("xscrollbar")->configure(-width => 6);
503             $update->columnInsert(0, -text => 'Module', -width => 35);
504             $update->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
505             $update->pack(-fill => 'both', -expand => 1);
506              
507             #---- on click, fill details into right frame
508             $update->bindRows('',
509             [ sub {
510             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
511             $self->{INFO}->pack(-fill => 'both', -expand => 1);
512             my @sel = $update->curselection;
513             my (@mods) = map {$update->columnGet(0)->get($_, $_)} @sel;
514             $self->{INFO}->delete('0.0', 'end');
515             return if @mods > 1;
516             my $rv = $CP->details(modules => [$mods[0]]);
517             foreach (sort keys %{$rv->{rv}->{$mods[0]}}) {
518             $self->{INFO}->insert('end', "\n$_:\n\t" . $rv->{rv}->{$mods[0]}->{$_});
519             }
520             }
521             ]
522             );
523              
524             #---- on right click show popup menu
525             my $button3_menu = $self->_create_button3_menu($update);
526             $update->bindRows('',
527             [ sub {
528             my @sel = $update->curselection;
529             @{$self->{MODS}} = map {$update->columnGet(0)->get($_, $_)} @sel;
530             $button3_menu->Popup(-popover => 'cursor', -popanchor => 'nw');
531             },
532             ]
533             );
534              
535             $update->insert(0, map { [$_, 1] } sort keys %{$self->{NOT_UPTODATE}});
536             }
537              
538             #------------------------------------------------------------------------
539             # setup listbox with installed modules
540             #
541             sub _setup_installed_tab {
542             my $self = shift;
543             my $installed_tab = shift;
544             my $MW = $self->{MW};
545             my $CP = $self->{CP};
546              
547             my $installed = $installed_tab->Scrolled('MListbox',
548             -scrollbars => 'osow',
549             -selectmode => 'extended',
550             -moveable => 0,
551             -background => 'white',
552             );
553             $installed->Subwidget("yscrollbar")->configure(-width => 6);
554             $installed->Subwidget("xscrollbar")->configure(-width => 6);
555             $installed->columnInsert(0, -text => 'Module', -width => 35);
556             $installed->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
557             $installed->pack(-fill => 'both', -expand => 1);
558              
559             #---- on click, fill details into right frame
560             $installed->bindRows('',
561             [ sub {
562             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
563             $self->{INFO}->pack(-fill => 'both', -expand => 1);
564             my @sel = $installed->curselection;
565             my (@mods) = map {$installed->columnGet(0)->get($_, $_)} @sel;
566             $self->{INFO}->delete('0.0', 'end');
567             return if @mods > 1;
568             my $rv = $CP->details(modules => [$mods[0]]);
569             foreach (sort keys %{$rv->{rv}->{$mods[0]}}) {
570             $self->{INFO}->insert('end', "\n$_:\n\t" . $rv->{rv}->{$mods[0]}->{$_});
571             }
572             }
573             ]
574             );
575              
576             #---- on right click show popup menu
577             my $button3_menu = $self->_create_button3_menu($installed);
578             $installed->bindRows('',
579             [ sub {
580             my @sel = $installed->curselection;
581             @{$self->{MODS}} = map {$installed->columnGet(0)->get($_, $_)} @sel;
582             $button3_menu->Popup(-popover => 'cursor', -popanchor => 'nw');
583             },
584             ]
585             );
586              
587             $installed->insert(0, map { [$_, 1] } sort keys %{$self->{INSTALLED}});
588             }
589              
590             #------------------------------------------------------------------------
591             # setup search tab
592             #
593             sub _setup_search_tab {
594             my $self = shift;
595             my $search_tab = shift;
596             my $MW = $self->{MW};
597             my $CP = $self->{CP};
598              
599             my $search;
600             my $searchtype = 'module';
601             my $searchtext;
602              
603             #---- frame for search form, searchtype (module/author), text and button
604             my $sf = $search_tab->Frame(
605             -background => 'white',
606             );
607             $sf->pack(-side => 'top', -fill => 'both', -expand => 0);
608             my $search_type = $sf->BrowseEntry(-variable => \$searchtype,
609             -state => 'readonly',
610             -background => 'white',
611             )->pack(-side => 'top', -anchor => 'w')->insert(0, (qw (module author)));
612             my $search_entry = $sf->Entry(-relief => 'sunken',
613             -textvariable => \$searchtext,
614             );
615             $search_entry->pack(-side => 'left');
616             my $search_button = $sf->Button(-text => 'Search',
617             -pady => -1,
618             -command => sub {
619             my $rv = $CP->search(type => $searchtype,
620             list => [$searchtext],
621             );
622             $search->delete(0,'end');
623             foreach (reverse sort keys %$rv) {
624             $search->insert(0, [$_]);
625             }
626             }
627             );
628             $search_button->pack(-side => 'right');
629             $search_entry->bind('', sub { $search_button->invoke });
630             $search_entry->focus;
631              
632             #---- listbox with searchresult
633             $search = $search_tab->Scrolled('MListbox',
634             -scrollbars => 'osow',
635             -selectmode => 'extended',
636             -moveable => 0,
637             -background => 'white',
638             );
639             $search->Subwidget("yscrollbar")->configure(-width => 6);
640             $search->Subwidget("xscrollbar")->configure(-width => 6);
641             $search->columnInsert(0, -text => 'Module', -width => 35);
642             $search->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
643            
644             $search->bindRows('',
645             [ sub {
646             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
647             $self->{INFO}->pack(-fill => 'both', -expand => 1);
648             my @sel = $search->curselection;
649             my (@mods) = map {$search->columnGet(0)->get($_, $_)} @sel;
650             $self->{INFO}->delete('0.0', 'end');
651             return if @mods > 1;
652             my $rv = $CP->details(modules => [$mods[0]]);
653             foreach (sort keys %{$rv->{rv}->{$mods[0]}}) {
654             $self->{INFO}->insert('end', "\n$_:\n\t" . $rv->{rv}->{$mods[0]}->{$_});
655             }
656             }
657             ]
658             );
659              
660             my $button3_menu = $self->_create_button3_menu($search);
661             $search->bindRows('',
662             [ sub {
663             my @sel = $search->curselection;
664             @{$self->{MODS}} = map {$search->columnGet(0)->get($_, $_)} @sel;
665             $button3_menu->Popup(-popover => 'cursor', -popanchor => 'nw');
666             },
667             ]
668             );
669             $search->pack(-side => 'bottom', -fill => 'both', -expand => 1);
670             }
671              
672             #------------------------------------------------------------------------
673             # right frame contains three text widgets, two are always hidden
674             # 1. history editor
675             # 2. module info
676             # 3. module pod
677             # the actual contents depends on the last action in popup or history menu
678             #
679             sub _setup_right_frame {
680             my $self = shift;
681             my $rightframe = shift;
682              
683             #---- setting history widget
684             my $hist = $rightframe->Scrolled('Text',
685             -scrollbars => 'osow',
686             -background => 'white',
687             -wrap => 'none',
688             -font => '{Helvetica} -12 {normal}',
689             );
690             $hist->Subwidget("yscrollbar")->configure(-width => 6);
691             $hist->Subwidget("xscrollbar")->configure(-width => 6);
692             $hist->pack(-fill => 'both', -expand => 1);
693             $hist->packForget;
694              
695             #---- read old history, ignore comments and blank lines, set commands to comments
696             $hist->insert('end', "# Command history\n\n");
697             open HISTORY, "<$ENV{HOME}/.cpui.hist" or warn $!;
698             while () {
699             next if /^#/;
700             next if /^\s*$/;
701             $hist->insert('end', "# $_");
702             }
703             close HISTORY;
704              
705             $self->{HISTORY} = $hist;
706              
707             #---- setting pod widget
708             my $pod = $rightframe->Scrolled('PodText',
709             -scrollbars => 'w',
710             -background => 'white',
711             -wrap => 'word',
712             -font => '{Helvetica} -12 {normal}',
713             -poddone => sub { $self->{MW}->title('CPANPLUS') } # PodText changes title, we change it back
714             );
715             $pod->Subwidget("yscrollbar")->configure(-width => 6);
716             $pod->Subwidget("xscrollbar")->configure(-width => 6);
717             $pod->Subwidget("scrolled")->configure(-scrollbars => '');
718             $pod->pack(-fill => 'both', -expand => 1);
719             $pod->packForget;
720              
721             $self->{POD} = $pod;
722              
723             #---- setting info widget
724             my $info = $rightframe->Scrolled('ROText',
725             -scrollbars => 'osow',
726             -background => 'white',
727             -wrap => 'none',
728             -font => '{Helvetica} -12 {normal}',
729             );
730             $info->Subwidget("yscrollbar")->configure(-width => 6);
731             $info->Subwidget("xscrollbar")->configure(-width => 6);
732             $info->pack(-fill => 'both', -expand => 1);
733              
734             $self->{INFO} = $info;
735             }
736              
737             #------------------------------------------------------------------------
738             # popup menu for button 3 in listbox
739             #
740             sub _create_button3_menu {
741             my ($self, $list) = @_;
742             my $MW = $self->{MW};
743             my $CP = $self->{CP};
744              
745              
746             my $menu = $list->Menu(-tearoff => 0,
747             -menuitems => [
748             [Button => 'Install',
749             -command => sub {
750             $MW->Busy;
751             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
752             $self->{INFO}->pack(-fill => 'both', -expand => 1);
753             $self->{INFO}->delete('0.0', 'end');
754             $CP->install(modules => $self->{MODS});
755             $self->{HISTORY}->insert('end', "install\t" . join(' ', @{$self->{MODS}}) . "\n");
756             $MW->Unbusy;
757             }],
758             [Button => 'Uninstall',
759             -command => sub {
760             $MW->Busy;
761             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
762             $self->{INFO}->pack(-fill => 'both', -expand => 1);
763             $self->{INFO}->delete('0.0', 'end');
764             $CP->uninstall(modules => $self->{MODS});
765             $self->{HISTORY}->insert('end', "uninstall\t" . join(' ', @{$self->{MODS}}) . "\n");
766             $MW->Unbusy;
767             }],
768             [Button => 'Fetch',
769             -command => sub {
770             $MW->Busy;
771             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
772             $self->{INFO}->pack(-fill => 'both', -expand => 1);
773             $self->{INFO}->delete('0.0', 'end');
774             $CP->fetch(modules => $self->{MODS});
775             $self->{HISTORY}->insert('end', "fetch\t" . join(' ', @{$self->{MODS}}) . "\n");
776             $MW->Unbusy;
777             }],
778             [Button => 'Extract',
779             -command => sub {
780             $MW->Busy;
781             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
782             $self->{INFO}->pack(-fill => 'both', -expand => 1);
783             $self->{INFO}->delete('0.0', 'end');
784             $CP->extract(modules => $self->{MODS});
785             $self->{HISTORY}->insert('end', "extract\t" . join(' ', @{$self->{MODS}}) . "\n");
786             $MW->Unbusy;
787             }],
788             [Button => 'Make',
789             -command => sub {
790             $MW->Busy;
791             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
792             $self->{INFO}->pack(-fill => 'both', -expand => 1);
793             $self->{INFO}->delete('0.0', 'end');
794             $CP->make(modules => $self->{MODS});
795             $self->{HISTORY}->insert('end', "make\t" . join(' ', @{$self->{MODS}}) . "\n");
796             $MW->Unbusy;
797             }],
798             [Button => 'Pod',
799             -command => sub {
800             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
801             $self->{POD}->configure(-file => $self->{MODS}->[0]);
802             print $self->{MODS}->[0], "\n";
803             $self->{POD}->pack(-fill => 'both', -expand => 1);
804             }],
805             ],
806             );
807             return $menu;
808             }
809              
810             #------------------------------------------------------------------------
811             # configure CPANPLUS
812             #
813             sub _config_cpanplus {
814             my $self = shift;
815             my $MW = $self->{MW};
816             my $CP = $self->{CP};
817              
818             my $conf = $CP->configure_object();
819             my @options = $conf->subtypes('conf');
820             my %conf;
821              
822             #---- attributes of config values, should be moved to CPANPLUS::Configure
823             my %conf_attrs = (cpantest => { type => 's', width => 1, comment => 'Send testreport to CPAN testers'},
824             debug => { type => 's', width => 1, comment => 'Output debug messages'},
825             flush => { type => 's', width => 1, comment => 'Flush cache automatically'},
826             force => { type => 's', width => 1, comment => 'Install even if tests fail'},
827             lib => { type => 'a', width => 20, comment => 'additional INC directories'},
828             makeflags => { type => 'h', width => 20, comment => 'Flags for the make command'},
829             makemakerflags => { type => 'h', width => 20, comment => 'Flags for makemaker'},
830             prereqs => { type => 's', width => 1, comment => 'Handle prerequesites'},
831             storable => { type => 's', width => 1, comment => 'Use Storable'},
832             verbose => { type => 's', width => 1, comment => 'Be verbose'},
833             md5 => { type => 's', width => 1, comment => 'Check md5 checksums'},
834             signature => { type => 's', width => 1, comment => 'Check gpg signature'},
835             shell => { type => 's', width => 25, comment => 'Default CPANPLUS shell'},
836             dist_type => { type => 's', width => 20, comment => 'Distribution type'},
837             skiptest => { type => 's', width => 1, comment => 'Skip tests'},
838             );
839              
840             #---- window
841             my $confdlg = $MW->Toplevel(-title => 'CPANPLUS Configuration', -background => 'white');
842             $confdlg->geometry('500x500+200+100');
843              
844             my $row = 0;
845             $confdlg->Label(-text => 'CPANPLUS Configuration', -background => 'white', -font => '{Helvetica} -20 {bold}')->pack(-side => 'top', -pady => 10);
846             $row++;
847             my $f = $confdlg->Frame(-background => 'white')->pack(-side => 'top');
848              
849             #---- one line for each option
850             foreach (sort @options) {
851             $conf_attrs{$_} ||= { type => 's', width => 20, comment => 'unknown/new option'};
852             my $conf_attr;
853             if ($conf->can('conf_attr')) {
854             $conf_attr = $conf->conf_attr('conf', $_) || {type => 's', width => 20, comment => 'unknown/new option'};
855             } else {
856             $conf_attr = $conf_attrs{$_};
857             }
858              
859             SWITCH: { # tried the Switch module here, but it choked on something
860             if ($conf_attr->{type} eq 'a') { $conf{$_} = join ';', @{$conf->get_conf($_)}; last SWITCH }
861             if ($conf_attr->{type} eq 's') { $conf{$_} = $conf->get_conf($_); last SWITCH }
862             if ($conf_attr->{type} eq 'h') { my %tempconf = %{$conf->get_conf($_)};
863             $conf{$_} = join ', ', map { "$_ => '$tempconf{$_}'"} keys %tempconf; }
864             }
865             $f->Label(-text => $_, -background => 'white')->grid(-column => 1, -row => $row, -sticky => 'w');
866             $f->Entry(-textvariable => \$conf{$_}, -width => $conf_attr->{width} || 20)->grid(-column => 2, -row => $row, -sticky => 'w');
867             $f->Label(-text => $conf_attr->{comment}, -background => 'white')->grid(-column => 3, -row => $row, -sticky => 'w');
868             $row++;
869             }
870              
871             #---- the normal buttons
872             my $ok = $f->Button(-text => 'Ok',
873             -pady => -1,
874             -default => 'active',
875             -command => sub {
876             $confdlg->destroy();
877             foreach (@options) {
878             SWITCH: {
879             if ($conf_attrs{$_}->{type} eq 'a') { $conf->set_conf($_ => [split /;/, $conf{$_}]); last SWITCH }
880             if ($conf_attrs{$_}->{type} eq 's') { $conf->set_conf($_ => $conf{$_}); last SWITCH }
881             if ($conf_attrs{$_}->{type} eq 'h') { my %tempconf = eval "($conf{$_})";
882             $conf->set_conf($_ => \%tempconf); }
883             }
884             }
885             })->grid(-column => 1, -row => ++$row, -pady => 20);
886             $f->Button(-text => 'Cancel',
887             -pady => -1,
888             -command => sub {
889             $confdlg->destroy();
890             })->grid(-column => 2, -row => $row, -pady => 20);
891             $f->Button(-text => 'Save',
892             -pady => -1,
893             -command => sub {
894             $confdlg->destroy();
895             foreach (@options) {
896             SWITCH: {
897             if ($conf_attrs{$_}->{type} eq 'a') { $conf->set_conf($_ => [split /;/, $conf{$_}]); last SWITCH }
898             if ($conf_attrs{$_}->{type} eq 's') { $conf->set_conf($_ => $conf{$_}); last SWITCH }
899             if ($conf_attrs{$_}->{type} eq 'h') { my %tempconf = eval "($conf{$_})";
900             $conf->set_conf($_ => \%tempconf); }
901             }
902             }
903             $conf->save;
904             })->grid(-column => 3, -row => $row, -pady => 20);
905             $confdlg->bind('', [sub {$ok->invoke}]);
906             $confdlg->bind('', [sub {$ok->invoke}]);
907             $confdlg->bind('', [sub {$confdlg->destroy()}]);
908              
909             #---- show dialog
910             $confdlg->waitWindow();
911             }
912              
913             #------------------------------------------------------------------------
914             # configure ftp and other sites
915             #
916             sub _config_sources {
917             my $self = shift;
918             my $MW = $self->{MW};
919             my $CP = $self->{CP};
920              
921             my $conf = $CP->configure_object();
922              
923             my ($scheme, $host, $path, $sel);
924              
925             my $confdlg = $MW->Toplevel(-title => 'CPANPLUS Configuration', -background => 'white');
926             $confdlg->geometry('600x400+200+100');
927              
928             my $row = 0;
929             $confdlg->Label(-text => 'CPANPLUS package sources', -background => 'white', -font => '{Helvetica} -20 {bold}')->pack(-side => 'top', -pady => 10);
930              
931             my $sources = $confdlg->Scrolled('MListbox',
932             -scrollbars => 'ow',
933             -selectmode => 'single',
934             -moveable => 0,
935             -background => 'white',
936             )->pack(-side => 'top', -fill => 'both', -expand => 1);
937             $sources->Subwidget("yscrollbar")->configure(-width => 6);
938             $sources->Subwidget("xscrollbar")->configure(-width => 6);
939             $sources->columnInsert('end', -text => 'Scheme', -width => 10);
940             $sources->columnInsert('end', -text => 'Host', -width => 30);
941             $sources->columnInsert('end', -text => 'Path', -width => 50);
942             $sources->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
943             $sources->columnGet(1)->Subwidget('heading')->configure(-pady => -1);
944             $sources->columnGet(2)->Subwidget('heading')->configure(-pady => -1);
945             $sources->bindRows('',
946             [ sub {
947             $sel = $sources->curselection;
948             my @a = $sources->get($sel, $sel);
949             ($scheme, $host, $path) = @{$a[0]};
950             }
951             ]
952             );
953              
954             foreach (@{$conf->_get_ftp('urilist')}) {
955             $sources->insert('end', [$_->{scheme}, $_->{host}, $_->{path}]);
956             }
957              
958             my $f = $confdlg->Frame(-background => 'white')->pack(-side => 'bottom', -fill => 'x', -expand => 1);
959              
960             $f->Entry(-textvariable => \$scheme, -width => 10)->grid(-column => 1, -row => 0, -pady => 10);
961             $f->Entry(-textvariable => \$host, -width => 30)->grid(-column => 2, -row => 0, -pady => 10);
962             $f->Entry(-textvariable => \$path, -width => 50)->grid(-column => 3, -row => 0, -pady => 10);
963              
964             $f->Button(-text => 'Enter new',
965             -pady => -1,
966             -command => sub {
967             if ($scheme && $host && $path) {
968             $sources->insert('end', [$scheme, $host, $path]);
969             }
970             })->grid(-column => 1, -row => 1, -padx => 10, -pady => 10);
971             $f->Button(-text => 'Change selected',
972             -pady => -1,
973             -command => sub {
974             if (defined $sel) {
975             $sources->delete($sel, $sel);
976             $sources->insert($sel, [$scheme, $host, $path]);
977             }
978             })->grid(-column => 2, -row => 1, -padx => 10, -pady => 10);
979             $f->Button(-text => 'Delete selected',
980             -pady => -1,
981             -command => sub {
982             if (defined $sel) {
983             $sources->delete($sel, $sel);
984             }
985             })->grid(-column => 3, -row => 1, -padx => 10, -pady => 10);
986              
987             my $ok = $f->Button(-text => 'Ok',
988             -pady => -1,
989             -default => 'active',
990             -command => sub {
991             $conf->_set_ftp(urilist => [ map {
992             { scheme => $_->[0],
993             host => $_->[1],
994             path => $_->[2]
995             }
996             } $sources->get(0, 'end')
997             ]
998             );
999             $confdlg->destroy();
1000             })->grid(-column => 1, -row => 2, -padx => 10, -pady => 10);
1001             $f->Button(-text => 'Cancel',
1002             -pady => -1,
1003             -command => sub {
1004             $confdlg->destroy();
1005             })->grid(-column => 2, -row => 2, -padx => 10, -pady => 10);
1006             $f->Button(-text => 'Save',
1007             -pady => -1,
1008             -command => sub {
1009             $conf->_set_ftp(urilist => [ map {
1010             { scheme => $_->[0],
1011             host => $_->[1],
1012             path => $_->[2]
1013             }
1014             } $sources->get(0, 'end')
1015             ]
1016             );
1017             $confdlg->destroy();
1018             $conf->save;
1019             })->grid(-column => 3, -row => 2, -padx => 10, -pady => 10);
1020             $confdlg->bind('', [sub {$ok->invoke}]);
1021             $confdlg->bind('', [sub {$ok->invoke}]);
1022             $confdlg->bind('', [sub {$confdlg->destroy()}]);
1023              
1024             $confdlg->waitWindow();
1025              
1026             }
1027              
1028             #------------------------------------------------------------------------
1029             # show complete perl configuration
1030             #
1031             sub _perl_config {
1032             my $self = shift;
1033             my $MW = $self->{MW};
1034             my $CP = $self->{CP};
1035              
1036             my $conf = $CP->configure_object();
1037              
1038             my ($scheme, $host, $path, $sel);
1039              
1040             my $confdlg = $MW->Toplevel(-title => 'Perl configuration', -background => 'white');
1041             $confdlg->geometry('600x400+200+100');
1042              
1043             my $row = 0;
1044             $confdlg->Label(-text => 'Perl configuration', -background => 'white', -font => '{Helvetica} -20 {bold}')->pack(-side => 'top', -pady => 10);
1045              
1046             my $options = $confdlg->Scrolled('MListbox',
1047             -scrollbars => 'ow',
1048             -selectmode => 'single',
1049             -moveable => 0,
1050             -background => 'white',
1051             )->pack(-side => 'top', -fill => 'both', -expand => 1);
1052             $options->Subwidget("yscrollbar")->configure(-width => 6);
1053             $options->Subwidget("xscrollbar")->configure(-width => 6);
1054             $options->columnInsert('end', -text => 'Key', -width => 20);
1055             $options->columnInsert('end', -text => 'Value', -width => 60);
1056             $options->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
1057             $options->columnGet(1)->Subwidget('heading')->configure(-pady => -1);
1058              
1059             foreach (sort keys %Config) {
1060             $options->insert('end', [$_, $Config{$_}]);
1061             }
1062              
1063             my $ok = $confdlg->Button(-text => 'Ok',
1064             -pady => -1,
1065             -default => 'active',
1066             -command => sub {
1067             $confdlg->destroy();
1068             })->pack(-side => 'bottom');
1069             $confdlg->bind('', [sub {$ok->invoke}]);
1070             $confdlg->bind('', [sub {$ok->invoke}]);
1071             $confdlg->bind('', [sub {$confdlg->destroy()}]);
1072              
1073             $confdlg->waitWindow();
1074              
1075             }
1076              
1077             #------------------------------------------------------------------------
1078             # restart shell with another perl version
1079             #
1080             sub _perl_restart {
1081             my $self = shift;
1082             my $MW = $self->{MW};
1083             my $CP = $self->{CP};
1084              
1085             my $restartdlg = $MW->Toplevel(-title => 'Restart', -background => 'white');
1086             $restartdlg->geometry('200x300+200+100');
1087              
1088             my $row = 0;
1089             $restartdlg->Label(-text => "Restart with other\nPerl version", -background => 'white', -font => '{Helvetica} -20 {bold}')->pack(-side => 'top', -pady => 10);
1090              
1091             my $versions = $restartdlg->Scrolled('MListbox',
1092             -scrollbars => 'ow',
1093             -selectmode => 'single',
1094             -moveable => 0,
1095             -height => 5,
1096             -width => 30,
1097             -background => 'white',
1098             )->pack(-side => 'top', -fill => 'both', -expand => 1);
1099             $versions->Subwidget("yscrollbar")->configure(-width => 6);
1100             $versions->Subwidget("xscrollbar")->configure(-width => 6);
1101             $versions->columnInsert('end', -text => 'Perl', -width => 20);
1102             $versions->columnGet(0)->Subwidget('heading')->configure(-pady => -1);
1103              
1104             find( sub {
1105             return if !/^perl\d/;
1106             $versions->insert('end', [$File::Find::name]);
1107             }, '/usr/bin', '/usr/local/bin'); # hardcoded at the moment, should move to some config
1108              
1109             my $ok = $restartdlg->Button( -text => 'Ok',
1110             -pady => -1,
1111             -default => 'active',
1112             -command => sub {
1113             my $sel = $versions->curselection;
1114             if (defined $sel) {
1115             my ($cmd) = $versions->get($sel);
1116             $, = ", ";
1117             print $cmd->[0], $0, @ARGV, "\n";
1118             exec $cmd->[0], $0, @ARGV;
1119             }
1120             $restartdlg->destroy();
1121             })->pack(-side => 'left', -pady => 10, -padx => 10);
1122             $restartdlg->Button(-text => 'Cancel',
1123             -pady => -1,
1124             -command => sub {
1125             $restartdlg->destroy();
1126             })->pack(-side => 'right', -pady => 10, -padx => 10);
1127             $restartdlg->bind('', [sub {$ok->invoke}]);
1128             $restartdlg->bind('', [sub {$ok->invoke}]);
1129             $restartdlg->bind('', [sub {$restartdlg->destroy()}]);
1130              
1131             $restartdlg->waitWindow();
1132              
1133             }
1134              
1135              
1136             #------------------------------------------------------------------------
1137             # bring the history editor to front
1138             #
1139             sub _show_history {
1140             my $self = shift;
1141              
1142             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
1143             $self->{HISTORY}->pack(-fill => 'both', -expand => 1);
1144             }
1145              
1146             #------------------------------------------------------------------------
1147             # load a history file
1148             #
1149             sub _load_history {
1150             my $self = shift;
1151              
1152             my $fs = $self->{MW}->FileSelect(-directory => $ENV{HOME});
1153             my $file = $fs->Show;
1154              
1155             if (open HISTORY, "<$file") {
1156             $self->{HISTORY}->insert('end', );
1157             close HISTORY;
1158             } else {
1159             $self->{MW}->messageBox(-title => 'cpui - error', -message => $!, -type => 'OK');
1160             }
1161             }
1162              
1163             #------------------------------------------------------------------------
1164             # save the history to some file
1165             #
1166             sub _save_history {
1167             my $self = shift;
1168              
1169             my $fs = $self->{MW}->FileSelect(-directory => $ENV{HOME});
1170             my $file = $fs->Show;
1171              
1172             if (open HISTORY, ">$file") {
1173             print HISTORY $self->{HISTORY}->get('0.0', 'end');
1174             close HISTORY;
1175             } else {
1176             $self->{MW}->messageBox(-title => 'cpui - error', -message => $!, -type => 'OK');
1177             }
1178             }
1179              
1180             #------------------------------------------------------------------------
1181             # exit program, sub exists for some cleanup
1182             #
1183             sub _exit_ui {
1184             exit;
1185             }
1186              
1187             #------------------------------------------------------------------------
1188             # get input from user when installation process asks (not used by now)
1189             #
1190             sub _get_input {
1191             my $self = shift;
1192             my $MW = $self->{MW};
1193              
1194             my $inputdlg = $MW->Toplevel(-title => 'User input', -background => 'white');
1195             $inputdlg->geometry('500x200+200+100');
1196              
1197             $inputdlg->Label(-text => 'User input required', -background => 'white', -font => '{Helvetica} -20 {bold}')->pack(-side => 'top', -pady => 10);
1198              
1199             my $input;
1200             $inputdlg->Entry(-textvariable => \$input, -width => 20)->pack(-side => 'left');
1201              
1202             my $ok = $inputdlg->Button(-text => 'Ok',
1203             -pady => -1,
1204             -default => 'active',
1205             -command => sub {
1206             $inputdlg->destroy();
1207             return $input;
1208             }
1209             )->pack(-side => 'right');
1210             $inputdlg->bind('', [sub {$ok->invoke}]);
1211             $inputdlg->bind('', [sub {$ok->invoke}]);
1212              
1213             $inputdlg->waitWindow();
1214             }
1215              
1216              
1217             #------------------------------------------------------------------------
1218             # show about dialog
1219             #
1220             sub _about {
1221             my $self = shift;
1222             my $dialog = $self->{MW}->MainWindow->Dialog(
1223             -title => 'About CPANPLUS::Shell::Tk',
1224             -text => "Tk User Interface for CPANPLUS\n\nVersion: $VERSION\n\n(C) Bernd Dulfer\n\n",
1225             -default_button => 'Ok',
1226             -buttons => ['Ok']
1227             );
1228             $dialog->configure(
1229             -wraplength => '10i',
1230             );
1231             $dialog->Show();
1232             $dialog->destroy();
1233             $dialog = undef;
1234             }
1235              
1236              
1237             #------------------------------------------------------------------------
1238             # show pod as online help
1239             #
1240             sub _help {
1241             my $self = shift;
1242              
1243             $self->{$_}->packForget foreach qw(HISTORY POD INFO);
1244             $self->{POD}->configure(-file => 'CPANPLUS::Shell::Tk');
1245             $self->{POD}->pack(-fill => 'both', -expand => 1);
1246             }
1247              
1248              
1249             #------------------------------------------------------------------------
1250              
1251             1;
1252