File Coverage

blib/lib/Devel/tcltkdb.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             $tcltkdb::VERSION = '2.1';
2              
3 1     1   1007 use strict;
  1         1  
  1         30  
4 1     1   872 use Data::Dumper;
  1         9822  
  1         57  
5 1     1   1360 use Tcl::Tk;
  0            
  0            
6              
7             #
8             # This package is the main_window object for the debugger. We start
9             # with the Devel:: prefix because we want to install it with
10             # the DB:: package that is required to be in a Devel/ subdir of a
11             # directory in the @INC set.
12             #
13             package Devel::tcltkdb;
14              
15              
16             =head1 NAME
17              
18             Devel::tcltkdb - Perl debugger using a Tcl/Tk GUI
19              
20             =head1 DESCRIPTION
21              
22             tcltkdb is a debugger for perl that uses perl+Tcl/Tk for a user interface.
23             Features include:
24              
25             Hot Variable Inspection (currently disabled)
26             Breakpoint Control Panel
27             Expression List
28             Subroutine Tree
29              
30             =head1 SYNOPSIS
31              
32             To debug a script using tcltkdb invoke perl like this:
33              
34             perl -d:tcltkdb myscript.pl
35              
36             =head1 Usage
37              
38             perl -d:tcltkdb myscript.pl
39              
40             =head1 Code Pane
41              
42             =over 4
43              
44             =item Line Numbers
45              
46             Line numbers are presented on the left side of the window. Lines that
47             have lines through them are not breakable. Lines that are plain text
48             are breakable. Clicking on these line numbers will insert a
49             breakpoint on that line and change the line number color to
50             $ENV{'PTKDB_BRKPT_COLOR'} (Defaults to Red). Clicking on the number
51             again will remove the breakpoint. If you disable the breakpoint with
52             the controls on the BrkPt notebook page the color will change to
53             $ENV{'PTKDB_DISABLEDBRKPT_COLOR'} (Defaults to Green).
54              
55             =item Cursor Motion
56              
57             If you place the cursor over a variable (i.e. $myVar, @myVar, or
58             %myVar) and pause for a second the debugger will evaluate the current
59             value of the variable and pop a balloon up with the evaluated
60             result.
61              
62             Data::Dumper will be used to format the result. If there is an active
63             selection, the text of that selection will be evaluated.
64              
65             =back
66              
67             =head1 Notebook Pane
68              
69             =over 2
70              
71             =item Exprs
72              
73             This is a list of expressions that are evaluated each time the
74             debugger stops. The results of the expresssion are presented
75             heirarchically for expression that result in hashes or lists. Double
76             clicking on such an expression will cause it to collapse; double
77             clicking again will cause the expression to expand. Expressions are
78             entered through B entry, or by Alt-E when text is
79             selected in the code pane.
80              
81             The B entry, will take an expression, evaluate it, and
82             replace the entries contents with the result. The result is also
83             transfered to the 'clipboard' for pasting.
84              
85             =item Subs
86              
87             Displays a list of all the packages invoked with the script
88             heirarchially. At the bottom of the heirarchy are the subroutines
89             within the packages. Double click on a package to expand
90             it. Subroutines are listed by their full package names.
91              
92             =item BrkPts
93              
94             Presents a list of the breakpoints current in use. The pushbutton
95             allows a breakpoint to be 'disabled' without removing it. Expressions
96             can be applied to the breakpoint. If the expression evaluates to be
97             'true'(results in a defined value that is not 0) the debugger will
98             stop the script. Pressing the 'Goto' button will set the text pane
99             to that file and line where the breakpoint is set. Pressing the
100             'Delete' button will delete the breakpoint.
101              
102             =back
103              
104             =head1 Menus
105              
106             =head2 File Menu
107              
108             =over
109              
110             =item About...
111              
112             Presents a dialog box telling you about the version of ptkdb. It
113             recovers your OS name, version of perl, version of Tcl/Tk, and some other
114             information
115              
116             =item Open
117              
118             Presents a list of files that are part of the invoked perl
119             script. Selecting a file from this list will present this file in the
120             text window.
121              
122             =item Save Config...
123              
124             Prompts for a filename to save the
125             configuration to. Saves the breakpoints, expressions, eval text and
126             window geometry. If the name given as the default is used and the
127             script is reinvoked, this configuration will be reloaded automatically.
128              
129             B You may find this preferable to using
130              
131             =item Restore Config...
132              
133             Prompts for a filename to restore a configuration saved with
134             the "Save Config..." menu item.
135              
136             =item Goto Line...
137              
138             Prompts for a line number. Pressing the "Okay" button sends the window
139             to the line number entered.
140              
141             =item Find Text...
142              
143             Prompts for text to search for. Options include forward search,
144             backwards search, and regular expression searching.
145              
146             =item Quit
147              
148             Causes the debugger and the target script to exit.
149              
150             =back
151              
152             =head2 Control Menu
153              
154             =over
155              
156             =item Run
157              
158             The debugger allows the script to run to the next breakpoint or until
159             the script exits.
160              
161             =item Run To Here
162              
163             Runs the debugger until it comes to wherever the insertion cursor
164             in text window is placed.
165              
166             =item Set Breakpoint
167              
168             Sets a breakpoint on the line at the insertion cursor.
169              
170             =item Clear Breakpoint
171              
172             Remove a breakpoint on the at the insertion cursor.
173              
174             =item Clear All Breakpoints
175              
176             Removes all current breakpoints
177              
178             =item Step Over
179              
180             Causes the debugger to step over the next line. If the line is a
181             subroutine call it steps over the call, stopping when the subroutine
182             returns.
183              
184             =item Step In
185              
186             Causes the debugger to step into the next line. If the line is a
187             subroutine call it steps into the subroutine, stopping at the first
188             executable line within the subroutine.
189              
190             =item Return
191              
192             Runs the script until it returns from the currently executing subroutine.
193              
194             =item Restart
195              
196             Saves the breakpoints and expressions in a temporary file and restarts
197             the script from the beginning. CAUTION: This feature will not work
198             properly with debugging of CGI Scripts.
199              
200             =item Stop On Warning
201              
202             When C<-w> is enabled the debugger will stop when warnings such as, "Use
203             of uninitialized value at undef_warn.pl line N" are encountered. The debugger
204             will stop on the NEXT line of execution since the error can't be detected
205             until the current line has executed.
206              
207             This feature can be turned on at startup by adding:
208              
209             $DB::tcltkdb::stop_on_warning = 1 ;
210              
211             to a .ptkdbrc file
212              
213             =back
214              
215             =head2 Data Menu
216              
217             =over
218              
219             =item Enter Expression
220              
221             When an expression is entered in the "Enter Expression:" text box,
222             selecting this item will enter the expression into the expression
223             list. Each time the debugger stops this expression will be evaluated
224             and its result updated in the list window.
225              
226             =item Delete Expression
227              
228             Deletes the highlighted expression in the expression window.
229              
230             =item Delete All Expressions
231              
232             Delete all expressions in the expression window.
233              
234             =item Expression Eval Window
235              
236             Pops up a two pane window. Expressions of virtually unlimitted length
237             can be entered in the top pane. Pressing the 'Eval' button will cause
238             the expression to be evaluated and its placed in the lower pane.
239             Data::Dumper is used to format the resulting
240             text. Undo is enabled for the text in the upper pane.
241              
242             HINT: You can enter multiple expressions by separating them with commas.
243              
244             =item Use Data::Dumper for Eval Window
245              
246             Enables or disables the use of Data::Dumper for formatting the results
247             of expressions in the Eval window.
248              
249             =back
250              
251             =head2 Stack Menu
252              
253             Maintains a list of the current subroutine stack each time the
254             debugger stops. Selecting an item from this menu will set the text in
255             the code window to that particular subourtine entry point.
256              
257             =head2 Bookmarks Menu
258              
259             Maintains a list of bookmarks. The booksmarks are saved in ~/.ptkdb_bookmarks
260              
261             =over
262              
263             =item Add Bookmark
264              
265             Adds a bookmark to the bookmark list.
266              
267             =back
268              
269             =head1 Options
270              
271             Here is a list of the current active XResources options. Several of
272             these can be overridden with environmental variables. Resources can be
273             added to .Xresources or .Xdefaults depending on your X configuration.
274             To enable these resources you must either restart your X server or use
275             the xrdb -override resFile command. xfontsel can be used to select
276             fonts.
277              
278             /*
279             * Perl Tk Debugger XResources.
280             * Note... These resources are subject to change.
281             *
282             * Use 'xfontsel' to select different fonts.
283             *
284             * Append these resource to ~/.Xdefaults | ~/.Xresources
285             * and use xrdb -override ~/.Xdefaults | ~/.Xresources
286             * to activate them.
287             */
288              
289             ptkdb.frame*font: fixed /* Menu Bar */
290             ptkdb.frame2.frame1.rotext.font: fixed /* Code Pane */
291              
292             ptkdb.toplevel.frame.textundo.font: fixed /* Eval Expression Entry Window */
293             ptkdb.toplevel.frame1.text.font: fixed /* Eval Expression Results Window */
294             ptkdb.toplevel.button.font: fixed /* "Eval..." Button */
295             ptkdb.toplevel.button1.font: fixed /* "Clear Eval" Button */
296             ptkdb.toplevel.button2.font: fixed /* "Clear Results" Button */
297             ptkdb.toplevel.button3.font: fixed /* "Clear Dismiss" Button */
298              
299             /*
300             * Background color for where the debugger has stopped
301             */
302             ptkdb*stopcolor: blue
303              
304             /*
305             * Background color for set breakpoints
306             */
307             ptkdb*breaktagcolor*background: yellow
308             ptkdb*disabledbreaktagcolor*background: white
309             /*
310             * Font for where the debugger has stopped
311             */
312             ptkdb*stopfont: -*-fixed-bold-*-*-*-*-*-*-*-*-*-*-*
313              
314             /*
315             * Background color for the search tag
316             */
317             ptkdb*searchtagcolor: green
318              
319             =head1 Environmental Variables
320              
321             =over 4
322              
323             =item PTKDB_BRKPT_COLOR
324              
325             Sets the background color of a set breakpoint
326              
327             =item PTKDB_DISABLEDBRKPT_COLOR
328              
329             Sets the background color of a disabled breakpoint
330              
331             =item PTKDB_CODE_FONT
332              
333             Sets the font of the Text in the code pane.
334              
335             =item PTKDB_EXPRESSION_FONT
336              
337             Sets the font used in the expression notebook page.
338              
339             =item PTKDB_EVAL_FONT
340              
341             Sets the font used in the Expression Eval Window
342              
343             =item PTKDB_DISPLAY
344              
345             Sets the X display that the ptkdb window will appear on when invoked.
346             Useful for debugging CGI scripts on remote systems.
347              
348             =item PTKDB_BOOKMARKS_PATH
349              
350             Sets the path of the bookmarks file. Default is $ENV{'HOME'}/.ptkdb_bookmarks
351              
352             =item PTKDB_STOP_TAG_COLOR
353              
354             Sets the color that highlights the line where the debugger is stopped
355              
356             =back
357              
358             =head1 FILES
359              
360             =head2 .ptkdbrc
361              
362             If this file is present in ~/ or in the directory where perl is
363             invoked the file will be read and executed as a perl script before the
364             debugger makes its initial stop at startup. There are several 'api'
365             calls that can be used with such scripts. There is an internal
366             variable $DB::no_stop_at_start that may be set to non-zero to prevent
367             the debugger from stopping at the first line of the script. This is
368             useful for debugging CGI scripts.
369              
370             =over 4
371              
372             =item brkpt($fname, @lines)
373              
374             Sets breakspoints on the list of lines in $fname. A warning message
375             is generated if a line is not breakable.
376              
377             =item condbrkpt($fname, @($line, $expr) )
378              
379             Sets conditional breakpoints in $fname on pairs of $line and $expr. A
380             warning message is generated if a line is not breakable. NOTE: the
381             validity of the expression will not be determined until execution of
382             that particular line.
383              
384             =item brkonsub(@names)
385              
386             Sets a breakpoint on each subroutine name listed. A warning message is
387             generated if a subroutine does not exist. NOTE: for a script with no
388             other packages the default package is "main::" and the subroutines
389             would be "main::mySubs".
390              
391             =item brkonsub_regex(@regExprs)
392              
393             Uses the list of @regExprs as a list of regular expressions to set breakpoints. Sets breakpoints
394             on every subroutine that matches any of the listed regular expressions.
395              
396             =back
397              
398             =head1 NOTES
399              
400             =head2 Debugging Other perlTk Applications
401              
402             ptkdb can be used to debug other perlTk applications if some cautions
403             are observed. Basically, do not click the mouse in the application's
404             window(s) when you've entered the debugger and do not click in the
405             debugger's window(s) while the application is running. Doing either
406             one is not necessarily fatal, but it can confuse things that are going
407             on and produce unexpected results.
408              
409             Be aware that most perlTk applications have a central event loop.
410             User actions, such as mouse clicks, key presses, window exposures, etc
411             will generate 'events' that the script will process. When a perlTk
412             application is running, its 'MainLoop' call will accept these events
413             and then dispatch them to appropriate callbacks associated with the
414             appropriate widgets.
415              
416             Ptkdb has its own event loop that runs whenever you've stopped at a
417             breakpoint and entered the debugger. However, it can accept events
418             that are generated by other perlTk windows and dispatch their
419             callbacks. The problem here is that the application is supposed to be
420             'stopped', and logically the application should not be able to process
421             events.
422              
423             =head2 Debugging CGI Scripts
424              
425             One advantage of ptkdb over the builtin debugger(-d) is that it can be
426             used to debug CGI perl scripts as they run on a web server. Be sure
427             that that your web server's perl instalation includes Tcl::Tk.
428              
429             Change your
430              
431             #! /usr/local/bin/perl
432              
433             to
434              
435             #! /usr/local/bin/perl -d:tcltkdb
436              
437             TIP: You can debug scripts remotely if you're using a unix based
438             Xserver and where you are authoring the script has an Xserver. The
439             Xserver can be another unix workstation, a Macintosh or Win32 platform
440             with an appropriate XWindows package. In your script insert the
441             following BEGIN subroutine:
442              
443             sub BEGIN {
444             $ENV{'DISPLAY'} = "myHostname:0.0" ;
445             }
446              
447             Be sure that your web server has permission to open windows on your
448             Xserver (see the xhost manpage).
449              
450             Access your web page with your browswer and 'submit' the script as
451             normal. The ptkdb window should appear on myHostname's monitor. At
452             this point you can start debugging your script. Be aware that your
453             browser may timeout waiting for the script to run.
454              
455             To expedite debugging you may want to setup your breakpoints in
456             advance with a .ptkdbrc file and use the $DB::no_stop_at_start
457             variable. NOTE: for debugging web scripts you may have to have the
458             .ptkdbrc file installed in the server account's home directory (~www)
459             or whatever username your webserver is running under. Also try
460             installing a .ptkdbrc file in the same directory as the target script.
461              
462             =head1 AUTHORS
463              
464             Andrew E. Page
465             Vadim Konovalov
466              
467             =cut
468              
469             use vars qw(@dbline);
470              
471             sub BEGIN {
472              
473             $DB::on = 0 ;
474              
475             $DB::subroutine_depth = 0 ; # our subroutine depth counter
476             $DB::step_over_depth = -1 ;
477              
478             # Fonts used in the displays
479              
480             @Devel::tcltkdb::button_font = $ENV{'PTKDB_BUTTON_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ; # font for buttons
481             @Devel::tcltkdb::code_text_font = $ENV{'PTKDB_CODE_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ;
482              
483             @Devel::tcltkdb::expression_text_font = $ENV{'PTKDB_EXPRESSION_FONT'} ? ( "-font" => $ENV{'PTKDB_EXPRESSION_FONT'} ) : () ;
484             @Devel::tcltkdb::eval_text_font = $ENV{'PTKDB_EVAL_FONT'} ? ( -font => $ENV{'PTKDB_EVAL_FONT'} ) : () ; # text for the expression eval window
485              
486             $Devel::tcltkdb::linenumber_length = 5;
487              
488             #
489             # DB Options (things not directly involving the window)
490             #
491              
492             # Flag to disable us from intercepting $SIG{'INT'}
493              
494             $DB::sigint_disable = defined $ENV{'PTKDB_SIGINT_DISABLE'} && $ENV{'PTKDB_SIGINT_DISABLE'} ;
495             #
496             # Possibly for debugging perl CGI Web scripts on
497             # remote machines.
498             #
499             $ENV{'DISPLAY'} = $ENV{'PTKDB_DISPLAY'} if exists $ENV{'PTKDB_DISPLAY'} ;
500              
501             } # end of BEGIN
502              
503             ##
504             ## subroutine provided to the user for initializing
505             ## files in .ptkdbrc
506             ##
507             sub brkpt {
508             my ($fName, @idx) = @_ ;
509             my($offset) ;
510             local(*dbline) = $main::{'_<' . $fName} ;
511              
512             $offset = $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ? 1 : 0 ;
513              
514             for( @idx ) {
515             if( !&DB::checkdbline($fName, $_ + $offset) ) {
516             my ($package, $filename, $line) = caller ;
517             print "$filename:$line: $fName line $_ is not breakable\n" ;
518             next ;
519             }
520             $DB::window->insertBreakpoint($fName, $_, 1) ; # insert a simple breakpoint
521             }
522             } # end of brkpt
523              
524             #
525             # Set conditional breakpoint(s)
526             #
527             sub condbrkpt {
528             my ($fname) = shift ;
529             local(*dbline) = $main::{'_<' . $fname} ;
530              
531             my $offset = $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ? 1 : 0 ;
532              
533             while( @_ ) { # arg loop
534             my($index, $expr) = splice @_, 0, 2 ; # take args 2 at a time
535              
536             if( !&DB::checkdbline($fname, $index + $offset) ) {
537             my ($package, $filename, $line) = caller ;
538             print "$filename:$line: $fname line $index is not breakable\n" ;
539             next ;
540             }
541             $DB::window->insertBreakpoint($fname, $index, 1, $expr) ; # insert a simple breakpoint
542             } # end of arg loop
543             }
544              
545             sub brkonsub {
546             my(@names) = @_ ;
547              
548             for (@names) {
549              
550             # get the filename and line number range of the target subroutine
551              
552             if( !exists $DB::sub{$_} ) {
553             print "No subroutine $_. Try main::$_\n" ;
554             next ;
555             }
556              
557             $DB::sub{$_} =~ /(.*):(\d+)-(\d+)$/o ; # file name will be in $1, start line $2, end line $3
558              
559             for( $2..$3 ) {
560             next unless &DB::checkdbline($1, $_) ;
561             $DB::window->insertBreakpoint($1, $_, 1) ;
562             last ; # only need the one breakpoint
563             }
564             } # end of name loop
565             }
566              
567             #
568             # set breakpoints on subroutines matching a regular
569             # expression
570             #
571             sub brkonsub_regex {
572             my(@regexps) = @_ ;
573             my($regexp, @subList) ;
574              
575             #
576             # accumulate matching subroutines
577             #
578             foreach $regexp ( @regexps ) {
579             study $regexp ;
580             push @subList, grep /$regexp/, keys %DB::sub ;
581             } # end of brkonsub_regex
582              
583             brkonsub(@subList) ; # set breakpoints on matching subroutines
584              
585             } # end of brkonsub_regex
586              
587             #
588             # Run files provided by the user
589             #
590             sub do_user_init_files {
591             for (grep {-e} ( (exists $ENV{'HOME'}?("$ENV{'HOME'}/.ptkdbrc"):()), ".ptkdbrc")) {
592             do $_;
593             if ($@) {
594             print STDERR "init file $_ failed: $@\n" ;
595             }
596             }
597             &set_stop_on_warning();
598             }
599              
600             #
601             # Constructor for our Devel::tcltkdb
602             #
603             sub new {
604             my($type) = @_ ;
605             my($self) = {} ;
606              
607             bless $self, $type ;
608              
609             # Current position of the executing program
610              
611             $self->{current_file} = "" ;
612             $self->{current_line} = -1 ; # initial value indicating we haven't set our line/tag
613             $self->{window_pos_offset} = 10 ; # when we enter how far from the top of the text are we positioned down
614             $self->{search_start} = "1.0" ;
615             $self->{fwdOrBack} = 1 ;
616             $self->{BookMarksPath} = $ENV{'PTKDB_BOOKMARKS_PATH'} || "$ENV{'HOME'}/.ptkdb_bookmarks" || '.ptkdb_bookmarks' ;
617              
618             $self->{'expr_list'} = [] ; # list of expressions to eval in our window fields: {'expr'} The expr itself {'depth'} expansion depth
619              
620              
621             $self->{'brkPtCnt'} = 0 ;
622             $self->{'brkPtSlots'} = [] ; # open slots for adding breakpoints to the table
623              
624             $self->{'main_window'} = undef ;
625              
626             $self->{'subs_list_cnt'} = 0 ;
627              
628             $self->setup_main_window() ;
629              
630             return $self ;
631              
632             } # end of new
633              
634             sub setup_main_window {
635             my($self) = @_ ;
636              
637             # Main Window
638             $self->{int} = new Tcl::Tk;
639             $self->{int}->packageRequireTreectrl;
640              
641             $self->{main_window} = $self->{int}->mainwindow();
642             $self->{main_window}->geometry($ENV{'PTKDB_GEOMETRY'} || "800x600") ;
643              
644             $self->{main_window}->bind('', \&DB::dbint_handler) ;
645              
646             #
647             # Bind our 'quit' routine to a close command from the window manager (Alt-F4)
648             #
649             $self->{main_window}->protocol('WM_DELETE_WINDOW', sub { $self->close_ptkdb_window(); } );
650              
651             # Menu bar
652             $self->setup_menu_bar();
653              
654             #
655             # setup Frames
656             # Setup our Code, Data, and breakpoints
657             $self->setup_frames();
658              
659             }
660              
661             #
662             # Check for changes to the bookmarks and quit
663             #
664             sub DoQuit {
665             print STDERR "DoQuit\n";
666             my($self) = @_;
667              
668             $self->save_bookmarks($self->{BookMarksPath}) if $self->{'bookmarks_changed'};
669             $self->{main_window}->destroy if $self->{main_window} ;
670             $self->{main_window} = undef;
671             }
672              
673             #
674             # This supports the File -> Open menu item
675             # We create a new window and list all of the files
676             # that are contained in the program. We also
677             # pick up all of the perlTk files that are supporting
678             # the debugger.
679             #
680             sub DoOpen {
681             my $self = shift ;
682             my ($topLevel, $listBox, $frame, $selectedFile, @fList) ;
683              
684             #
685             # subroutine we call when we've selected a file
686             #
687              
688             my $chooseSub = sub { $selectedFile = $listBox->get('active') ;
689             print "attempting to open $selectedFile\n" ;
690             $DB::window->set_file($selectedFile, 0) ;
691             $topLevel->destroy;
692             } ;
693              
694             #
695             # Take the list the files and resort it.
696             # we put all of the local files first, and
697             # then list all of the system libraries.
698             #
699             @fList = sort {
700             # sort comparison function block
701             my $fa = substr($a, 0, 1);
702             my $fb = substr($b, 0, 1);
703              
704             return $a cmp $b if ($fa eq '/') && ($fb eq '/');
705              
706             return -1 if ($fb eq '/');
707             return 1 if ($fa eq '/' );
708              
709             return $a cmp $b ;
710              
711             } grep s/^_
712              
713             #
714             # Create a list box with all of our files
715             # to select from
716             #
717             $topLevel = $self->{main_window}->Toplevel(-title => "File Select", -overanchor => 'cursor') ;
718              
719             $listBox = $topLevel->Scrolled('Listbox',
720             @Devel::tcltkdb::expression_text_font,
721             -width => 30)->pack(qw/-side top -fill both -expand 1/);
722              
723              
724             # Bind a double click on the mouse button to the same action
725             # as pressing the Okay button
726              
727             $listBox->bind('' => $chooseSub) ;
728              
729             $listBox->_insertEnd(@fList);
730              
731             $topLevel->Button(-text => "Okay", -command => $chooseSub, @Devel::tcltkdb::button_font,
732             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
733              
734             $topLevel->Button( -text => "Cancel", @Devel::tcltkdb::button_font,
735             -command => sub { $topLevel->destroy; } )->pack(qw/-side left -fill both -expand 1/);
736             } # end of DoOpen
737              
738             sub do_tabs {
739             my $w = $DB::window->{'main_window'}->DialogBox(-title => "Tabs", -buttons => [qw/Okay Cancel/]) ;
740              
741             my $tabs_cfg = $DB::window->{'text'}->cget('-tabs');
742             my $tabs_str = join " ", @$tabs_cfg if $tabs_cfg;
743              
744             $w->add('Label', -text => 'Tabs:')->pack(-side => 'left');
745             $w->add('Entry', -textvariable => \$tabs_str)->pack(-side => 'left')->selectionRange(0,'end');
746             my $result = $w->Show();
747              
748             $DB::window->{'text'}->configure(-tabs => [ split /\s+/, $tabs_str ])
749             if $result eq 'Okay' ;
750             }
751              
752             sub close_ptkdb_window {
753             print STDERR "close_ptkdb_window\n";
754             my($self) = @_ ;
755              
756             $DB::window->{'event'} = 'run';
757             $self->{current_file} = ""; # force a file reset
758             $self->{'main_window'}->destroy;
759             $self->{'main_window'} = undef;
760             }
761              
762             sub setup_menu_bar {
763             my ($self) = @_;
764              
765             my $mw = $self->{main_window} ;
766             my $int = $mw->interp;
767              
768              
769             # file menu in menu bar
770              
771             my $items1 = [ [ 'command' => 'About...', -command => sub { $self->DoAbout() ; } ],
772             [ 'command' => 'Bug Report...', -command => 'puts "bugreport TBD"' ],
773             "-",
774              
775             [ 'command' => 'Open', -accelerator => 'Alt+O',
776             -underline => 0,
777             -command => sub { $self->DoOpen() ; } ],
778              
779             [ 'command' => 'Save Config...',
780             -underline => 0,
781             -command => \&DB::SaveState ],
782              
783             [ 'command' => 'Restore Config...',
784             -underline => 0,
785             -command => \&DB::RestoreState],
786              
787             [ 'command' => 'Goto Line...',
788             -underline => 0,
789             -accelerator => 'Alt-g',
790             -command => sub { $self->GotoLine() ; } ],
791              
792             [ 'command' => 'Find Text...',
793             -accelerator => 'Ctrl-f',
794             -underline => 0,
795             -command => sub { $self->FindText() ; } ],
796              
797             [ 'command' => "Tabs...", -command => \&do_tabs ],
798              
799             "-",
800              
801             [ 'command' => 'Close Window and Run', -accelerator => 'Alt+W',
802             -underline => 6, -command => sub { $self->close_ptkdb_window ; } ],
803              
804             [ 'command' => 'Quit...', -accelerator => 'Alt+Q',
805             -underline => 0,
806             -command => sub { $self->DoQuit } ]
807             ];
808              
809              
810             $mw->bind('' => sub { $self->GotoLine() ; }) ;
811             $mw->bind('' => sub { $self->FindText() ; }) ;
812             $mw->bind('' => \&Devel::tcltkdb::DoRestart) ;
813             $mw->bind('' => sub { $self->{'event'} = 'quit' } ) ;
814             $mw->bind('' => sub { $self->close_ptkdb_window ; }) ;
815              
816              
817             # Control Menu
818              
819             my $runSub = sub { $DB::step_over_depth = -1 ; $self->{'event'} = 'run' } ;
820              
821             my $runToSub = sub { $DB::window->{'event'} = 'run' if $DB::window->SetBreakPoint(1) ; } ;
822              
823             my $stepOverSub = sub { &DB::SetStepOverBreakPoint(0) ;
824             $DB::single = 1 ;
825             $DB::window->{'event'} = 'step' ;
826             } ;
827              
828             my $stepInSub = sub {
829             $DB::step_over_depth = -1 ;
830             $DB::single = 1 ;
831             $DB::window->{'event'} = 'step' ;
832             };
833              
834             my $returnSub = sub {
835             &DB::SetStepOverBreakPoint(-1) ;
836             $self->{'event'} = 'run' ;
837             };
838              
839              
840             my $items2 = [ [ 'command' => 'Run', -accelerator => 'Alt+r', -underline => 0, -command => $runSub ],
841             [ 'command' => 'Run To Here', -accelerator => 'Alt+t', -underline => 5, -command => $runToSub ],
842             '-',
843             [ 'command' => 'Set Breakpoint', -underline => 4, -command => sub { $self->SetBreakPoint ; }, -accelerator => 'Ctrl-b' ],
844             [ 'command' => 'Clear Breakpoint', -command => sub { $self->UnsetBreakPoint } ],
845             [ 'command' => 'Clear All Breakpoints', -underline => 6, -command => sub {
846             $DB::window->removeAllBreakpoints($DB::window->{current_file});
847             &DB::clearalldblines();
848             } ],
849             '-',
850             [ 'command' => 'Step Over', -accelerator => 'Alt+N', -underline => 0, -command => $stepOverSub ],
851             [ 'command' => 'Step In', -accelerator => 'Alt+S', -underline => 5, -command => $stepInSub ],
852             [ 'command' => 'Return', -accelerator => 'Alt+U', -underline => 3, -command => $returnSub ],
853             '-',
854             [ 'command' => 'Restart...', -accelerator => 'Ctrl-r', -underline => 0, -command => \&Devel::tcltkdb::DoRestart ],
855             '-',
856             [ 'checkbutton' => 'Stop On Warning', -variable => \$DB::tcltkdb::stop_on_warning, -command => \&set_stop_on_warning ]
857             ] ; # end of control menu items
858              
859             $mw->bind('' => $runSub) ;
860             $mw->bind('', $runToSub) ;
861             $mw->bind('', sub { $self->SetBreakPoint ; });
862              
863             # step over a subroutine
864             for ('', '', '') {
865             $mw->bind($_ => $stepOverSub);
866             }
867              
868             # keys for step into a subroutine
869             for ('', '', '') {
870             $mw->bind($_ => $stepInSub );
871             }
872              
873             # return from a subroutine
874             for ('', '') {
875             $mw->bind($_ => $returnSub );
876             }
877              
878             # Data Menu
879              
880             my $items3 = [ [ 'command' => 'Enter Expression', -accelerator => 'Alt+E', -command => sub { $self->EnterExpr() } ],
881             [ 'command' => 'Delete Expression', -accelerator => 'Ctrl+D', -command => sub { $self->deleteExpr() } ],
882             [ 'command' => 'Delete All Expressions', -command => sub {
883             $self->deleteAllExprs() ;
884             $self->{'expr_list'} = [] ; # clears list by dropping ref to it, replacing it with a new one
885             } ],
886             '-',
887             [ 'command' => 'Expression Eval Window...', -accelerator => 'F8', -command => sub { $self->setupEvalWindow() ; } ],
888             ];
889              
890             $mw->bind('' => sub { $self->EnterExpr() } ) ;
891             $mw->bind('' => sub { $self->deleteExpr() } );
892             $mw->bind('', sub { $self->setupEvalWindow() ; }) ;
893              
894             #
895             # Windows Menu
896             #
897             my $bsub = sub { $self->{'text'}->focus() };
898             my $csub = sub { $self->{'quick_entry'}->focus() };
899             my $dsub = sub { $self->{'entry'}->focus() };
900              
901             my $items4 = [ [ 'command' => 'Code Pane', -accelerator => 'Alt+0', -command => $bsub ],
902             [ 'command' => 'Quick Entry', -accelerator => 'F9', -command => $csub ],
903             [ 'command' => 'Expr Entry', -accelerator => 'F11', -command => $dsub ]
904             ];
905              
906             $mw->bind('', $bsub);
907             $mw->bind('', $csub);
908             $mw->bind('', $dsub);
909              
910             my $menu = $mw->Menu(-menuitems => [
911             [Cascade=>'File', -tearoff => 0, -underline=>0, -menuitems=>$items1],
912             [Cascade=>'Control', -tearoff=>0, -underline=>0, -menuitems => $items2],
913             [Cascade=>'Data', -tearoff=>0, -menuitems => $items3, -underline => 0],
914             [Cascade=>'Stack', -tearoff=>0, -underline => 2],
915             [Cascade=>'Bookmarks', -tearoff=>0, -underline=>0],
916             [Cascade=>'Windows', -tearoff=>0, -menuitems => $items4]
917             ]);
918             #
919             # Stack menu
920             $self->{stack_menu} = $int->widget($menu->entrycget(4,'-menu'),'Menubutton');
921             #
922             # Bookmarks menu
923             $self->{bookmarks_menu} = $int->widget($menu->entrycget(5,'-menu'),'Menubutton');
924              
925             $self->setup_bookmarks_menu();
926              
927             $mw->config(-menu=>$menu);
928              
929             #
930             # Bar for some popular controls
931             my $bb = $mw->Frame()->pack(-side => 'top');
932              
933             $bb->Button(-text => "Step In", @Devel::tcltkdb::button_font,
934             -command => $stepInSub) ->pack(-side => 'left');
935             $bb->Button(-text => "Step Over", @Devel::tcltkdb::button_font,
936             -command => $stepOverSub) ->pack(-side => 'left');
937             $bb->Button(-text => "Return", @Devel::tcltkdb::button_font,
938             -command => $returnSub) ->pack(-side => 'left');
939             $bb->Button(-text => "Run", -background => 'green', @Devel::tcltkdb::button_font,
940             -command => $runSub) ->pack(-side => 'left');
941             $bb->Button(-text => "Run To", @Devel::tcltkdb::button_font,
942             -command => $runToSub) ->pack(-side => 'left');
943             $bb->Button(-text => "Break", @Devel::tcltkdb::button_font,
944             -command => sub { $self->SetBreakPoint ; } ) ->pack(-side => 'left');
945              
946             } # end of setup_menu_bar
947              
948             sub edit_bookmarks {
949             my ($self) = @_ ;
950              
951             my $top = $self->{main_window}->Toplevel(-title => "Edit Bookmarks");
952             my $list = $top->Scrolled('Listbox', -selectmode => 'multiple')->pack(-side => 'top', -fill => 'both', -expand => 1) ;
953              
954             my $deleteSub = sub {
955             my $cnt = 0 ;
956             for( $list->curselection ) {
957             $list->delete($_ - $cnt++) ;
958             }
959             };
960              
961             my $okaySub = sub {
962             $self->{'bookmarks'} = [ $list->get(0, 'end') ] ; # replace the bookmarks
963             };
964              
965             my $frm = $top->Frame()->pack(-side => 'top', -fill => 'x', -expand => 1 ) ;
966              
967             my $deleteBtn = $frm->Button(-text => 'Delete', -command => $deleteSub)->pack(-side => 'left', -fill => 'x', -expand => 1 );
968             my $cancelBtn = $frm->Button(-text => 'Cancel', -command => sub { $top->destroy; })->pack(-side =>'left', -fill => 'x', -expand => 1 );
969             my $dismissBtn = $frm->Button(-text => 'Okay', -command => $okaySub)->pack(-side => 'left', -fill => 'x', -expand => 1 );
970              
971             $list->insert('end', @{$self->{'bookmarks'}}) ;
972              
973             } # end of edit_bookmarks
974              
975             sub setup_bookmarks_menu {
976             my ($self) = @_ ;
977              
978             #
979             # "Add bookmark" item
980             #
981             my $bkMarkSub = sub { $self->add_bookmark() ; } ;
982              
983             $self->{'bookmarks_menu'}->command(-label => "Add Bookmark",
984             -accelerator => 'Alt+k',
985             -command => $bkMarkSub
986             ) ;
987              
988             $self->{'main_window'}->bind('', $bkMarkSub) ;
989              
990             $self->{'bookmarks_menu'}->command(-label => "Edit Bookmarks",
991             -command => sub { $self->edit_bookmarks() } ) ;
992              
993             $self->{'bookmarks_menu'}->separator() ;
994              
995             #
996             # Check to see if there is a bookmarks file
997             #
998             return unless -e $self->{BookMarksPath} && -r $self->{BookMarksPath} ;
999              
1000             use vars qw($ptkdb_bookmarks) ;
1001             local($ptkdb_bookmarks) ; # ref to hash of bookmark entries
1002              
1003             do $self->{BookMarksPath} ; # eval the file
1004              
1005             $self->add_bookmark_items(@$ptkdb_bookmarks) ;
1006              
1007             } # end of setup_bookmarks_menu
1008              
1009             #
1010             # $item = "$fname:$lineno"
1011             #
1012             sub add_bookmark_items {
1013             my($self, @items) = @_ ;
1014             my($menu) = ( $self->{'bookmarks_menu'} ) ;
1015              
1016             $self->{'bookmarks_changed'} = 1 ;
1017              
1018             for( @items ) {
1019             my $item = $_ ;
1020             $menu->command( -label => $_,
1021             -command => sub { $self->bookmark_cmd($item) });
1022             push @{$self->{'bookmarks'}}, $item;
1023             }
1024             } # end of add_bookmark_item
1025              
1026             #
1027             # Invoked from the "Add Bookmark" command
1028             #
1029             sub add_bookmark {
1030             my($self) = @_ ;
1031              
1032             my $line = $self->get_lineno();
1033             my $fname = $self->{'current_file'};
1034             $self->add_bookmark_items("$fname:$line");
1035              
1036             } # end of add_bookmark
1037              
1038             #
1039             # Command executed when someone selects a bookmark
1040             #
1041             sub bookmark_cmd {
1042             my ($self, $item) = @_;
1043             $item =~ /^(.*):(\d+)$/;
1044             $self->set_file($1,$2);
1045             }
1046              
1047             sub save_bookmarks {
1048             my($self, $pathName) = @_ ;
1049              
1050             local(*F) ;
1051              
1052             eval {
1053             open F, ">$pathName" || die "open failed" ;
1054             my $d = Data::Dumper->new([ $self->{'bookmarks'} ],
1055             [ 'ptkdb_bookmarks' ]);
1056             $d->Indent(2) ; # make it more editable for people
1057              
1058             print F $d->Dump() || die "outputing bookmarks failed";
1059             close(F);
1060             };
1061              
1062             if ($@) {
1063             $self->DoAlert("Couldn't save bookmarks file $@") ;
1064             return;
1065             }
1066              
1067             } # end of save_bookmarks
1068              
1069              
1070             sub line_number_from_coord {
1071             my($txtWidget, $coord) = @_ ;
1072             $txtWidget->index($coord) =~ /^(\d*)\.(\d*)$/;
1073             return $1;
1074             } # end of line_number_from_coord
1075              
1076             #
1077             # It may seem as if $txtWidget and $self are
1078             # erroneously reversed, but this is a result
1079             # of the calling syntax of the text-bind callback.
1080             #
1081             sub set_breakpoint_tag {
1082             my ($self, $txtWidget, $coord, $value) = @_ ;
1083              
1084             my $idx = line_number_from_coord($txtWidget, $coord) ;
1085              
1086             $self->insertBreakpoint($self->{'current_file'}, $idx, $value) ;
1087              
1088             } # end of set_breakpoint_tag
1089              
1090             sub clear_breakpoint_tag {
1091             my ($self, $txtWidget, $coord) = @_ ;
1092              
1093             my $idx = line_number_from_coord($txtWidget, $coord) ;
1094              
1095             $self->removeBreakpoint($self->{'current_file'}, $idx) ;
1096              
1097             } # end of clear_breakpoint_tag
1098              
1099             sub change_breakpoint_tag {
1100             my ($self, $txtWidget, $coord, $value) = @_ ;
1101             my ($brkPt, @tagSet) ;
1102              
1103             my $idx = line_number_from_coord($txtWidget, $coord) ;
1104              
1105             #
1106             # Change the value of the breakpoint
1107             #
1108             @tagSet = ( "$idx.0", "$idx.$Devel::tcltkdb::linenumber_length" ) ;
1109              
1110             $brkPt = &DB::getdbline($self->{'current_file'}, $idx + $self->{'line_offset'}) ;
1111             return unless $brkPt ;
1112              
1113             #
1114             # Check the breakpoint tag
1115             #
1116              
1117             if ( $txtWidget ) {
1118             $txtWidget->tagRemove('breaksetLine', @tagSet ) ;
1119             $txtWidget->tagRemove('breakdisabledLine', @tagSet ) ;
1120             }
1121              
1122             $brkPt->{'value'} = $value ;
1123              
1124             if ( $txtWidget ) {
1125             if ( $brkPt->{'value'} ) {
1126             $txtWidget->tagAdd('breaksetLine', @tagSet ) ;
1127             }
1128             else {
1129             $txtWidget->tagAdd('breakdisabledLine', @tagSet ) ;
1130             }
1131             }
1132              
1133             } # end of change_breakpoint_tag
1134              
1135             #
1136             # God Forbid anyone comment something complex and tightly optimized.
1137             #
1138             # We can get a list of the subroutines from the interpreter
1139             # by querrying the *DB::sub typeglob: keys %DB::sub
1140             #
1141             # The list appears broken down by module:
1142             #
1143             # main::BEGIN
1144             # main::mySub
1145             # main::otherSub
1146             # Tk::Adjuster::Mapped
1147             # Tk::Adjuster::Packed
1148             # Tk::Button::BEGIN
1149             # Tk::Button::Enter
1150             #
1151             # We would like to break this list down into a heirarchy.
1152             #
1153             # main Tk
1154             # | | | |
1155             # BEGIN mySub OtherSub | |
1156             # Adjuster Button
1157             # | | | |
1158             # Mapped Packed BEGIN Enter
1159             #
1160             #
1161             # We translate this list into a heirarchy of hashes(say three times fast).
1162             # We take each entry and split it into elements. Each element is a leaf in the tree.
1163             # We traverse the tree with the inner for loop.
1164             # With each branch we check to see if it already exists or
1165             # we create it. When we reach the last element, this becomes our entry.
1166             #
1167              
1168             #
1169             # An incoming list is potentially 'large' so we
1170             # pass in the ref to it instead.
1171             #
1172             # New entries can be inserted by providing a $topH
1173             # hash ref to an existing tree.
1174             #
1175             sub tree_split {
1176             my ($listRef) = @_;
1177             my $topH = {};
1178              
1179             for my $list_elem (@$listRef) {
1180             my $h = $topH ;
1181             for (split /::/, $list_elem) { # Tk::Adjuster::Mapped -> ( Tk Adjuster Mapped )
1182             $h->{$_} or $h->{$_} = {}; # either we have an entry for this OR we create one
1183             $h = $h->{$_};
1184             }
1185             @$h{'name', 'path'} = (undef, $list_elem) ; # the last leaf is our entry
1186             } # end of tree_split loop
1187              
1188             return $topH ;
1189             } # end of tree_split
1190              
1191             #
1192             # callback executed when someone double clicks
1193             # an entry in the 'Subs' Tk::Notebook page.
1194             #
1195             sub sub_list_cmd {
1196             my ($self, $path) = @_;
1197             print STDERR "arg=[[@_]]\n";
1198             my $sub_list = $self->{'sub_list'} ;
1199              
1200             if ($sub_list->info('children', $path)) {
1201             #
1202             # Delete the children
1203             $sub_list->deleteOffsprings($path);
1204             print STDERR "vvvv2\n";
1205             return;
1206             }
1207             print STDERR "vvvv3\n";
1208              
1209             #
1210             # split the path up into elements
1211             # end descend through the tree.
1212             #
1213             my $h = $Devel::tcltkdb::subs_tree ;
1214             for ( split /\./, $path ) {
1215             $h = $h->{$_} ; # next level down
1216             }
1217              
1218             #
1219             # if we don't have a 'name' entry we
1220             # still have levels to decend through.
1221             #
1222             if ( !exists $h->{'name'} ) {
1223             #
1224             # Add the next level paths
1225             #
1226             for ( sort keys %$h ) {
1227              
1228             if ( exists $h->{$_}->{'path'} ) {
1229             $sub_list->add($path . '.' . $_, -text => $h->{$_}->{'path'}) ;
1230             } else {
1231             $sub_list->add($path . '.' . $_, -text => $_) ;
1232             }
1233             }
1234             return ;
1235             }
1236              
1237             $DB::sub{$h->{'path'}} =~ /^(.*):(\d+)-\d+$/; # file name will be in $1, line number will be in $2
1238              
1239             $self->set_file($1, $2);
1240             } # end of sub_list_cmd
1241              
1242             sub sub_list_cmd0 {
1243             my ($self) = @_;
1244             my $list = $self->{sub_list0} ;
1245             my ($la, $le) = ($list->_indexActive,$list->_indexEnd);
1246             print STDERR "<<$la-$le>>\n";
1247             my @l = map {$list->get($_)} $la .. $le;
1248             # check if items following $l[0] are its children, and delete it, if it is the case
1249             my @levs = map {/^(\s*)/;length($1)} @l;
1250             print STDERR "{{@l}}\n";
1251             print STDERR "{{@levs}}\n";
1252             my $lev = $levs[0];
1253             my $l1 = 1;
1254             my $direct_children=0;
1255             while ($l1<=$#l and $lev<$levs[$l1]) {
1256             # delete list[l1]
1257             $list->delete($la+1);
1258             $l1++;
1259             $direct_children=1;
1260             }
1261             return if $direct_children;
1262              
1263             #
1264             # split the path up into elements end descend through the tree.
1265             my $path = $list->get($la);
1266             $path =~ s/^\s+//;
1267             my $h = $Devel::tcltkdb::subs_tree;
1268             for ( split /::/, $path ) {
1269             $h = $h->{$_} ; # next level down
1270             }
1271              
1272             #
1273             # if we don't have a 'name' entry we
1274             # still have levels to decend through.
1275             #
1276             if ( !exists $h->{'name'} ) {
1277             #
1278             # Add the next level paths
1279             my $sp = " " x ($lev+1);
1280             for (sort keys %$h) {
1281             if ( exists $h->{$_}->{'path'} ) {
1282             $list->insert($la+$l1,$sp.$h->{$_}->{'path'});
1283             } else {
1284             $list->insert($la+$l1,$sp.$_);
1285             }
1286             $l1++;
1287             }
1288             return ;
1289             }
1290              
1291             $DB::sub{$h->{'path'}} =~ /(.*):(\d+)-\d+$/; # file name will be in $1, line number in $2
1292              
1293             $self->set_file($1, $2);
1294             }
1295              
1296             sub fill_subs_page {
1297             my $self = shift;
1298             my @list = keys %DB::sub;
1299              
1300             $self->{sub_list0}->delete(0,'end'); # clear existing entries
1301              
1302             $Devel::tcltkdb::subs_tree = tree_split(\@list);
1303              
1304             for ( sort keys %$Devel::tcltkdb::subs_tree ) {
1305             $self->{sub_list0}->_insertEnd($_);
1306             }
1307             }
1308              
1309             sub setup_subs_page {
1310             my $self = shift;
1311              
1312             $self->{'subs_page_activated'} = 1;
1313              
1314             my $w1 = $self->{'subs_page'}->Scrolled('Listbox', -selectmode=>'single');
1315             $self->{'sub_list0'} = $w1->Subwidget;
1316             $self->{int}->bind($self->{'sub_list0'}, "" => sub { $self->sub_list_cmd0(@_); });
1317              
1318             $w1->pack(qw/-side left -fill both -expand 1/);
1319              
1320             $self->fill_subs_page();
1321              
1322             $self->{'subs_list_cnt'} = scalar keys %DB::sub;
1323              
1324             } # end of setup_subs_page
1325              
1326              
1327             sub check_search_request {
1328             my($entry, $self, $searchButton, $regexBtn) = @_ ;
1329             my($txt) = $entry->get ;
1330              
1331             if( $txt =~ /^\s*\d+\s*$/ ) {
1332             $self->DoGoto($entry) ;
1333             return ;
1334             }
1335              
1336             if( $txt =~ /\.\*/ ) { # common regex search pattern
1337             $self->FindSearch($entry, $regexBtn, 1) ;
1338             return ;
1339             }
1340              
1341             # vanilla search
1342             $self->FindSearch($entry, $searchButton, 0) ;
1343             }
1344              
1345             sub setup_search_panel {
1346             my ($self, $parent) = @_ ;
1347             my ($srchBtn, $regexBtn, $entry) ;
1348              
1349             my $frm = $parent->Frame();
1350              
1351             $frm->Button(-text => 'Goto', -command => sub { $self->DoGoto($entry) })->pack(-side => 'left');
1352             $srchBtn = $frm->Button(-text => 'Search', -command => sub { $self->FindSearch($entry, $srchBtn, 0) ; }
1353             )->pack(-side => 'left');
1354              
1355             $regexBtn = $frm->Button(-text => 'Regex',
1356             -command => sub { $self->FindSearch($entry, $regexBtn, 1) ; }
1357             )->pack(-side => 'left');
1358              
1359             $entry = $frm->Entry(-width => 50)->pack(qw/-side left -fill both -expand 1/);
1360              
1361             $entry->bind('', sub { check_search_request($entry, $self, $srchBtn, $regexBtn) ; } );
1362              
1363             $frm->pack(qw/-side top -fill x/);
1364              
1365             } # end of setup search_panel
1366              
1367             sub setup_breakpts_page {
1368             my ($self) = @_ ;
1369              
1370             $self->{'notebook'}->_insertEnd("brkptspage", -text => "BrkPts") ;
1371              
1372             my $sw = $self->{'notebook'}->getframe("brkptspage")->ScrolledWindow()->pack(qw(-side top -fill both -expand 1));
1373              
1374             $self->{'breakpts_table'} = $sw->ScrollableFrame();
1375             $sw->setwidget($self->{'breakpts_table'});
1376              
1377             $self->{'breakpts_table_data'} = {}; # controls addressed by "fname:lineno"
1378              
1379             } # end of setup_breakpts_page
1380              
1381             sub setup_frames {
1382             my ($self) = @_;
1383             my $mw = $self->{'main_window'};
1384              
1385             my $pw = $mw->Panedwindow()->pack(qw/-side left -fill both -expand 1/);
1386             my $frm = $pw->Frame->pack(qw/-side top -fill both -expand 1/); # frame for our code pane and search controls
1387              
1388             $self->setup_search_panel($frm);
1389              
1390             #
1391             # Text window for the code of our currently viewed file
1392             #
1393             my $txt = $frm->Scrolled('ROText', -wrap => "none",
1394             @Devel::tcltkdb::code_text_font
1395             )->pack(qw/-side top -fill both -expand 1/);
1396             $self->{'text'} = $txt->Subwidget;
1397              
1398             $self->configure_text();
1399              
1400             #
1401             # Notebook
1402             #
1403              
1404             my $nb = $self->{'notebook'} = $pw->BWNoteBook()
1405             ->pack(qw/-side left -fill both -expand 1/);
1406              
1407             $pw->add($frm, $nb);
1408              
1409             #
1410             # a widget for the data entries
1411             #
1412             $nb->_insertEnd("datapage", -text => "Exprs");
1413             $self->{'data_page'} = $nb->getframe("datapage");
1414              
1415             #
1416             # frame, entry and label for quick expressions
1417             #
1418             my $frame = $self->{'data_page'}->Frame()->pack(-side => 'top', -fill => 'x') ;
1419             my $label = $frame->Label(-text => "Quick Expr:")->pack(-side => 'left') ;
1420              
1421             $self->{'quick_entry'} = $frame->Entry()->pack(-side => 'left', -fill => 'x', -expand => 1) ;
1422             $self->{'quick_entry'}->bind('', sub { $self->QuickExpr() ; } ) ;
1423              
1424             #
1425             # Entry widget for expressions and breakpoints
1426             #
1427             $frame = $self->{'data_page'}->Frame()->pack(-side => 'top', -fill => 'x') ;
1428             $label = $frame->Label(-text => "Enter Expr:")->pack(-side => 'left') ;
1429              
1430             $self->{'entry'} = $frame->Entry()->pack(-side => 'left', -fill => 'x', -expand => 1) ;
1431             $self->{'entry'}->bind('', sub { $self->EnterExpr() }) ;
1432              
1433             #
1434             # tk widget for data expressions
1435             #
1436             my $w_tree = $self->{'data_page'}->Scrolled('Treectrl',-showroot=>1,-showrootbutton=>1)
1437             ->pack(qw/-side top -fill both -expand 1/);
1438             $self->{data_list0} = [$w_tree->Subwidget, $w_tree->columnCreate()];
1439             $w_tree->elementCreate('foo','text');
1440             $w_tree->elementCreate('bar','rect',-showfocus=>1);
1441             $w_tree->styleCreate('st');
1442             $w_tree->styleElements('st',['foo','bar']);
1443             $w_tree->styleLayout('st','bar',-union=>'foo');
1444             $w_tree->configure(-defaultstyle=>'st',-treecolumn=>$self->{data_list0}->[1]);
1445              
1446             $self->{'subs_page_activated'} = 0 ;
1447             $nb->_insertEnd("subspage", -text => "Subs");
1448             $self->{'subs_page'} = $nb->getframe("subspage");
1449              
1450             $self->setup_subs_page();
1451             $self->setup_breakpts_page();
1452              
1453             $nb->_raise("datapage");
1454              
1455             } # end of setup_frames
1456              
1457              
1458             sub configure_text {
1459             my($self) = @_ ;
1460             my($txt, $mw) = ($self->{'text'}, $self->{'main_window'}) ;
1461              
1462             if (0) {
1463             # balloon
1464             $self->{'expr_balloon'} = $txt->Balloon();
1465             $self->{'balloon_expr'} = ' '; # initial expression
1466              
1467             $self->{'expr_ballon_msg'} = ' ';
1468             $self->{'expr_balloon'}->attach($txt, -initwait => 300,
1469             -msg => \$self->{'expr_ballon_msg'},
1470             -balloonposition => 'mouse',
1471             -postcommand => \&Devel::tcltkdb::balloon_post,
1472             -motioncommand => \&Devel::tcltkdb::balloon_motion );
1473             }
1474              
1475             $self->{'quick_dumper'} = new Data::Dumper([]);
1476             $self->{'quick_dumper'}->Terse(1);
1477             $self->{'quick_dumper'}->Indent(0);
1478              
1479              
1480             # tags for the text
1481             # 'code' Format for code in the text pane
1482             # 'stoppt' Format applied to the line where the debugger is currently stopped
1483             # 'breakableLine' Format applied to line numbers where the code is 'breakable'
1484             # 'nonbreakableLine' Format applied to line numbers where the code is no breakable
1485             # 'breaksetLine' Format applied to line numbers were a breakpoint is set
1486             # 'breakdisabledLine' Format applied to line numbers were a disabled breakpoint is set
1487             # 'search_tag' Format applied to text when located by a search.
1488              
1489             my @stopTagConfig = ( -foreground => 'white', -background => $mw->optionGet("stopcolor", "background") || $ENV{'PTKDB_STOP_TAG_COLOR'} || 'blue' );
1490              
1491             my $stopFnt = $mw->optionGet("stopfont", "background") || $ENV{'PTKDB_STOP_TAG_FONT'} ;
1492             push @stopTagConfig, ( -font => $stopFnt ) if $stopFnt ; # user may not have specified a font, if not, stay with the default
1493              
1494             $txt->_tagConfigure('stoppt', @stopTagConfig) ;
1495             $txt->_tagConfigure('search_tag', "-background" => $mw->optionGet("searchtagcolor", "background") || "green") ;
1496              
1497             $txt->_tagConfigure("breakableLine", -overstrike => 0) ;
1498             $txt->_tagConfigure("nonbreakableLine", -overstrike => 1) ;
1499             $txt->_tagConfigure("breaksetLine", -background => $mw->optionGet("breaktagcolor", "background") || $ENV{'PTKDB_BRKPT_COLOR'} || 'red') ;
1500             $txt->_tagConfigure("breakdisabledLine", -background => $mw->optionGet("disabledbreaktagcolor", "background") || $ENV{'PTKDB_DISABLEDBRKPT_COLOR'} || 'green') ;
1501              
1502             $txt->tagBind("breakableLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]);$self->set_breakpoint_tag($txt, "\@$ex,$ey", 1 )} );
1503             $txt->tagBind("breakableLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]); $self->set_breakpoint_tag($txt, "\@$ex,$ey", 0 )} ) ;
1504              
1505             $txt->tagBind("breaksetLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]); $self->clear_breakpoint_tag($txt, "\@$ex,$ey", )} ) ;
1506             $txt->tagBind("breaksetLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]); $self->change_breakpoint_tag($txt, "\@$ex,$ey", 0 )} ) ;
1507              
1508             $txt->tagBind("breakdisabledLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]); $self->clear_breakpoint_tag($txt, "\@$ex,$ey", )} ) ;
1509             $txt->tagBind("breakdisabledLine", '', \\'xy', sub {my($ex,$ey)=($_[-2],$_[-1]); $self->change_breakpoint_tag($txt, "\@$ex,$ey", 1) } ) ;
1510              
1511             } # end of configure_text
1512              
1513              
1514             sub DoAlert {
1515             my($self, $msg, $title) = @_ ;
1516              
1517             my $dlg = $self->{main_window}->Toplevel(-title => $title || "Alert", -overanchor => 'cursor') ;
1518             my $okaySub = sub {
1519             $dlg->destroy;
1520             };
1521              
1522             $dlg->Label(-text => $msg )->pack( -side => 'top' ) ;
1523             $dlg->Button(-text => "Okay", -command => $okaySub )->pack(-side => 'top')->focus;
1524             $dlg->bind('', $okaySub);
1525              
1526             } # end of DoAlert
1527              
1528             sub simplePromptBox {
1529             my ($self, $title, $defaultText, $okaySub, $cancelSub) = @_ ;
1530             $Devel::tcltkdb::promptString = $defaultText;
1531              
1532             my $top = $self->{main_window}->Toplevel(-title => $title, -overanchor => 'cursor');
1533             my $entry = $top->Entry(-textvariable => \$Devel::tcltkdb::promptString)->pack(-side => 'top', -fill => 'both', -expand => 1);
1534             $top->Button(-text => "Okay", @Devel::tcltkdb::button_font, -command => sub { &$okaySub(); $top->destroy ;}
1535             )->pack(-side => 'left', -fill => 'both', -expand => 1);
1536             $top->Button(-text => "Cancel", -command => sub { &$cancelSub() if $cancelSub ; $top->destroy() },
1537             @Devel::tcltkdb::button_font)->pack(-side => 'left', -fill => 'both', -expand => 1);
1538             $entry->icursor('end');
1539             $entry->selectionRange(0, 'end');
1540             $entry->focus();
1541              
1542             return $top ;
1543             } # end of simplePromptBox
1544              
1545              
1546             #
1547             # Clear any text that is in the entry field. If there
1548             # was any text in that field return it. If there
1549             # was no text then return any selection that may be active.
1550             #
1551             sub clear_entry_text {
1552             my($self) = @_ ;
1553             my $str = $self->{'entry'}->get() ;
1554             $self->{'entry'}->delete(0, 'end') ;
1555              
1556             #
1557             # No String
1558             # Empty String
1559             # Or a string that is only whitespace
1560             #
1561             if( !$str || $str =~ /^\s*$/ ) {
1562             #
1563             # If there is no string or the string is just white text
1564             # Get the text in the selection (if any)
1565             #
1566             if( $self->{'text'}->tagRanges('sel') ) { # check to see if 'sel' tag exists
1567             $str = $self->{'text'}->get("sel.first", "sel.last") ; # get the text between the 'first' and 'last' point of the sel (selection) tag
1568             }
1569             # If still no text, bring the focus to the entry
1570             if (!$str || $str =~ /^\s*$/) {
1571             $self->{'entry'}->focus();
1572             $str = "";
1573             }
1574             }
1575             #
1576             # Erase existing text
1577             #
1578             return $str;
1579             } # end of clear_entry_text
1580              
1581             sub brkPtCheckbutton {
1582             my ($self, $fname, $idx, $brkPt) = @_ ;
1583             my ($widg) ;
1584              
1585             $self->change_breakpoint_tag($self->{'text'}, "$idx.0", $brkPt->{'value'}) if $fname eq $self->{'current_file'} ;
1586              
1587             } # end of brkPtCheckbutton
1588              
1589             #
1590             # insert a breakpoint control into our breakpoint list.
1591             # returns a handle to the control
1592             #
1593             # Expression, if defined, is to be evaluated at the breakpoint
1594             # and execution stopped if it is non-zero/defined.
1595             #
1596             # If action is defined && True then it will be evalled
1597             # before continuing.
1598             #
1599             sub insertBreakpoint {
1600             my ($self, $fname, @brks) = @_ ;
1601             my ($btn, $cnt, $item) ;
1602              
1603             my($offset) ;
1604             local(*dbline) = $main::{'_<' . $fname} ;
1605              
1606             $offset = $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ? 1 : 0 ;
1607              
1608             while( @brks ) {
1609             my($index, $value, $expression) = splice @brks, 0, 3 ; # take args 3 at a time
1610              
1611             my $brkPt = {} ;
1612             my $txt = &DB::getdbtextline($fname, $index) ;
1613             @$brkPt{'type', 'line', 'expr', 'value', 'fname', 'text'} =
1614             ('user', $index, $expression, $value, $fname, "$txt") ;
1615              
1616             &DB::setdbline($fname, $index + $offset, $brkPt) ;
1617             $self->add_brkpt_to_brkpt_page($brkPt) ;
1618              
1619             next unless $fname eq $self->{'current_file'} ;
1620              
1621             $self->{'text'}->tagRemove("breakableLine", "$index.0", "$index.$Devel::tcltkdb::linenumber_length") ;
1622             $self->{'text'}->tagAdd($value ? "breaksetLine" : "breakdisabledLine", "$index.0", "$index.$Devel::tcltkdb::linenumber_length") ;
1623             } # end of loop
1624             } # end of insertBreakpoint
1625              
1626             sub add_brkpt_to_brkpt_page {
1627             my($self, $brkPt) = @_ ;
1628             #
1629             # Add the breakpoint to the breakpoints page
1630             #
1631             my ($fname, $index) = @$brkPt{'fname', 'line'} ;
1632             return if exists $self->{'breakpts_table_data'}->{"$fname:$index"} ;
1633             $self->{'brkPtCnt'} += 1 ;
1634              
1635             my $btnName = $fname ;
1636             $btnName =~ s/.*\/([^\/]*)$/$1/o ;
1637              
1638             # take the last leaf of the pathname
1639              
1640             my $frm = $self->{'breakpts_table'}->getframe;
1641             my $upperFrame = $frm->Frame()->pack(qw/-side top -fill x -expand 1/);
1642              
1643              
1644             my $btn = $upperFrame->Checkbutton(-text => "$btnName:$index",
1645             -variable => \$brkPt->{'value'}, # CAUTION value tracking
1646             -command => sub { $self->brkPtCheckbutton($fname, $index, $brkPt) }) ;
1647              
1648             $btn->pack(-side => 'left') ;
1649              
1650             $btn = $upperFrame->Button(-text => "Delete", -command => sub { $self->removeBreakpoint($fname, $index) ; } )
1651             ->pack(qw/-side left -fill x -expand 1/);
1652              
1653             $btn = $upperFrame->Button(-text => "Goto", -command => sub { $self->set_file($fname, $index) ; } )
1654             ->pack(qw/-side left -fill x -expand 1/);
1655              
1656             my $lowerFrame = $frm->Frame()->pack(-side => 'top', '-fill' => 'x', '-expand' => 1) ;
1657              
1658             $lowerFrame->Label(-text => "Cond:")->pack(-side => 'left') ;
1659              
1660             $btn = $lowerFrame->Entry(-textvariable => \$brkPt->{'expr'})
1661             ->pack(qw/-side left -fill x -expand 1/);
1662              
1663             my $row;
1664             $row = pop @{$self->{'brkPtSlots'}} or $row = $self->{'brkPtCnt'} ;
1665              
1666             $self->{'breakpts_table_data'}->{"$fname:$index"}->{'frm'} = $frm ;
1667             $self->{'breakpts_table_data'}->{"$fname:$index"}->{'row'} = $row ;
1668              
1669             #TODO $self->{'main_window'}->update;
1670              
1671             #TODO my $width = $frm->cget('-width') ;#TODO < Must be widget method
1672             #TODO if ( $width > $self->{'breakpts_table'}->width ) {
1673             #TODO $self->{'notebook'}->configure(-width => $width) ;
1674             #TODO }
1675              
1676             } # end of add_brkpt_to_brkpt_page
1677              
1678             sub remove_brkpt_from_brkpt_page {
1679             my($self, $fname, $idx) = @_ ;
1680              
1681             my $table = $self->{'breakpts_table'} ;
1682              
1683             # Delete the breakpoint control in the breakpoints window
1684              
1685             # TODO deleting means ->packForget, with {'row'} etc go away
1686             $table->windowDelete(($self->{'breakpts_table_data'}->{"$fname:$idx"}->{'row'}-1).',0' ) ; # delete?
1687              
1688             #
1689             # Add this now empty slot to the list of ones we have open
1690             #
1691              
1692             push @{$self->{'brkPtSlots'}}, $self->{'breakpts_table_data'}->{"$fname:$idx"}->{'row'} ;
1693              
1694             $self->{'brkPtSlots'} = [ sort { $b <=> $a } @{$self->{'brkPtSlots'}} ] ;
1695              
1696             delete $self->{'breakpts_table_data'}->{"$fname:$idx"} ;
1697              
1698             $self->{'brkPtCnt'} -= 1 ;
1699              
1700             } # end of remove_brkpt_from_brkpt_page
1701              
1702              
1703             #
1704             # Supporting the "Run To Here..." command
1705             #
1706             sub insertTempBreakpoint {
1707             my ($self, $fname, $index) = @_ ;
1708             my($offset) ;
1709             local(*dbline) = $main::{'_<' . $fname} ;
1710              
1711             $offset = $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ? 1 : 0 ;
1712              
1713             return if( &DB::getdbline($fname, $index + $offset) ) ; # we already have a breakpoint here
1714              
1715             &DB::setdbline($fname, $index + $offset, {'type' => 'temp', 'line' => $index, 'value' => 1 } ) ;
1716              
1717             } # end of insertTempBreakpoint
1718              
1719             sub reinsertBreakpoints {
1720             my ($self, $fname) = @_ ;
1721             my ($brkPt) ;
1722              
1723             foreach $brkPt ( &DB::getbreakpoints($fname) ) {
1724             #
1725             # Our breakpoints are indexed by line
1726             # therefore we can have 'gaps' where there
1727             # lines, but not breaks set for them.
1728             #
1729             next unless defined $brkPt ;
1730              
1731             $self->insertBreakpoint($fname, @$brkPt{'line', 'value', 'expr'}) if( $brkPt->{'type'} eq 'user' ) ;
1732             $self->insertTempBreakpoint($fname, $brkPt->{line}) if( $brkPt->{'type'} eq 'temp' ) ;
1733             } # end of reinsert loop
1734              
1735             } # end of reinsertBreakpoints
1736              
1737             sub removeBreakpointTags {
1738             my ($self, @brkPts) = @_ ;
1739             my($idx, $brkPt) ;
1740              
1741             foreach $brkPt (@brkPts) {
1742              
1743             $idx = $brkPt->{'line'} ;
1744              
1745             if ( $brkPt->{'value'} ) {
1746             $self->{'text'}->tagRemove("breaksetLine", "$idx.0", "$idx.$Devel::tcltkdb::linenumber_length") ;
1747             }
1748             else {
1749             $self->{'text'}->tagRemove("breakdisabledLine", "$idx.0", "$idx.$Devel::tcltkdb::linenumber_length") ;
1750             }
1751              
1752             $self->{'text'}->tagAdd("breakableLine", "$idx.0", "$idx.$Devel::tcltkdb::linenumber_length") ;
1753             }
1754             } # end of removeBreakpointTags
1755              
1756             #
1757             # Remove a breakpoint from the current window
1758             #
1759             sub removeBreakpoint {
1760             my ($self, $fname, @idx) = @_ ;
1761             my ($idx, $chkIdx, $i, $j, $info) ;
1762             my($offset) ;
1763             local(*dbline) = $main::{'_<' . $fname} ;
1764              
1765             $offset = $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ? 1 : 0 ;
1766              
1767             foreach $idx (@idx) { # end of removal loop
1768             next unless defined $idx ;
1769             my $brkPt = &DB::getdbline($fname, $idx + $offset) ;
1770             next unless $brkPt ; # if we do not have an entry
1771             &DB::cleardbline($fname, $idx + $offset) ;
1772              
1773             $self->remove_brkpt_from_brkpt_page($fname, $idx) ;
1774              
1775             next unless $brkPt->{fname} eq $self->{'current_file'} ; # if this isn't our current file there will be no controls
1776              
1777             # Delete the ext associated with the breakpoint expression (if any)
1778              
1779             $self->removeBreakpointTags($brkPt) ;
1780             } # end of remove loop
1781              
1782             return ;
1783             } # end of removeBreakpoint
1784              
1785             sub removeAllBreakpoints {
1786             my ($self, $fname) = @_ ;
1787              
1788             $self->removeBreakpoint($fname, &DB::getdblineindexes($fname)) ;
1789              
1790             } # end of removeAllBreakpoints
1791              
1792             #
1793             # Delete expressions prior to an update
1794             #
1795             sub deleteAllExprs {
1796             my ($self) = @_ ;
1797             my @c = $self->{data_list0}->[0]->itemChildrenRoot =~ /(\d+)/g;
1798             print STDERR "{{{@c;$#c}}}";
1799             $self->{data_list0}->[0]->itemDelete($_) for @c;
1800             } # end of deleteAllExprs
1801              
1802             sub EnterExpr {
1803             my ($self) = @_ ;
1804             my $str = $self->clear_entry_text() ;
1805             if( $str && $str !~ /^\s*$/ ) { # if there is an expression and it's more than white space
1806             $self->{'expr'} = $str ;
1807             $self->{'event'} = 'expr' ;
1808             }
1809             } # end of EnterExpr
1810              
1811             #
1812             #
1813             sub QuickExpr {
1814             my ($self) = @_ ;
1815              
1816             my $str = $self->{'quick_entry'}->get() ;
1817              
1818             if( $str && $str !~ /^\s*$/ ) { # if there is an expression and it's more than white space
1819             $self->{'qexpr'} = $str ;
1820             $self->{'event'} = 'qexpr' ;
1821             }
1822             } # end of QuickExpr
1823              
1824             sub deleteExpr {
1825             my ($self) = @_ ;
1826             my ($entry, @indexes) ;
1827             my @sList = $self->{'data_list'}->info('select'); # TBD TODO TBD
1828             my @sList0 = $self->{data_list0}->[0]->selectionGet;
1829              
1830             #
1831             # if we're deleteing a top level expression
1832             # we have to take it out of the list of expressions
1833             #
1834              
1835             foreach $entry ( @sList ) {
1836             next if ($entry =~ /\//) ; # goto next expression if we're not a top level ( expr/entry)
1837             my $i = 0 ;
1838             grep { push @indexes, $i if ($_->{'expr'} eq $entry) ; $i++ ; } @{$self->{'expr_list'}} ;
1839             } # end of check loop
1840              
1841             # now take out our list of indexes ;
1842              
1843             for (0..$#indexes) {
1844             splice @{$self->{'expr_list'}}, $indexes[$_] - $_, 1 ;
1845             }
1846              
1847             for( @sList ) {
1848             $self->{'data_list'}->delete('entry', $_) ;
1849             }
1850             } # end of deleteExpr
1851              
1852             ##
1853             ## Inserts an expression($theRef) into tk widget. If the expression
1854             ## is an array, blessed array, hash, or blessed hash(typical object), then this
1855             ## routine is called recursively, adding the members to the next level of heirarchy,
1856             ## prefixing array members with a [idx] and the hash members with the key name.
1857             ## This continues until the entire expression is decomposed to it's atomic constituents.
1858             ## Protection is given(with $reusedRefs) to ensure that 'circular' references within
1859             ## arrays or hashes(i.e. where a member of a array or hash contains a reference to a
1860             ## parent element within the heirarchy.
1861             ##
1862             #
1863             # Returns 1 if sucessfully added 0 if not
1864             #
1865             sub insertExpr {
1866             my($self, $reusedRefs, $theRef, $name, $depth, $el) = @_ ;
1867             my($type, $result, @circRefs, $t) ;
1868             local($^W) = 0 ; # spare us uncessary warnings about comparing strings with ==
1869             my ($tv, $tcol) = @{$self->{data_list0}};
1870              
1871             while( ref $theRef eq 'SCALAR' ) {
1872             $theRef = $$theRef ;
1873             }
1874              
1875             my $label = "" ;
1876             REF_CHECK: for( ; ; ) {
1877             push @circRefs, $theRef ;
1878             $type = ref $theRef ;
1879             last unless ($type eq "REF") ;
1880             $theRef = $$theRef ; # dref again
1881              
1882             $label .= "\\" ; # append a
1883             if( grep $_ == $theRef, @circRefs ) {
1884             $label .= "(circular)" ;
1885             last ;
1886             }
1887             }
1888              
1889             if( !$type || $type eq "" || $type eq "GLOB" || $type eq "CODE") {
1890             eval {
1891             $t = "$name = $label" . (defined $theRef?$theRef:"undef");
1892             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1893             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$t");
1894             };
1895             $self->DoAlert($@), return 0 if $@ ;
1896             return 1 ;
1897             }
1898              
1899             if( $type eq 'ARRAY' or "$theRef" =~ /ARRAY/ ) {
1900             my $idx = 0 ;
1901             eval {
1902             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1903             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = $theRef");
1904             } ;
1905             if( $@ ) {
1906             $self->DoAlert($@) ;
1907             return 0 ;
1908             }
1909             $result = 1 ;
1910             for my $r ( @$theRef ) {
1911              
1912             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
1913             eval {
1914             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1915             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"[$idx] = $r REUSED ADDR");
1916             } ;
1917             $self->DoAlert($@) if( $@ ) ;
1918             next ;
1919             }
1920              
1921             push @$reusedRefs, $r ;
1922             $result = $self->insertExpr($reusedRefs, $r, "[$idx]", $depth-1, $el) unless $depth == 0 ;
1923             pop @$reusedRefs ;
1924              
1925             return 0 unless $result ;
1926             $idx += 1 ;
1927             }
1928             return 1 ;
1929             } # end of array case
1930              
1931             if ("$theRef" !~ /HASH\050\060x[\da-f]*\051/) {
1932             eval {
1933             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1934             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = $theRef");
1935             };
1936             if( $@ ) {
1937             $self->DoAlert($@) ;
1938             return 0 ;
1939             }
1940             return 1 ;
1941             }
1942             #
1943             # Anything else at this point is
1944             # either a 'HASH' or an object
1945             # of some kind.
1946             #
1947             my $idx = 0 ;
1948             my @theKeys = sort keys %$theRef;
1949             $el = $tv->itemCreate(-parent=>$el);
1950             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = " . "$theRef");
1951             $result = 1 ;
1952              
1953             for my $r ( @$theRef{@theKeys} ) { # slice out the values with the sorted list
1954              
1955             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
1956             eval {
1957             $el = $tv->itemCreate(-parent=>$el);
1958             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$theKeys[$idx++] = $r REUSED ADDR");
1959             } ;
1960             print "bad path $@\n" if( $@ ) ;
1961             next ;
1962             }
1963              
1964             push @$reusedRefs, $r;
1965              
1966             $result = $self->insertExpr($reusedRefs, # recursion protection
1967             $r, # reference whose value is displayed
1968             $theKeys[$idx], # name
1969             $depth-1, # remaining expansion depth
1970             $el)
1971             unless $depth == 0 ;
1972              
1973             pop @$reusedRefs ;
1974              
1975             return 0 unless $result ;
1976             $idx += 1 ;
1977             } # end of ref add loop
1978              
1979             return 1 ;
1980             } # end of insertExpr
1981              
1982             #
1983             # We're setting the line where we are stopped.
1984             # Create a tag for this and set it as bold.
1985             #
1986             sub set_line {
1987             my ($self, $lineno) = @_ ;
1988             my $text = $self->{'text'} ;
1989              
1990             return if( $lineno <= 0 ) ;
1991              
1992             if( $self->{current_line} > 0 ) {
1993             $text->tagRemove('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
1994             }
1995             $self->{current_line} = $lineno - $self->{'line_offset'} ;
1996             $text->tagAdd('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
1997              
1998             $self->{'text'}->see("$self->{current_line}.0 linestart") ;
1999             } # end of set_line
2000              
2001             #
2002             # Set the file that is in the code window.
2003             #
2004             # $fname the 'new' file to view
2005             # $line the line number we're at
2006             # $brkPts any breakpoints that may have been set in this file
2007             #
2008              
2009             sub set_file {
2010             my ($self, $fname, $line) = @_ ;
2011             my ($lineStr, $offset, $text, @text);
2012              
2013             return unless $fname ; # we're getting an undef here on 'Restart...'
2014              
2015             local(*dbline) = $main::{'_<' . $fname};
2016              
2017             #
2018             # with the #! /usr/bin/perl -d:tcltkdb at the header of the file
2019             # we've found that with various combinations of other options the
2020             # files haven't come in at the right offsets
2021             #
2022             $offset = 0 ;
2023             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ;
2024             $self->{'line_offset'} = $offset ;
2025              
2026             $text = $self->{'text'} ;
2027              
2028             if( $fname eq $self->{current_file} ) {
2029             $self->set_line($line) ;
2030             return ;
2031             } ;
2032              
2033             $self->{main_window}->configure('-title' => $fname) ;
2034              
2035             # Erase any existing text
2036             $text->delete('1.0','end');
2037              
2038             #
2039             # This is the tightest loop we have in the ptkdb code.
2040             # It is here where performance is the most critical.
2041             # The map block formats perl code for display. Since
2042             # the file could be potentially large, we will try
2043             # to make this loop as thin as possible.
2044             #
2045              
2046             local($^W) = 0 ; # spares us useless warnings under -w when checking $dbline[$_] != 0
2047              
2048             my $noCode = ($#dbline - ($offset + 1)) < 0 ;
2049              
2050             my $i0 = "0" x $Devel::tcltkdb::linenumber_length;
2051             $text->_insertEnd(map {
2052             #$lineStr .= "\n" unless /\n$/; # append a \n if there isn't one already
2053             ($i0++, ($_==0?'nonbreakableLine':'breakableLine'), " $_", 'code') # a string,tag pair for text insert
2054              
2055             } @dbline[$offset+1 .. $#dbline] ) unless $noCode;
2056              
2057             #
2058             # Reinsert breakpoints (if info provided)
2059             #
2060              
2061             $self->set_line($line);
2062             $self->{current_file} = $fname;
2063             return $self->reinsertBreakpoints($fname);
2064             } # end of set_file
2065              
2066             #
2067             # Get the current line that the insert cursor is in
2068             #
2069             sub get_lineno {
2070             my ($self) = @_ ;
2071              
2072             my $info = $self->{'text'}->index('insert'); # get the location for the insertion point
2073             $info =~ s/\..*$/\.0/ ;
2074              
2075             return int $info ;
2076             } # end of get_lineno
2077              
2078             sub DoGoto {
2079             my ($self, $entry) = @_ ;
2080              
2081             my $txt = $entry->get() ;
2082              
2083             $txt =~ s/(\d*).*/$1/; # take the first blob of digits
2084             if( $txt eq "" ) {
2085             print "invalid text range\n";
2086             return;
2087             }
2088              
2089             $self->{'text'}->see("$txt.0") ;
2090              
2091             $entry->_selectionRange(0, 'end');
2092             } # end of DoGoto
2093              
2094             sub GotoLine {
2095             my ($self) = @_ ;
2096              
2097             if( $self->{goto_window} ) {
2098             $self->{goto_window}->raise() ;
2099             $self->{goto_text}->focus() ;
2100             return ;
2101             }
2102              
2103             #
2104             # Construct a dialog that has an
2105             # entry field, okay and cancel buttons
2106             #
2107             my $okaySub = sub { $self->DoGoto($self->{'goto_text'}) } ;
2108              
2109             my $topLevel = $self->{main_window}->Toplevel(-title => "Goto Line?", -overanchor => 'cursor') ;
2110              
2111             $self->{goto_text} = $topLevel->Entry()->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2112             $self->{goto_text}->bind('', $okaySub) ; # make a CR do the same thing as pressing an okay
2113             $self->{goto_text}->focus();
2114              
2115             $topLevel->Button( -text => "Okay", -command => $okaySub, @Devel::tcltkdb::button_font,
2116             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2117              
2118             #
2119             # Subroutone called when the 'Dismiss' button is pushed.
2120             my $dismissSub = sub {
2121             delete $self->{goto_text} ;
2122             $self->{goto_window}->destroy;
2123             delete $self->{goto_window} ; # remove the entry from our hash so we won't
2124             } ;
2125              
2126             $topLevel->Button( -text => "Dismiss", @Devel::tcltkdb::button_font,
2127             -command => $dismissSub )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2128              
2129             $topLevel->protocol('WM_DELETE_WINDOW', sub { $topLevel->destroy; } ) ;
2130             $self->{goto_window} = $topLevel;
2131              
2132             } # end of GotoLine
2133              
2134              
2135             #
2136             # Subroutine called when the 'okay' button is pressed
2137             #
2138             sub FindSearch {
2139             my ($self, $entry, $btn, $regExp) = @_ ;
2140             my (@switches, $result) ;
2141             my $txt = $entry->get() ;
2142              
2143             return if $txt eq "" ;
2144              
2145             push @switches, "-forward" if $self->{fwdOrBack} eq "forward" ;
2146             push @switches, "-backward" if $self->{fwdOrBack} eq "backward" ;
2147              
2148             if( $regExp ) {
2149             push @switches, "-regexp" ;
2150             }
2151             else {
2152             push @switches, "-nocase" ; # if we're not doing regex we may as well do caseless search
2153             }
2154              
2155             $result = $self->{'text'}->search(@switches, $txt, $self->{search_start}) ;
2156              
2157             # untag the previously found text
2158              
2159             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2160              
2161             if( !$result || $result eq "" ) {
2162             # No Text was found
2163             $btn->flash() ;
2164             $btn->bell() ;
2165              
2166             delete $self->{search_tag} ;
2167             $self->{'search_start'} = "0.0" ;
2168             }
2169             else { # text found
2170             $self->{'text'}->see($result) ;
2171             # set the insertion of the text as well
2172             $self->{'text'}->markSet('insert' => $result) ;
2173             my $len = length $txt;
2174              
2175             if( $self->{fwdOrBack} ) {
2176             $self->{search_start} = "$result +$len chars" ;
2177             $self->{search_tag} = [ $result, $self->{search_start} ] ;
2178             }
2179             else {
2180             # backwards search
2181             $self->{search_start} = "$result -$len chars" ;
2182             $self->{search_tag} = [ $result, "$result +$len chars" ] ;
2183             }
2184              
2185             # tag the newly found text
2186              
2187             $self->{'text'}->tagAdd('search_tag', @{$self->{search_tag}}) ;
2188             } # end of text found
2189              
2190             $entry->_selectionRange(0, 'end');
2191              
2192             } # end of FindSearch
2193              
2194              
2195             #
2196             # Support for the Find Text... Menu command
2197             #
2198             sub FindText {
2199             my ($self) = @_ ;
2200             my ($okayBtn);
2201              
2202             #
2203             # if we already have the Find Text Window open don't bother openning
2204             # another, bring the existing one to the front.
2205             if( $self->{find_window} ) {
2206             $self->{find_window}->raise();
2207             return;
2208             }
2209              
2210             $self->{search_start} = $self->{'text'}->index('insert') if( $self->{search_start} eq "" ) ;
2211              
2212             #
2213             # Subroutine called when the 'Dismiss' button is pushed.
2214             my $dismissSub = sub {
2215             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2216             $self->{search_start} = "" ;
2217             $self->{find_window}->destroy;
2218             delete $self->{search_tag} ;
2219             delete $self->{find_window} ;
2220             };
2221              
2222             #
2223             # Construct a dialog that has an entry field, forward, backward, regex option, okay and cancel buttons
2224             #
2225             my $top = $self->{main_window}->Toplevel(-title => "Find Text?");
2226              
2227             my $we = $top->Entry()->pack(qw/-side top -fill both -expand 1/);
2228              
2229             my $frm = $top->Frame()->pack(qw/-side top -fill both -expand 1/);
2230              
2231             $self->{fwdOrBack} = 'forward';
2232             $frm->Radiobutton(-text => "Forward", -value => 1, -variable => \$self->{fwdOrBack})
2233             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2234             $frm->Radiobutton(-text => "Backward", -value => 0, -variable => \$self->{fwdOrBack})
2235             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2236              
2237             my $regExp = 0 ;
2238             $frm->Checkbutton(-text => "RegExp", -variable => \$regExp)
2239             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2240              
2241             # Okay and dismiss buttons
2242             $okayBtn = $top->Button( -text => "Okay", -command => sub { $self->FindSearch($we, $okayBtn, $regExp) ; },
2243             @Devel::tcltkdb::button_font,
2244             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2245              
2246             $we->bind('', sub { $self->FindSearch($we, $okayBtn, $regExp) ; }) ;
2247              
2248             $top->Button( -text => "Dismiss", @Devel::tcltkdb::button_font,
2249             -command => $dismissSub)->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2250              
2251             $top->protocol('WM_DELETE_WINDOW', $dismissSub) ;
2252             $we->focus();
2253             $self->{find_window} = $top;
2254              
2255             } # end of FindText
2256              
2257             sub main_loop {
2258             my ($self) = @_;
2259             my $evt;
2260              
2261             SWITCH: for ($self->{'event'} = 'null' ; $DB::window->{main_window}; $self->{'event'} = undef ) {
2262              
2263             $DB::window->{main_window}->update;
2264             next unless $self->{'event'} ;
2265              
2266             $evt = $self->{'event'} ;
2267             $evt =~ /step/ && do { last SWITCH ; } ;
2268             $evt =~ /null/ && do { next SWITCH ; } ;
2269             $evt =~ /run/ && do { last SWITCH ; } ;
2270             $evt =~ /quit/ && do { $self->DoQuit ; } ;
2271             $evt =~ /expr/ && do { return $evt ; } ; # adds an expression to our expression window
2272             $evt =~ /qexpr/ && do { return $evt ; } ; # does a 'quick' expression
2273             $evt =~ /update/ && do { return $evt ; } ; # forces an update on our expression window
2274             $evt =~ /reeval/ && do { return $evt ; } ; # updated the open expression eval window
2275             $evt =~ /balloon_eval/ && do { return $evt } ;
2276             } # end of switch block
2277             return $evt ;
2278             } # end of main_loop
2279              
2280             #
2281             # $subStackRef A reference to the current subroutine stack
2282             #
2283              
2284             sub goto_sub_from_stack {
2285             my ($self, $f, $lineno) = @_ ;
2286             $self->set_file($f, $lineno) ;
2287             } # end of goto_sub_from_stack ;
2288              
2289             sub refresh_stack_menu {
2290             my ($self) = @_ ;
2291             my ($name, $i, $sub_offset, $subStack) ;
2292              
2293             #
2294             # CAUTION: In the effort to 'rationalize' the code
2295             # are moving some of this function down from DB::DB
2296             # to here. $sub_offset represents how far 'down'
2297             # we are from DB::DB. The $DB::subroutine_depth is
2298             # tracked in such a way that while we are 'in' the debugger
2299             # it will not be incremented, and thus represents the stack depth
2300             # of the target program.
2301             #
2302             $sub_offset = 1 ;
2303             $subStack = [] ;
2304              
2305             # clear existing entries
2306              
2307             for( $i = 0 ; $i <= $DB::subroutine_depth ; $i++ ) {
2308             my ($package, $filename, $line, $subName) = caller $i+$sub_offset ;
2309             last if !$subName ;
2310             push @$subStack, { 'name' => $subName, 'pck' => $package, 'filename' => $filename, 'line' => $line } ;
2311             }
2312              
2313             $self->{stack_menu}->menu->delete(0, 'last') ; # delete existing menu items
2314              
2315             for( $i = 0 ; $subStack->[$i] ; $i++ ) {
2316              
2317             my $str = defined $subStack->[$i+1] ? "$subStack->[$i+1]->{name}" : "MAIN" ;
2318              
2319             my ($f, $line) = ($subStack->[$i]->{filename}, $subStack->[$i]->{line}) ; # make copies of the values for use in 'sub'
2320             $self->{stack_menu}->command(-label => $str, -command => sub { $self->goto_sub_from_stack($f, $line) ; } ) ;
2321             }
2322             } # end of refresh_stack_menu
2323              
2324             no strict ;
2325              
2326             sub get_state {
2327             my ($self, $fname) = @_ ;
2328             my ($val) ;
2329             local($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
2330              
2331             do "$fname" ;
2332              
2333             if( $@ ) {
2334             $self->DoAlert($@) ;
2335             return ( undef ) x 4 ; # return a list of 4 undefined values
2336             }
2337              
2338             return ($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
2339             } # end of get_state
2340              
2341             use strict ;
2342              
2343             sub restoreStateFile {
2344             my ($self, $fname) = @_ ;
2345             local(*F) ;
2346             my ($saveCurFile, $s, @n, $n) ;
2347              
2348             if (!(-e $fname && -r $fname)) {
2349             $self->DoAlert("$fname does not exist") ;
2350             return ;
2351             }
2352              
2353             my ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $self->get_state($fname) ;
2354             my ($f, $brks) ;
2355              
2356             return unless defined $files || defined $expr_list ;
2357              
2358             &DB::restore_breakpoints_from_save($files) ;
2359              
2360             #
2361             # This should force the breakpoints to be restored
2362             #
2363             $saveCurFile = $self->{current_file} ;
2364              
2365             @$self{ 'current_file', 'expr_list', 'eval_saved_text' } =
2366             ( "" , $expr_list, $eval_saved_text) ;
2367              
2368             $self->set_file($saveCurFile, $self->{current_line}) ;
2369              
2370             $self->{'event'} = 'update' ;
2371              
2372             if ( $main_win_geometry && $self->{'main_window'} ) {
2373             # restore the height and width of the window
2374             $self->{main_window}->geometry( $main_win_geometry ) ;
2375             }
2376             } # end of retstoreState
2377              
2378             sub updateEvalWindow {
2379             my ($self, @result) = @_ ;
2380             my ($leng, $str) = (0,'');
2381              
2382             for (@result) {
2383             if( $self->{hexdump_evals} ) {
2384             # eventually put hex dumper code in here
2385             $self->{eval_results}->insert('end', hexDump($_)) ;
2386             } else {
2387             my $d = Data::Dumper->new([$_]);
2388             $d->Indent(2);
2389             $d->Terse(1);
2390             $str = $d->Dump($_);
2391             }
2392             $leng += length $str ;
2393             $self->{eval_results}->insert('end', $str) ;
2394             }
2395             } # end of updateEvalWindow
2396              
2397             ##
2398             ## converts non printable chars to '.' for a string
2399             ##
2400             sub printablestr {
2401             return join "", map { (ord($_) >= 32 && ord($_) < 127) ? $_ : '.' } split //, $_[0] ;
2402             }
2403              
2404             ##
2405             ## hex dump utility function
2406             ##
2407             sub hexDump {
2408             my @retList;
2409             my $width = 8;
2410             my $offset = 0;
2411              
2412             for (@_) {
2413             my $str = '';
2414             my $len = length $_ ;
2415              
2416             while($len) {
2417             my $n = $len >= $width ? $width : $len ;
2418              
2419             my $fmt = "\n%04X " . ("%02X " x $n ) . ( ' ' x ($width - $n) ) . " %s" ;
2420             my @elems = map ord, split //, (substr $_, $offset, $n) ;
2421             $str .= sprintf($fmt, $offset, @elems, printablestr(substr $_, $offset, $n)) ;
2422             $offset += $width;
2423              
2424             $len -= $n;
2425             } # while
2426              
2427             push @retList, $str;
2428             } # for
2429              
2430             return $retList[0] unless wantarray ;
2431             return @retList ;
2432             } # end of hd
2433              
2434              
2435             sub setupEvalWindow {
2436             my($self) = @_;
2437             $self->{eval_window}->focus(), return if exists $self->{eval_window} ; # already running this window?
2438              
2439             my $top = $self->{main_window}->Toplevel(-title => "Evaluate Expressions...");
2440             $self->{eval_window} = $top;
2441             $self->{eval_text} = $top->Scrolled('Text',
2442             @Devel::tcltkdb::eval_text_font,
2443             -width => 50,
2444             -height => 10,
2445             -wrap => "none",
2446             )->pack(qw/-side top -fill both -expand 1/);
2447              
2448             $self->{eval_text}->insert('end', $self->{eval_saved_text}) if exists $self->{eval_saved_text} && defined $self->{eval_saved_text};
2449              
2450             $top->Label(-text => "Results:")->pack(qw/-side top -fill both -expand n/);
2451              
2452             $self->{eval_results} = $top->Scrolled('Text',
2453             -width => 50,
2454             -height => 10,
2455             -wrap => "none",
2456             @Devel::tcltkdb::eval_text_font
2457             )->pack(qw/-side top -fill both -expand 1/);
2458              
2459             my $btn = $top->Button(-text => 'Eval...', -command => sub { $DB::window->{event} = 'reeval' ; }
2460             )->pack(-side => 'left', -fill => 'x', -expand => 1);
2461              
2462             my $dismissSub = sub {
2463             $self->{eval_saved_text} = $self->{eval_text}->get('0.0', 'end') ;
2464             $self->{eval_window}->destroy ;
2465             delete $self->{eval_window} ;
2466             };
2467              
2468             $top->protocol('WM_DELETE_WINDOW', $dismissSub ) ;
2469              
2470             $top->Button(-text => 'Clear Eval', -command => sub { $self->{eval_text}->delete('0.0', 'end') }
2471             )->pack(-side => 'left', -fill => 'x', -expand => 1);
2472              
2473             $top->Button(-text => 'Clear Results', -command => sub { $self->{eval_results}->delete('0.0', 'end') }
2474             )->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2475              
2476             $top->Button(-text => 'Dismiss', -command => $dismissSub)->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2477             $top->Checkbutton(-text => 'Hex', -variable => \$self->{hexdump_evals})->pack(-side => 'left') ;
2478              
2479             } # end of setupEvalWindow ;
2480              
2481             sub filterBreakPts {
2482             my ($breakPtsListRef, $fname) = @_ ;
2483             my $dbline = $main::{'_<' . $fname}; # breakable lines
2484             local($^W) = 0 ;
2485             #
2486             # Go through the list of breaks and take out any that
2487             # are no longer breakable
2488             #
2489              
2490             for( @$breakPtsListRef ) {
2491             next unless defined $_ ;
2492              
2493             next if $dbline->[$_->{'line'}] != 0 ; # still breakable
2494              
2495             $_ = undef ;
2496             }
2497             } # end of filterBreakPts
2498              
2499             sub DoAbout {
2500             my $self = shift ;
2501             my $str = <<"__STR__" ;
2502             tcltkdb $tcltkdb::VERSION
2503             Copyright 1998,2003 by Andrew E. Page, 2010,2011 Vadim Konovalov.
2504              
2505             This program is free software; you can redistribute it and/or modify
2506             it under the terms of either:
2507              
2508             a) the GNU General Public License as published by the Free
2509             Software Foundation; either version 1, or (at your option) any
2510             later version, or
2511              
2512             b) the "Artistic License" which comes with this Kit.
2513              
2514             This program is distributed in the hope that it will be useful,
2515             but WITHOUT ANY WARRANTY; without even the implied warranty of
2516             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either
2517             the GNU General Public License or the Artistic License for more details.
2518              
2519             OS $^O
2520             Tcl/Tk Version $Tcl::Tk::TK_VERSION
2521             Tcl::Tk Version $Tcl::Tk::VERSION
2522             Perl Version $]
2523             __STR__
2524              
2525             $self->DoAlert($str, "About ptkdb") ;
2526             } # end of DoAbout
2527              
2528             #
2529             # return 1 if succesfully set,
2530             # return 0 if otherwise
2531             #
2532             sub SetBreakPoint {
2533             my ($self, $isTemp) = @_ ;
2534             my $dbw = $DB::window ;
2535             my $lineno = $dbw->get_lineno();
2536             my $expr = $dbw->clear_entry_text() ;
2537             local($^W) = 0 ;
2538              
2539             if( !&DB::checkdbline($DB::window->{current_file}, $lineno + $self->{'line_offset'}) ) {
2540             $dbw->DoAlert("line $lineno in $DB::window->{current_file} is not breakable") ;
2541             return 0 ;
2542             }
2543              
2544             if( !$isTemp ) {
2545             $dbw->insertBreakpoint($DB::window->{current_file}, $lineno, 1, $expr) ;
2546             return 1 ;
2547             }
2548             else {
2549             $dbw->insertTempBreakpoint($DB::window->{current_file}, $lineno) ;
2550             return 1 ;
2551             }
2552              
2553             return 0 ;
2554             } # end of SetBreakPoint
2555              
2556             sub UnsetBreakPoint {
2557             my ($self) = @_ ;
2558             my $lineno = $self->get_lineno();
2559              
2560             $self->removeBreakpoint($DB::window->{current_file}, $lineno) ;
2561             } # end of UnsetBreakPoint
2562              
2563             sub balloon_post {
2564             my $self = $DB::window ;
2565             my $txt = $DB::window->{'text'} ;
2566              
2567             return 0 if ($self->{'expr_ballon_msg'} eq "") || ($self->{'balloon_expr'} eq "") ; # don't post for an empty string
2568              
2569             return $self->{'balloon_coord'} ;
2570             }
2571              
2572             sub balloon_motion {
2573             my ($txt, $x, $y) = @_ ;
2574             my ($offset_x, $offset_y) = ($x + 4, $y + 4) ;
2575             my $self = $DB::window ;
2576             my $txt2 = $self->{'text'} ;
2577             my $data ;
2578              
2579             $self->{'balloon_coord'} = "$offset_x,$offset_y" ;
2580              
2581             $x -= $txt->rootx ;
2582             $y -= $txt->rooty ;
2583             #
2584             # Post an event that will cause us to put up a popup
2585             #
2586              
2587             if ($txt2->_tagRangesSel) { # check to see if 'sel' tag exists (return undef value)
2588             $data = $txt2->get("sel.first", "sel.last") ; # get the text between the 'first' and 'last' point of the sel (selection) tag
2589             }
2590             else {
2591             $data = $DB::window->retrieve_text_expr($x, $y) ;
2592             }
2593              
2594             if( !$data ) {
2595             $self->{'balloon_expr'} = "" ;
2596             return 0 ;
2597             }
2598              
2599             return 0 if ($data eq $self->{'balloon_expr'}) ; # nevermind if it's the same expression
2600              
2601             $self->{'event'} = 'balloon_eval' ;
2602             $self->{'balloon_expr'} = $data ;
2603              
2604             return 1 ; # ballon will be canceled and a new one put up(maybe)
2605             } # end of balloon_motion
2606              
2607             sub retrieve_text_expr {
2608             my($self, $x, $y) = @_ ;
2609             my $txt = $self->{'text'} ;
2610              
2611             my ($idx, $col) = $txt->index("\@$x,$y") =~ /^(\d*)\.(\d*)$/;
2612              
2613             my $offset = $Devel::tcltkdb::linenumber_length + 1 ; # line number text + 1 space
2614              
2615             return undef if $col < $offset ; # no posting
2616              
2617             $col -= $offset ;
2618              
2619             local(*dbline) = $main::{'_<' . $self->{current_file}} ;
2620              
2621             return undef if( !defined $dbline[$idx] || $dbline[$idx] == 0 ) ; # no executable text, no real variable(?)
2622              
2623             my $data = $dbline[$idx] ;
2624              
2625             # if we're sitting over white space, leave
2626             my $len = length $data ;
2627             return unless $data && $col && $len > 0 ;
2628              
2629             return if substr($data, $col, 1) =~ /\s/ ;
2630              
2631             # walk backwards till we find some whitespace
2632              
2633             $col = $len if $len < $col ;
2634             while( --$col >= 0 ) {
2635             last if substr($data, $col, 1) =~ /[\s\$\@\%]/ ;
2636             }
2637              
2638             substr($data, $col) =~ /^([\$\@\%]\w+)/ ;
2639              
2640             return $1 ;
2641             }
2642              
2643             #
2644             # after DB::eval get's us a result
2645             #
2646             sub code_motion_eval {
2647             my ($self, @result) = @_;
2648             my $d = new Data::Dumper([]);
2649             $d->Terse(1);
2650             $d->Indent(2);
2651             $d->Values( [ $#result == 0 ? @result : \@result ]);
2652             my $str = $d->Dump();
2653             chomp($str) ;
2654             # Cut the string down to 1024 characters to keep from overloading the balloon window
2655             $self->{'expr_ballon_msg'} = "$self->{'balloon_expr'} = " . substr $str, 0, 1024 ;
2656             } # end of code motion eval
2657              
2658             #
2659             # Subroutine called when we enter DB::DB()
2660             # In other words when the target script 'stops'
2661             # in the Debugger
2662             #
2663             sub EnterActions {
2664             my($self) = @_ ;
2665              
2666             # $self->{'main_window'}->Unbusy() ;
2667             }
2668              
2669             #
2670             # Subroutine called when we return from DB::DB()
2671             # When the target script resumes.
2672             #
2673             sub LeaveActions {
2674             my($self) = @_ ;
2675              
2676             # $self->{'main_window'}->Busy() ;
2677             }
2678              
2679              
2680             sub BEGIN {
2681             $Devel::tcltkdb::scriptName = $0 ;
2682             @Devel::tcltkdb::script_args = @ARGV ; # copy args
2683             }
2684              
2685             ##
2686             ## Save the ptkdb state file and restart the debugger
2687             ##
2688             sub DoRestart {
2689             my($fname) ;
2690              
2691             $fname = $ENV{'TMP'} || $ENV{'TMPDIR'} || $ENV{'TMP_DIR'} || $ENV{'TEMP'} || $ENV{'HOME'} ;
2692             $fname .= '/' if $fname ;
2693             $fname = "" unless $fname ;
2694              
2695             $fname .= "ptkdb_restart_state$$" ;
2696              
2697             # print "saving temp state file $fname\n" ;
2698              
2699             &DB::save_state_file($fname) ;
2700              
2701             $ENV{'PTKDB_RESTART_STATE_FILE'} = $fname ;
2702              
2703             ##
2704             ## build up the command to do the restart
2705             ##
2706              
2707             $fname = "perl -w -d:tcltkdb $Devel::tcltkdb::scriptName @Devel::tcltkdb::script_args" ;
2708              
2709             # print "$$ doing a restart with $fname\n" ;
2710              
2711             exec $fname ;
2712              
2713             } # end of DoRestart
2714              
2715             ##
2716             ## Enables/Disables the feature where we stop
2717             ## if we've encountered a perl warning such as:
2718             ## "Use of uninitialized value at undef_warn.pl line N"
2719             ##
2720              
2721             sub stop_on_warning_cb {
2722             &$DB::tcltkdb::warn_sig_save() if $DB::tcltkdb::warn_sig_save ; # call any previously registered warning
2723             $DB::window->DoAlert(@_) ;
2724             $DB::single = 1 ; # forces debugger to stop next time
2725             }
2726              
2727             sub set_stop_on_warning {
2728              
2729             if( $DB::tcltkdb::stop_on_warning ) {
2730              
2731             return if $DB::tcltkdb::warn_sig_save == \&stop_on_warning_cb ; # prevents recursion
2732              
2733             $DB::tcltkdb::warn_sig_save = $SIG{'__WARN__'} if $SIG{'__WARN__'} ;
2734             $SIG{'__WARN__'} = \&stop_on_warning_cb ;
2735             }
2736             else {
2737             ##
2738             ## Restore any previous warning signal
2739             ##
2740             local($^W) = 0 ;
2741             $SIG{'__WARN__'} = $DB::tcltkdb::warn_sig_save ;
2742             }
2743             } # end of set_stop_on_warning
2744              
2745             # end of Devel::tcltkdb
2746              
2747             package DB;
2748              
2749             use vars '$VERSION';
2750             use vars '@dbline', '%dbline';
2751              
2752             $VERSION = '2.0';
2753             $DB::window->{current_file} = "" ;
2754              
2755             #
2756             # Here's the clue...
2757             # eval only seems to eval the context of
2758             # the executing script while in the DB
2759             # package. When we had updateExprs in the Devel::tcltkdb
2760             # package eval would turn up an undef result.
2761             #
2762              
2763             sub updateExprs {
2764             my ($package) = @_ ;
2765             #
2766             # Update expressions
2767             #
2768             $DB::window->deleteAllExprs();
2769              
2770             foreach my $expr (@{$DB::window->{'expr_list'}}) {
2771             next if length $expr == 0 ;
2772              
2773             my @result = &DB::dbeval($package, $expr->{'expr'}) ;
2774              
2775             my $r = (@result==1?$result[0]:\@result);
2776             $DB::window->insertExpr([$r], $r, $expr->{'expr'}, $expr->{'depth'},'root');
2777             }
2778             } # end of updateExprs
2779              
2780             #no strict ; # turning strict off (shame shame) because we keep getting errrs for the local(*dbline)
2781              
2782             #
2783             # returns true if line is breakable
2784             #
2785             sub checkdbline($$) {
2786             my ($fname, $lineno) = @_ ;
2787              
2788             return 0 unless $fname; # we're getting an undef here on 'Restart...'
2789              
2790             local($^W) = 0 ; # spares us warnings under -w
2791             local(*dbline) = $main::{'_<' . $fname} ;
2792              
2793             my $flag = $dbline[$lineno] != 0 ;
2794              
2795             return $flag;
2796              
2797             } # end of checkdbline
2798              
2799             #
2800             # sets a breakpoint 'through' a magic
2801             # variable that perl is able to interpert
2802             #
2803             sub setdbline($$$) {
2804             my ($fname, $lineno, $value) = @_ ;
2805             local(*dbline) = $main::{'_<' . $fname};
2806              
2807             $dbline{$lineno} = $value ;
2808             } # end of setdbline
2809              
2810             sub getdbline($$) {
2811             my ($fname, $lineno) = @_ ;
2812             local(*dbline) = $main::{'_<' . $fname};
2813             return $dbline{$lineno} ;
2814             } # end of getdbline
2815              
2816             sub getdbtextline {
2817             my ($fname, $lineno) = @_ ;
2818             local(*dbline) = $main::{'_<' . $fname};
2819             return $dbline[$lineno] ;
2820             } # end of getdbline
2821              
2822              
2823             sub cleardbline($$;&) {
2824             my ($fname, $lineno, $clearsub) = @_ ;
2825             local(*dbline) = $main::{'_<' . $fname};
2826             my $value ; # just in case we want it for something
2827              
2828             $value = $dbline{$lineno} ;
2829             delete $dbline{$lineno} ;
2830             &$clearsub($value) if $value && $clearsub ;
2831              
2832             return $value ;
2833             } # end of cleardbline
2834              
2835             sub clearalldblines(;&) {
2836             my ($clearsub) = @_ ;
2837             my ($key, $value, $brkPt, $dbkey) ;
2838             local(*dbline) ;
2839              
2840             while ( ($key, $value) = each %main:: ) { # key loop
2841             next unless $key =~ /^_
2842             *dbline = $value ;
2843              
2844             foreach $dbkey (keys %dbline) {
2845             $brkPt = $dbline{$dbkey} ;
2846             delete $dbline{$dbkey} ;
2847             next unless $brkPt && $clearsub ;
2848             &$clearsub($brkPt) ; # if specificed, call the sub routine to clear the breakpoint
2849             }
2850              
2851             } # end of key loop
2852              
2853             } # end of clearalldblines
2854              
2855             sub getdblineindexes {
2856             my ($fname) = @_ ;
2857             local(*dbline) = $main::{'_<' . $fname} ;
2858             return keys %dbline ;
2859             } # end of getdblineindexes
2860              
2861             sub getbreakpoints {
2862             my (@fnames) = @_;
2863             my @retList;
2864              
2865             for my $fname (@fnames) {
2866             next unless $main::{'_<' . $fname};
2867             local(*dbline) = $main::{'_<' . $fname};
2868             push @retList, values %dbline;
2869             }
2870             return @retList;
2871             } # end of getbreakpoints
2872              
2873             #
2874             # Construct a hash of the files that have breakpoints to save
2875             #
2876             sub breakpoints_to_save {
2877             my (@breaks);
2878             my $brkList = {};
2879              
2880             for my $file ( keys %main:: ) { # file loop
2881             next unless $file =~ /^_
2882             local(*dbline) = $main::{$file};
2883              
2884             next unless @breaks = values %dbline;
2885              
2886             $brkList->{$file} = [map { { %$_ } } @breaks]; # list of anon.hashes
2887             } # end of file loop
2888              
2889             return $brkList;
2890              
2891             } # end of breakpoints_to_save
2892              
2893             #
2894             # When we restore breakpoints from a state file
2895             # they've often 'moved' because the file has been editted.
2896             #
2897             # We search for the line starting with the original line number,
2898             # then we walk it back 20 lines, then with line right after the
2899             # orginal line number and walk forward 20 lines.
2900             #
2901             # NOTE: dbline is expected to be 'local' when called
2902             #
2903             sub fix_breakpoints {
2904             my(@brkPts) = @_ ;
2905             my (@retList) ;
2906             local($^W) = 0;
2907              
2908             my $nLines = scalar @dbline;
2909              
2910             for my $brkPt (@brkPts) {
2911              
2912             my $startLine = $brkPt->{'line'} > 20 ? $brkPt->{'line'} - 20 : 0 ;
2913             my $endLine = $brkPt->{'line'} < $nLines - 20 ? $brkPt->{'line'} + 20 : $nLines;
2914              
2915             for( (reverse $startLine..$brkPt->{'line'}), $brkPt->{'line'} + 1 .. $endLine ) {
2916             next unless $brkPt->{'text'} eq $dbline[$_] ;
2917             $brkPt->{'line'} = $_ ;
2918             push @retList, $brkPt ;
2919             last;
2920             }
2921             } # end of breakpoint list
2922              
2923             return @retList;
2924             } # end of fix_breakpoints
2925              
2926             #
2927             # Restore breakpoints saved above
2928             #
2929             sub restore_breakpoints_from_save {
2930             my ($brkList) = @_ ;
2931             my ($key, $list, $brkPt, @newList) ;
2932              
2933             while ( ($key, $list) = each %$brkList ) { # reinsert loop
2934             next unless exists $main::{$key} ;
2935             local(*dbline) = $main::{$key} ;
2936              
2937             my $offset = 0;
2938             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?tcltkdb/ ;
2939              
2940             @newList = fix_breakpoints(@$list) ;
2941              
2942             foreach $brkPt ( @newList ) {
2943             if( !&DB::checkdbline($key, $brkPt->{'line'} + $offset) ) {
2944             print "Breakpoint $key:$brkPt->{'line'} in config file is not breakable.\n" ;
2945             next ;
2946             }
2947             $dbline{$brkPt->{'line'}} = { %$brkPt } ; # make a fresh copy
2948             }
2949             } # end of reinsert loop
2950              
2951             } # end of restore_breakpoints_from_save ;
2952              
2953             sub dbint_handler {
2954             my($sigName) = @_;
2955             $DB::single = 1;
2956             print STDERR "signalled\n";
2957             } # end of dbint_handler
2958              
2959             #
2960             # Do first time initialization at the startup of DB::DB
2961             #
2962             my $isInitialized=0;
2963             sub Initialize {
2964             my ($fName) = @_ ;
2965             $isInitialized = 1;
2966              
2967             $DB::window = new Devel::tcltkdb;
2968              
2969             $DB::window->do_user_init_files();
2970              
2971             $DB::dbint_handler_save = $SIG{'INT'} unless $DB::sigint_disable ; # saves the old handler
2972             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ;
2973              
2974             # Save the file name we started up with
2975             $DB::startupFname = $fName ;
2976              
2977             # Check for a 'restart' file
2978              
2979             if( $ENV{'PTKDB_RESTART_STATE_FILE'} && -e $ENV{'PTKDB_RESTART_STATE_FILE'} ) {
2980             ##
2981             ## Restore expressions and breakpoints in state file
2982             ##
2983             $DB::window->restoreStateFile($ENV{'PTKDB_RESTART_STATE_FILE'}) ;
2984             unlink $ENV{'PTKDB_RESTART_STATE_FILE'} ; # delete state file
2985              
2986             # print "restoring state from $ENV{'PTKDB_RESTART_STATE_FILE'}\n" ;
2987              
2988             $ENV{'PTKDB_RESTART_STATE_FILE'} = "" ; # clear entry
2989             }
2990             else {
2991             &DB::restoreState($fName);
2992             }
2993              
2994             } # end of Initialize
2995              
2996             sub restoreState {
2997             my ($fName) = @_ ;
2998              
2999             my $stateFile = makeFileSaveName($fName);
3000             if( -e $stateFile && -r $stateFile ) {
3001             my ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $DB::window->get_state($stateFile) ;
3002             &DB::restore_breakpoints_from_save($files) ;
3003             $DB::window->{'expr_list'} = $expr_list if defined $expr_list ;
3004             $DB::window->{eval_saved_text} = $eval_saved_text ;
3005              
3006             if ($main_win_geometry) {
3007             # restore the height and width of the window
3008             $DB::window->{main_window}->geometry($main_win_geometry) ;
3009             }
3010             }
3011              
3012             } # end of Restore State
3013              
3014             sub makeFileSaveName {
3015             return "$_[0].ptkdb";
3016             }
3017              
3018             sub save_state_file {
3019             my($fname) = @_ ;
3020              
3021             my $files = &DB::breakpoints_to_save();
3022              
3023             my $d = Data::Dumper->new( [ $files, $DB::window->{'expr_list'}, "" ],
3024             [ "files", "expr_list", "eval_saved_text" ] ) ;
3025             $d->Purity(1) ;
3026              
3027             local(*F) ;
3028             open F, ">$fname" || die "Couldn't open file $fname" ;
3029             print F $d->Dump() || die "Couldn't write file" ;
3030             close F ;
3031             } # end of save_state_file
3032              
3033             sub SaveState {
3034             my($name_in) = @_ ;
3035             my ($top, $entry, $okayBtn);
3036             my ($fname, $saveSub, $cancelSub, $saveName, $eval_saved_text, $d) ;
3037             my ($files, $main_win_geometry);
3038             #
3039             # Create our default name
3040             #
3041             my $win = $DB::window ;
3042              
3043             #
3044             # Extract the height and width of our window
3045             #
3046             $main_win_geometry = $win->{main_window}->geometry ;
3047              
3048             if ( defined $win->{save_box} ) {
3049             $win->{save_box}->raise ;
3050             $win->{save_box}->focus ;
3051             return ;
3052             }
3053              
3054             $saveName = $name_in || makeFileSaveName($DB::startupFname) ;
3055              
3056              
3057             $saveSub = sub {
3058             $win->{'event'} = 'null' ;
3059              
3060             delete $win->{save_box} ;
3061              
3062             if( exists $win->{eval_window} ) {
3063             $eval_saved_text = $win->{eval_text}->get('0.0', 'end') ;
3064             }
3065             else {
3066             $eval_saved_text = $win->{eval_saved_text} ;
3067             }
3068              
3069             $files = &DB::breakpoints_to_save();
3070              
3071             $d = Data::Dumper->new( [ $files, $win->{'expr_list'}, $eval_saved_text, $main_win_geometry ],
3072             [ "files", "expr_list", "eval_saved_text", "main_win_geometry"] ) ;
3073             $d->Purity(1) ;
3074              
3075             local(*F) ;
3076             eval {
3077             open F, ">$saveName" || die "Couldn't open file $saveName" ;
3078             print F $d->Dump() || die "Couldn't write file" ;
3079             close F ;
3080             };
3081             $win->DoAlert($@) if $@ ;
3082             }; # end of save sub
3083              
3084             $cancelSub = sub {
3085             delete $win->{'save_box'}
3086             } ; # end of cancel sub
3087              
3088             #
3089             # Create a dialog
3090             #
3091              
3092             $win->{'save_box'} = $win->simplePromptBox("Save Config?", $saveName, $saveSub, $cancelSub) ;
3093              
3094             } # end of SaveState
3095              
3096             sub RestoreState {
3097             my $restoreSub = sub {
3098             $DB::window->restoreStateFile($Devel::tcltkdb::promptString);
3099             };
3100             $DB::window->simplePromptBox("Restore Config?", makeFileSaveName($DB::startupFname), $restoreSub) ;
3101             } # end of RestoreState
3102              
3103             sub SetStepOverBreakPoint {
3104             my ($offset) = @_ ;
3105             $DB::step_over_depth = $DB::subroutine_depth + ($offset ? $offset : 0) ;
3106             } # end of SetStepOverBreakPoint
3107              
3108             #
3109             # NOTE: It may be logical and somewhat more economical
3110             # lines of codewise to set $DB::step_over_depth_saved
3111             # when we enter the subroutine, but this gets called
3112             # for EVERY callable line of code in a program that
3113             # is being debugged, so we try to save every line of
3114             # execution that we can.
3115             #
3116             sub isBreakPoint {
3117             my ($fname, $line, $package) = @_ ;
3118              
3119             if ( $DB::single && ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth > 0) && !$DB::on) {
3120             $DB::single = 0 ;
3121             return 0 ;
3122             }
3123             #
3124             # doing a step over/in
3125             #
3126              
3127             if( $DB::single || $DB::signal ) {
3128             $DB::single = 0 ;
3129             $DB::signal = 0 ;
3130             $DB::subroutine_depth = $DB::subroutine_depth ;
3131             return 1 ;
3132             }
3133             #
3134             # 1st Check to see if there is even a breakpoint there.
3135             # 2nd If there is a breakpoint check to see if it's check box control is 'on'
3136             # 3rd If there is any kind of expression, evaluate it and see if it's true.
3137             #
3138             my $brkPt = &DB::getdbline($fname, $line) ;
3139              
3140             return 0 if( !$brkPt || !$brkPt->{'value'} || !breakPointEvalExpr($brkPt, $package) ) ;
3141              
3142             &DB::cleardbline($fname, $line) if( $brkPt->{'type'} eq 'temp' ) ;
3143              
3144             $DB::subroutine_depth = $DB::subroutine_depth ;
3145              
3146             return 1 ;
3147             } # end of isBreakPoint
3148              
3149             #
3150             # Check the breakpoint expression to see if it is true.
3151             #
3152             sub breakPointEvalExpr {
3153             my ($brkPt, $package) = @_ ;
3154             my (@result) ;
3155              
3156             return 1 unless $brkPt->{expr} ; # return if there is no expression
3157              
3158             no strict ;
3159             @result = &DB::dbeval($package, $brkPt->{'expr'}) ;
3160             use strict ;
3161              
3162             $DB::window->DoAlert($@) if $@ ;
3163              
3164             return $result[0] or @result ; # we could have a case where the 1st element is undefined
3165             # but subsequent elements are defined
3166              
3167             } # end of breakPointEvalExpr
3168              
3169             #
3170             # Evaluate the given expression, return the result.
3171             # MUST BE CALLED from within DB::DB in order for it
3172             # to properly interpret the vars
3173             #
3174             sub dbeval {
3175             my($ptkdb__package, $ptkdb__expr) = @_ ;
3176             my(@ptkdb__result, $ptkdb__str) ;
3177             my(@ptkdb_args) ;
3178             local($^W) = 0 ; # temporarily turn off warnings
3179              
3180             no strict ;
3181             #
3182             # This substitution is done so that
3183             # we return HASH, as opposed to an ARRAY.
3184             # An expression of %hash results in a
3185             # list of key/value pairs.
3186             #
3187              
3188             $ptkdb__expr =~ s/^\s*%/\\%/;
3189              
3190             @_ = @DB::saved_args ; # replace @_ arg array with what we came in with
3191              
3192             @ptkdb__result = eval <<__EVAL__ ;
3193              
3194              
3195             \$\@ = \$DB::save_err ;
3196              
3197             package $ptkdb__package;
3198              
3199             $ptkdb__expr;
3200              
3201             __EVAL__
3202              
3203             @ptkdb__result = ("ERROR ($@)") if $@ ;
3204              
3205             use strict ;
3206              
3207             return @ptkdb__result ;
3208             } # end of dbeval
3209              
3210             #
3211             # Call back we give to our 'quit' button
3212             # and binding to the WM_DELETE_WINDOW protocol
3213             # to quit the debugger.
3214             #
3215             sub dbexit {
3216             print STDERR "dbexit\n";
3217             exit ;
3218             } # end of dbexit
3219              
3220             #
3221             # This is the primary entry point for the debugger. When a perl program
3222             # is parsed with the -d(in our case -d:tcltkdb) option set the parser will
3223             # insert a call to DB::DB in front of every excecutable statement.
3224             #
3225             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
3226             #
3227              
3228             sub DB {
3229             @DB::saved_args = @_ ; # save arg context
3230             $DB::save_err = $@ ; # save value of $@
3231             my ($package, $filename, $line) = caller ;
3232              
3233             unless( $isInitialized ) {
3234             return if( $filename ne $0 ) ; # not in our target file
3235             &DB::Initialize($filename) ;
3236             }
3237              
3238             if (!isBreakPoint($filename, $line, $package) ) {
3239             $DB::single = 0;
3240             $@ = $DB::save_err;
3241             return;
3242             }
3243              
3244             if ( !$DB::window ) { # not setup yet
3245             $@ = $DB::save_err;
3246             return;
3247             }
3248              
3249             $DB::window->setup_main_window() unless $DB::window->{'main_window'} ;
3250              
3251             $DB::window->EnterActions() ;
3252              
3253             my ($saveP) = $^P;
3254             $^P = 0 ;
3255              
3256             $DB::on = 1 ;
3257              
3258             #
3259             # The user can specify this variable in one of the startup files,
3260             # this will make the debugger run right after startup without
3261             # the user having to press the 'run' button.
3262             #
3263             if( $DB::no_stop_at_start ) {
3264             $DB::no_stop_at_start = 0 ;
3265             $DB::on = 0 ;
3266             $@ = $DB::save_err ;
3267             return ;
3268             }
3269              
3270             if( !$DB::sigint_disable ) {
3271             $SIG{'INT'} = $DB::dbint_handler_save if $DB::dbint_handler_save ; # restore original signal handler
3272             $SIG{'INT'} = "DB::dbexit" unless $DB::dbint_handler_save ;
3273             }
3274              
3275             #$DB::window->{main_window}->raise() ; # bring us to the top make sure OUR event loop runs
3276             $DB::window->{main_window}->focus() ;
3277              
3278             $DB::window->set_file($filename, $line) ;
3279             #
3280             # Refresh the exprs to see if anything has changed
3281             #
3282             updateExprs($package) ;
3283              
3284             #
3285             # Update subs Page if necessary
3286             #
3287             my $cnt = scalar keys %DB::sub ;
3288             if ( $cnt != $DB::window->{'subs_list_cnt'} && $DB::window->{'subs_page_activated'} ) {
3289             $DB::window->fill_subs_page();
3290             $DB::window->{'subs_list_cnt'} = $cnt;
3291             }
3292             #
3293             # Update the subroutine stack menu
3294             #
3295             $DB::window->refresh_stack_menu() ;
3296             $DB::window->{run_flag} = 1 ;
3297              
3298             my ($evt, @result, $r) ;
3299              
3300             for( ; ; ) {
3301             #
3302             # we wait here for something to do
3303             #
3304             $evt = $DB::window->main_loop() ;
3305              
3306             last if( $evt eq 'step' ) ;
3307              
3308             $DB::single = 0 if ($evt eq 'run' ) ;
3309              
3310             if ($evt eq 'balloon_eval' ) {
3311             $DB::window->code_motion_eval(&DB::dbeval($package, $DB::window->{'balloon_expr'})) ;
3312             next ;
3313             }
3314              
3315             if ( $evt eq 'qexpr' ) {
3316             @result = &DB::dbeval($package, $DB::window->{'qexpr'}) ;
3317             $DB::window->{'quick_entry'}->delete(0, 'end') ; # clear old text
3318             $DB::window->{'quick_dumper'}->Reset() ;
3319             $DB::window->{'quick_dumper'}->Values( [ $#result == 0 ? @result : \@result ] ) ;
3320             $DB::window->{'quick_entry'}->insert(0, $DB::window->{'quick_dumper'}->Dump());
3321             $DB::window->{'quick_entry'}->selectionRange(0, 'end') ; # select it
3322             $evt = 'update' ; # force an update on the expressions
3323             }
3324              
3325             if( $evt eq 'expr' ) {
3326             #
3327             # Append the new expression to the list
3328             # but first check to make sure that we don't already have it.
3329             #
3330              
3331             if ( grep $_->{'expr'} eq $DB::window->{'expr'}, @{$DB::window->{'expr_list'}} ) {
3332             $DB::window->DoAlert("$DB::window->{'expr'} is already listed") ;
3333             next ;
3334             }
3335              
3336             @result = &DB::dbeval($package, $DB::window->{expr}) ;
3337             my $rr = (@result == 1? $result[0] : \@result);
3338             $r = $DB::window->insertExpr([ $rr ], $rr, $DB::window->{'expr'}, -1,'root') ;
3339              
3340             #
3341             # $r will be 1 if the expression was added succesfully, 0 if not,
3342             # and it if wasn't added sucessfully it won't be reevalled the
3343             # next time through.
3344             #
3345             push @{$DB::window->{'expr_list'}}, { 'expr' => $DB::window->{'expr'}, 'depth' => -1 } if $r;
3346              
3347             next;
3348             }
3349             if( $evt eq 'update' ) {
3350             updateExprs($package);
3351             next;
3352             }
3353             if( $evt eq 'reeval' ) {
3354             #
3355             # Reevaluate the contents of the expression eval window
3356             my $txt = $DB::window->{'eval_text'}->get('1.0', 'end') ;
3357             my @result = &DB::dbeval($package, $txt) ;
3358              
3359             $DB::window->updateEvalWindow(@result) ;
3360              
3361             next ;
3362             }
3363             last ;
3364             }
3365             $^P = $saveP ;
3366             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ; # set our signal handler
3367              
3368             $DB::window->LeaveActions() ;
3369              
3370             $@ = $DB::save_err ;
3371             $DB::on = 0 ;
3372             } # end of DB
3373              
3374             ##
3375             ## in this case we do not use local($^W) since we would like warnings
3376             ## to be issued past this point, and the localized copy of $^W will not
3377             ## go out of scope until the end of compilation
3378             ##
3379             ##
3380              
3381             #
3382             # This is another place where we'll try and keep the
3383             # code as 'lite' as possible to prevent the debugger
3384             # from slowing down the user's application
3385             #
3386             # When a perl program is parsed with the -d(in our case a -d:tcltkdb) option
3387             # the parser will route all subroutine calls through here, setting $DB::sub
3388             # to the name of the subroutine to be called, leaving it to the debugger to
3389             # make the actual subroutine call and do any pre or post processing it may
3390             # need to do. In our case we take the opportunity to track the depth of the call
3391             # stack so that we can update our 'Stack' menu when we stop.
3392             #
3393             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
3394             #
3395             #
3396             sub sub {
3397             my ($result, @result) ;
3398             #
3399             # See NOTES(1)
3400             #
3401             $DB::subroutine_depth += 1 unless $DB::on ;
3402             $DB::single = 0 if ( ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth >= 0) && !$DB::on) ;
3403              
3404             if( wantarray ) {
3405             # array context
3406              
3407             no strict ; # otherwise perl gripes about calling the sub by the reference
3408             @result = &$DB::sub ; # call the subroutine by name
3409             use strict ;
3410              
3411             $DB::subroutine_depth -= 1 unless $DB::on ;
3412             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3413             return @result;
3414              
3415             } elsif(defined wantarray) {
3416             # scalar context
3417              
3418             no strict;
3419             $result = &$DB::sub;
3420             use strict;
3421              
3422             $DB::subroutine_depth -= 1 unless $DB::on;
3423             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3424             return $result;
3425              
3426             } else {
3427             # void context
3428              
3429             no strict;
3430             &$DB::sub;
3431             use strict;
3432              
3433             $DB::subroutine_depth -= 1 unless $DB::on ;
3434             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3435             return;
3436              
3437             }
3438              
3439             } # end of sub
3440              
3441             1; # return true value
3442