File Coverage

blib/lib/Devel/tkdb.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             $tkdb::VERSION = '2.2';
2              
3 1     1   949 use strict;
  1         2  
  1         28  
4 1     1   946 use Data::Dumper;
  1         9863  
  1         81  
5 1     1   1383 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::tkdb;
14              
15              
16             =head1 NAME
17              
18             Devel::tkdb - Perl debugger using a Tcl/Tk GUI
19              
20             =head1 DESCRIPTION
21              
22             tkdb 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 tkdb invoke perl like this:
33              
34             perl -d:tkdb myscript.pl
35              
36             =head1 Usage
37              
38             perl -d:tkdb 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::tkdb::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:tkdb
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::tkdb::button_font = $ENV{'PTKDB_BUTTON_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ; # font for buttons
481             @Devel::tkdb::code_text_font = $ENV{'PTKDB_CODE_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ;
482              
483             @Devel::tkdb::expression_text_font = $ENV{'PTKDB_EXPRESSION_FONT'} ? ( "-font" => $ENV{'PTKDB_EXPRESSION_FONT'} ) : () ;
484             @Devel::tkdb::eval_text_font = $ENV{'PTKDB_EVAL_FONT'} ? ( -font => $ENV{'PTKDB_EVAL_FONT'} ) : () ; # text for the expression eval window
485              
486             $Devel::tkdb::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::_?tkdb/ ? 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::_?tkdb/ ? 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::tkdb
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::tkdb::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::tkdb::button_font,
732             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
733              
734             $topLevel->Button( -text => "Cancel", @Devel::tkdb::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             $self->{current_file} = ""; # force a file reset
757             $self->{'main_window'}->destroy;
758             $self->{'main_window'} = undef;
759             $self->{int}->Eval('set event run');
760             }
761              
762             sub setup_menu_bar {
763             my ($self) = @_;
764              
765             my $mw = $self->{main_window} ;
766             my $int = $self->{int};
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::tkdb::DoRestart) ;
813             $mw->bind('' => sub { $int->Eval('set 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 ; $int->Eval('set event run') };
820              
821             my $runToSub = sub { $int->Eval('set event run') if $DB::window->SetBreakPoint(1) ; } ;
822              
823             my $stepOverSub = sub { &DB::SetStepOverBreakPoint(0) ;
824             $DB::single = 1 ;
825             $int->Eval('set event step');
826             } ;
827              
828             my $stepInSub = sub {
829             $DB::step_over_depth = -1 ;
830             $DB::single = 1 ;
831             $int->Eval('set event step');
832             };
833              
834             my $returnSub = sub {
835             &DB::SetStepOverBreakPoint(-1) ;
836             $int->Eval('set 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::tkdb::DoRestart ],
855             '-',
856             [ 'checkbutton' => 'Stop On Warning', -variable => \$DB::tkdb::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::tkdb::button_font,
934             -command => $stepInSub) ->pack(-side => 'left');
935             $bb->Button(-text => "Step Over", @Devel::tkdb::button_font,
936             -command => $stepOverSub) ->pack(-side => 'left');
937             $bb->Button(-text => "Return", @Devel::tkdb::button_font,
938             -command => $returnSub) ->pack(-side => 'left');
939             $bb->Button(-text => "Run", -background => 'green', @Devel::tkdb::button_font,
940             -command => $runSub) ->pack(-side => 'left');
941             $bb->Button(-text => "Run To", @Devel::tkdb::button_font,
942             -command => $runToSub) ->pack(-side => 'left');
943             $bb->Button(-text => "Break", @Devel::tkdb::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::tkdb::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::tkdb::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::tkdb::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::tkdb::subs_tree = tree_split(\@list);
1303              
1304             for ( sort keys %$Devel::tkdb::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::tkdb::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::tkdb::balloon_post,
1472             -motioncommand => \&Devel::tkdb::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::tkdb::promptString = $defaultText;
1531              
1532             my $top = $self->{main_window}->Toplevel(-title => $title, -overanchor => 'cursor');
1533             my $entry = $top->Entry(-textvariable => \$Devel::tkdb::promptString)->pack(-side => 'top', -fill => 'both', -expand => 1);
1534             $top->Button(-text => "Okay", @Devel::tkdb::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::tkdb::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::_?tkdb/ ? 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::tkdb::linenumber_length") ;
1622             $self->{'text'}->tagAdd($value ? "breaksetLine" : "breakdisabledLine", "$index.0", "$index.$Devel::tkdb::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::_?tkdb/ ? 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::tkdb::linenumber_length") ;
1747             }
1748             else {
1749             $self->{'text'}->tagRemove("breakdisabledLine", "$idx.0", "$idx.$Devel::tkdb::linenumber_length") ;
1750             }
1751              
1752             $self->{'text'}->tagAdd("breakableLine", "$idx.0", "$idx.$Devel::tkdb::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::_?tkdb/ ? 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->{int}->Eval('set 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             if( $str && $str !~ /^\s*$/ ) { # if there is an expression and it's more than white space
1818             $self->{'qexpr'} = $str ;
1819             $self->{int}->Eval('set event qexpr');
1820             }
1821             } # end of QuickExpr
1822              
1823             sub deleteExpr {
1824             my ($self) = @_ ;
1825             my ($entry, @indexes) ;
1826             my @sList = $self->{'data_list'}->info('select'); # TBD TODO TBD
1827             my @sList0 = $self->{data_list0}->[0]->selectionGet;
1828              
1829             #
1830             # if we're deleteing a top level expression
1831             # we have to take it out of the list of expressions
1832             #
1833              
1834             foreach $entry ( @sList ) {
1835             next if ($entry =~ /\//) ; # goto next expression if we're not a top level ( expr/entry)
1836             my $i = 0 ;
1837             grep { push @indexes, $i if ($_->{'expr'} eq $entry) ; $i++ ; } @{$self->{'expr_list'}} ;
1838             } # end of check loop
1839              
1840             # now take out our list of indexes ;
1841              
1842             for (0..$#indexes) {
1843             splice @{$self->{'expr_list'}}, $indexes[$_] - $_, 1 ;
1844             }
1845              
1846             for( @sList ) {
1847             $self->{'data_list'}->delete('entry', $_) ;
1848             }
1849             } # end of deleteExpr
1850              
1851             ##
1852             ## Inserts an expression($theRef) into tk widget. If the expression
1853             ## is an array, blessed array, hash, or blessed hash(typical object), then this
1854             ## routine is called recursively, adding the members to the next level of heirarchy,
1855             ## prefixing array members with a [idx] and the hash members with the key name.
1856             ## This continues until the entire expression is decomposed to it's atomic constituents.
1857             ## Protection is given(with $reusedRefs) to ensure that 'circular' references within
1858             ## arrays or hashes(i.e. where a member of a array or hash contains a reference to a
1859             ## parent element within the heirarchy.
1860             ##
1861             #
1862             # Returns 1 if sucessfully added 0 if not
1863             #
1864             sub insertExpr {
1865             my($self, $reusedRefs, $theRef, $name, $depth, $el) = @_ ;
1866             my($type, $result, @circRefs, $t) ;
1867             local($^W) = 0 ; # spare us uncessary warnings about comparing strings with ==
1868             my ($tv, $tcol) = @{$self->{data_list0}};
1869              
1870             while( ref $theRef eq 'SCALAR' ) {
1871             $theRef = $$theRef ;
1872             }
1873              
1874             my $label = "" ;
1875             REF_CHECK: for( ; ; ) {
1876             push @circRefs, $theRef ;
1877             $type = ref $theRef ;
1878             last unless ($type eq "REF") ;
1879             $theRef = $$theRef ; # dref again
1880              
1881             $label .= "\\" ; # append a
1882             if( grep $_ == $theRef, @circRefs ) {
1883             $label .= "(circular)" ;
1884             last ;
1885             }
1886             }
1887              
1888             if( !$type || $type eq "" || $type eq "GLOB" || $type eq "CODE") {
1889             eval {
1890             $t = "$name = $label" . (defined $theRef?$theRef:"undef");
1891             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1892             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$t");
1893             };
1894             $self->DoAlert($@), return 0 if $@ ;
1895             return 1 ;
1896             }
1897              
1898             if( $type eq 'ARRAY' or "$theRef" =~ /ARRAY/ ) {
1899             my $idx = 0 ;
1900             eval {
1901             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1902             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = $theRef");
1903             } ;
1904             if( $@ ) {
1905             $self->DoAlert($@) ;
1906             return 0 ;
1907             }
1908             $result = 1 ;
1909             for my $r ( @$theRef ) {
1910              
1911             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
1912             eval {
1913             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1914             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"[$idx] = $r REUSED ADDR");
1915             } ;
1916             $self->DoAlert($@) if( $@ ) ;
1917             next ;
1918             }
1919              
1920             push @$reusedRefs, $r ;
1921             $result = $self->insertExpr($reusedRefs, $r, "[$idx]", $depth-1, $el) unless $depth == 0 ;
1922             pop @$reusedRefs ;
1923              
1924             return 0 unless $result ;
1925             $idx += 1 ;
1926             }
1927             return 1 ;
1928             } # end of array case
1929              
1930             if ("$theRef" !~ /HASH\050\060x[\da-f]*\051/) {
1931             eval {
1932             $el = $tv->itemCreate(-button=>'yes',-parent=>$el);
1933             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = $theRef");
1934             };
1935             if( $@ ) {
1936             $self->DoAlert($@) ;
1937             return 0 ;
1938             }
1939             return 1 ;
1940             }
1941             #
1942             # Anything else at this point is
1943             # either a 'HASH' or an object
1944             # of some kind.
1945             #
1946             my $idx = 0 ;
1947             my @theKeys = sort keys %$theRef;
1948             $el = $tv->itemCreate(-parent=>$el);
1949             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$name = " . "$theRef");
1950             $result = 1 ;
1951              
1952             for my $r ( @$theRef{@theKeys} ) { # slice out the values with the sorted list
1953              
1954             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
1955             eval {
1956             $el = $tv->itemCreate(-parent=>$el);
1957             $tv->itemElementConfigure($el, $tcol, 'foo', -text=>"$theKeys[$idx++] = $r REUSED ADDR");
1958             } ;
1959             print "bad path $@\n" if( $@ ) ;
1960             next ;
1961             }
1962              
1963             push @$reusedRefs, $r;
1964              
1965             $result = $self->insertExpr($reusedRefs, # recursion protection
1966             $r, # reference whose value is displayed
1967             $theKeys[$idx], # name
1968             $depth-1, # remaining expansion depth
1969             $el)
1970             unless $depth == 0 ;
1971              
1972             pop @$reusedRefs ;
1973              
1974             return 0 unless $result ;
1975             $idx += 1 ;
1976             } # end of ref add loop
1977              
1978             return 1 ;
1979             } # end of insertExpr
1980              
1981             #
1982             # We're setting the line where we are stopped.
1983             # Create a tag for this and set it as bold.
1984             #
1985             sub set_line {
1986             my ($self, $lineno) = @_ ;
1987             my $text = $self->{'text'} ;
1988              
1989             return if( $lineno <= 0 ) ;
1990              
1991             if( $self->{current_line} > 0 ) {
1992             $text->tagRemove('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
1993             }
1994             $self->{current_line} = $lineno - $self->{'line_offset'} ;
1995             $text->tagAdd('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
1996              
1997             $self->{'text'}->see("$self->{current_line}.0 linestart") ;
1998             } # end of set_line
1999              
2000             #
2001             # Set the file that is in the code window.
2002             #
2003             # $fname the 'new' file to view
2004             # $line the line number we're at
2005             # $brkPts any breakpoints that may have been set in this file
2006             #
2007              
2008             sub set_file {
2009             my ($self, $fname, $line) = @_ ;
2010             my ($lineStr, $offset, $text, @text);
2011              
2012             return unless $fname ; # we're getting an undef here on 'Restart...'
2013              
2014             local(*dbline) = $main::{'_<' . $fname};
2015              
2016             #
2017             # with the #! /usr/bin/perl -d:tkdb at the header of the file
2018             # we've found that with various combinations of other options the
2019             # files haven't come in at the right offsets
2020             #
2021             $offset = 0 ;
2022             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?tkdb/ ;
2023             $self->{'line_offset'} = $offset ;
2024              
2025             $text = $self->{'text'} ;
2026              
2027             if( $fname eq $self->{current_file} ) {
2028             $self->set_line($line) ;
2029             return ;
2030             } ;
2031              
2032             $self->{main_window}->configure('-title' => $fname) ;
2033              
2034             # Erase any existing text
2035             $text->delete('1.0','end');
2036              
2037             #
2038             # This is the tightest loop we have in the ptkdb code.
2039             # It is here where performance is the most critical.
2040             # The map block formats perl code for display. Since
2041             # the file could be potentially large, we will try
2042             # to make this loop as thin as possible.
2043             #
2044              
2045             local($^W) = 0 ; # spares us useless warnings under -w when checking $dbline[$_] != 0
2046              
2047             my $noCode = ($#dbline - ($offset + 1)) < 0 ;
2048              
2049             my $i0 = "0" x $Devel::tkdb::linenumber_length;
2050             $text->_insertEnd(map {
2051             #$lineStr .= "\n" unless /\n$/; # append a \n if there isn't one already
2052             ($i0++, ($_==0?'nonbreakableLine':'breakableLine'), " $_", 'code') # a string,tag pair for text insert
2053              
2054             } @dbline[$offset+1 .. $#dbline] ) unless $noCode;
2055              
2056             #
2057             # Reinsert breakpoints (if info provided)
2058             #
2059              
2060             $self->set_line($line);
2061             $self->{current_file} = $fname;
2062             return $self->reinsertBreakpoints($fname);
2063             } # end of set_file
2064              
2065             #
2066             # Get the current line that the insert cursor is in
2067             #
2068             sub get_lineno {
2069             my ($self) = @_ ;
2070              
2071             my $info = $self->{'text'}->index('insert'); # get the location for the insertion point
2072             $info =~ s/\..*$/\.0/ ;
2073              
2074             return int $info ;
2075             } # end of get_lineno
2076              
2077             sub DoGoto {
2078             my ($self, $entry) = @_ ;
2079              
2080             my $txt = $entry->get() ;
2081              
2082             $txt =~ s/(\d*).*/$1/; # take the first blob of digits
2083             if( $txt eq "" ) {
2084             print "invalid text range\n";
2085             return;
2086             }
2087              
2088             $self->{'text'}->see("$txt.0") ;
2089              
2090             $entry->_selectionRange(0, 'end');
2091             } # end of DoGoto
2092              
2093             sub GotoLine {
2094             my ($self) = @_ ;
2095              
2096             if( $self->{goto_window} ) {
2097             $self->{goto_window}->raise() ;
2098             $self->{goto_text}->focus() ;
2099             return ;
2100             }
2101              
2102             #
2103             # Construct a dialog that has an
2104             # entry field, okay and cancel buttons
2105             #
2106             my $okaySub = sub { $self->DoGoto($self->{'goto_text'}) } ;
2107              
2108             my $topLevel = $self->{main_window}->Toplevel(-title => "Goto Line?", -overanchor => 'cursor') ;
2109              
2110             $self->{goto_text} = $topLevel->Entry()->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2111             $self->{goto_text}->bind('', $okaySub) ; # make a CR do the same thing as pressing an okay
2112             $self->{goto_text}->focus();
2113              
2114             $topLevel->Button( -text => "Okay", -command => $okaySub, @Devel::tkdb::button_font,
2115             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2116              
2117             #
2118             # Subroutone called when the 'Dismiss' button is pushed.
2119             my $dismissSub = sub {
2120             delete $self->{goto_text} ;
2121             $self->{goto_window}->destroy;
2122             delete $self->{goto_window} ; # remove the entry from our hash so we won't
2123             } ;
2124              
2125             $topLevel->Button( -text => "Dismiss", @Devel::tkdb::button_font,
2126             -command => $dismissSub )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2127              
2128             $topLevel->protocol('WM_DELETE_WINDOW', sub { $topLevel->destroy; } ) ;
2129             $self->{goto_window} = $topLevel;
2130              
2131             } # end of GotoLine
2132              
2133              
2134             #
2135             # Subroutine called when the 'okay' button is pressed
2136             #
2137             sub FindSearch {
2138             my ($self, $entry, $btn, $regExp) = @_ ;
2139             my (@switches, $result) ;
2140             my $txt = $entry->get() ;
2141              
2142             return if $txt eq "" ;
2143              
2144             push @switches, "-forward" if $self->{fwdOrBack} eq "forward" ;
2145             push @switches, "-backward" if $self->{fwdOrBack} eq "backward" ;
2146              
2147             if( $regExp ) {
2148             push @switches, "-regexp" ;
2149             }
2150             else {
2151             push @switches, "-nocase" ; # if we're not doing regex we may as well do caseless search
2152             }
2153              
2154             $result = $self->{'text'}->search(@switches, $txt, $self->{search_start}) ;
2155              
2156             # untag the previously found text
2157              
2158             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2159              
2160             if( !$result || $result eq "" ) {
2161             # No Text was found
2162             $btn->flash() ;
2163             $btn->bell() ;
2164              
2165             delete $self->{search_tag} ;
2166             $self->{'search_start'} = "0.0" ;
2167             }
2168             else { # text found
2169             $self->{'text'}->see($result) ;
2170             # set the insertion of the text as well
2171             $self->{'text'}->markSet('insert' => $result) ;
2172             my $len = length $txt;
2173              
2174             if( $self->{fwdOrBack} ) {
2175             $self->{search_start} = "$result +$len chars" ;
2176             $self->{search_tag} = [ $result, $self->{search_start} ] ;
2177             }
2178             else {
2179             # backwards search
2180             $self->{search_start} = "$result -$len chars" ;
2181             $self->{search_tag} = [ $result, "$result +$len chars" ] ;
2182             }
2183              
2184             # tag the newly found text
2185              
2186             $self->{'text'}->tagAdd('search_tag', @{$self->{search_tag}}) ;
2187             } # end of text found
2188              
2189             $entry->_selectionRange(0, 'end');
2190              
2191             } # end of FindSearch
2192              
2193              
2194             #
2195             # Support for the Find Text... Menu command
2196             #
2197             sub FindText {
2198             my ($self) = @_ ;
2199             my ($okayBtn);
2200              
2201             #
2202             # if we already have the Find Text Window open don't bother openning
2203             # another, bring the existing one to the front.
2204             if( $self->{find_window} ) {
2205             $self->{find_window}->raise();
2206             return;
2207             }
2208              
2209             $self->{search_start} = $self->{'text'}->index('insert') if( $self->{search_start} eq "" ) ;
2210              
2211             #
2212             # Subroutine called when the 'Dismiss' button is pushed.
2213             my $dismissSub = sub {
2214             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2215             $self->{search_start} = "" ;
2216             $self->{find_window}->destroy;
2217             delete $self->{search_tag} ;
2218             delete $self->{find_window} ;
2219             };
2220              
2221             #
2222             # Construct a dialog that has an entry field, forward, backward, regex option, okay and cancel buttons
2223             #
2224             my $top = $self->{main_window}->Toplevel(-title => "Find Text?");
2225              
2226             my $we = $top->Entry()->pack(qw/-side top -fill both -expand 1/);
2227              
2228             my $frm = $top->Frame()->pack(qw/-side top -fill both -expand 1/);
2229              
2230             $self->{fwdOrBack} = 'forward';
2231             $frm->Radiobutton(-text => "Forward", -value => 1, -variable => \$self->{fwdOrBack})
2232             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2233             $frm->Radiobutton(-text => "Backward", -value => 0, -variable => \$self->{fwdOrBack})
2234             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2235              
2236             my $regExp = 0 ;
2237             $frm->Checkbutton(-text => "RegExp", -variable => \$regExp)
2238             ->pack(-side => 'left', -fill => 'both', -expand => 1);
2239              
2240             # Okay and dismiss buttons
2241             $okayBtn = $top->Button( -text => "Okay", -command => sub { $self->FindSearch($we, $okayBtn, $regExp) ; },
2242             @Devel::tkdb::button_font,
2243             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2244              
2245             $we->bind('', sub { $self->FindSearch($we, $okayBtn, $regExp) ; }) ;
2246              
2247             $top->Button( -text => "Dismiss", @Devel::tkdb::button_font,
2248             -command => $dismissSub)->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2249              
2250             $top->protocol('WM_DELETE_WINDOW', $dismissSub) ;
2251             $we->focus();
2252             $self->{find_window} = $top;
2253              
2254             } # end of FindText
2255              
2256             sub main_loop {
2257             my ($self) = @_;
2258             my $evt;
2259              
2260             SWITCH:
2261             for ($evt = 'null' ; $DB::window->{main_window}; ) {
2262              
2263             $self->{int}->tkwait('variable', 'event');
2264             $evt = $self->{int}->GetVar('event');
2265              
2266             $evt eq 'step' && do { last SWITCH ; } ;
2267             $evt eq 'null' && do { next SWITCH ; } ;
2268             $evt eq 'run' && do { last SWITCH ; } ;
2269             $evt eq 'quit' && do { $self->DoQuit ; } ;
2270             $evt eq 'expr' && do { return $evt ; } ; # adds an expression to our expression window
2271             $evt eq 'qexpr' && do { return $evt ; } ; # does a 'quick' expression
2272             $evt eq 'update' && do { return $evt ; } ; # forces an update on our expression window
2273             $evt eq 'reeval' && do { return $evt ; } ; # updated the open expression eval window
2274             $evt eq 'balloon_eval' && do { return $evt } ;
2275             } # end of switch block
2276             return $evt ;
2277             } # end of main_loop
2278              
2279             #
2280             # $subStackRef A reference to the current subroutine stack
2281             #
2282              
2283             sub goto_sub_from_stack {
2284             my ($self, $f, $lineno) = @_ ;
2285             $self->set_file($f, $lineno) ;
2286             } # end of goto_sub_from_stack ;
2287              
2288             sub refresh_stack_menu {
2289             my ($self) = @_ ;
2290             my ($name, $i, $sub_offset, $subStack) ;
2291              
2292             #
2293             # CAUTION: In the effort to 'rationalize' the code
2294             # are moving some of this function down from DB::DB
2295             # to here. $sub_offset represents how far 'down'
2296             # we are from DB::DB. The $DB::subroutine_depth is
2297             # tracked in such a way that while we are 'in' the debugger
2298             # it will not be incremented, and thus represents the stack depth
2299             # of the target program.
2300             #
2301             $sub_offset = 1 ;
2302             $subStack = [] ;
2303              
2304             # clear existing entries
2305              
2306             for( $i = 0 ; $i <= $DB::subroutine_depth ; $i++ ) {
2307             my ($package, $filename, $line, $subName) = caller $i+$sub_offset ;
2308             last if !$subName ;
2309             push @$subStack, { 'name' => $subName, 'pck' => $package, 'filename' => $filename, 'line' => $line } ;
2310             }
2311              
2312             $self->{stack_menu}->menu->delete(0, 'last') ; # delete existing menu items
2313              
2314             for( $i = 0 ; $subStack->[$i] ; $i++ ) {
2315              
2316             my $str = defined $subStack->[$i+1] ? "$subStack->[$i+1]->{name}" : "MAIN" ;
2317              
2318             my ($f, $line) = ($subStack->[$i]->{filename}, $subStack->[$i]->{line}) ; # make copies of the values for use in 'sub'
2319             $self->{stack_menu}->command(-label => $str, -command => sub { $self->goto_sub_from_stack($f, $line) ; } ) ;
2320             }
2321             } # end of refresh_stack_menu
2322              
2323             no strict ;
2324              
2325             sub get_state {
2326             my ($self, $fname) = @_ ;
2327             my ($val) ;
2328             local($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
2329              
2330             do "$fname" ;
2331              
2332             if( $@ ) {
2333             $self->DoAlert($@) ;
2334             return ( undef ) x 4 ; # return a list of 4 undefined values
2335             }
2336              
2337             return ($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
2338             } # end of get_state
2339              
2340             use strict ;
2341              
2342             sub restoreStateFile {
2343             my ($self, $fname) = @_ ;
2344             local(*F) ;
2345             my ($saveCurFile, $s, @n, $n) ;
2346              
2347             if (!(-e $fname && -r $fname)) {
2348             $self->DoAlert("$fname does not exist") ;
2349             return ;
2350             }
2351              
2352             my ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $self->get_state($fname) ;
2353             my ($f, $brks) ;
2354              
2355             return unless defined $files || defined $expr_list ;
2356              
2357             &DB::restore_breakpoints_from_save($files) ;
2358              
2359             #
2360             # This should force the breakpoints to be restored
2361             #
2362             $saveCurFile = $self->{current_file} ;
2363              
2364             @$self{ 'current_file', 'expr_list', 'eval_saved_text' } =
2365             ( "" , $expr_list, $eval_saved_text) ;
2366              
2367             $self->set_file($saveCurFile, $self->{current_line}) ;
2368              
2369             if ( $main_win_geometry && $self->{'main_window'} ) {
2370             # restore the height and width of the window
2371             $self->{main_window}->geometry( $main_win_geometry ) ;
2372             }
2373             $self->{int}->Eval('set event update');
2374              
2375             } # end of retstoreState
2376              
2377             sub updateEvalWindow {
2378             my ($self, @result) = @_ ;
2379             my ($leng, $str) = (0,'');
2380              
2381             for (@result) {
2382             if( $self->{hexdump_evals} ) {
2383             # eventually put hex dumper code in here
2384             $self->{eval_results}->insert('end', hexDump($_)) ;
2385             } else {
2386             my $d = Data::Dumper->new([$_]);
2387             $d->Indent(2);
2388             $d->Terse(1);
2389             $str = $d->Dump($_);
2390             }
2391             $leng += length $str ;
2392             $self->{eval_results}->insert('end', $str) ;
2393             }
2394             } # end of updateEvalWindow
2395              
2396             ##
2397             ## converts non printable chars to '.' for a string
2398             ##
2399             sub printablestr {
2400             return join "", map { (ord($_) >= 32 && ord($_) < 127) ? $_ : '.' } split //, $_[0] ;
2401             }
2402              
2403             ##
2404             ## hex dump utility function
2405             ##
2406             sub hexDump {
2407             my @retList;
2408             my $width = 8;
2409             my $offset = 0;
2410              
2411             for (@_) {
2412             my $str = '';
2413             my $len = length $_ ;
2414              
2415             while($len) {
2416             my $n = $len >= $width ? $width : $len ;
2417              
2418             my $fmt = "\n%04X " . ("%02X " x $n ) . ( ' ' x ($width - $n) ) . " %s" ;
2419             my @elems = map ord, split //, (substr $_, $offset, $n) ;
2420             $str .= sprintf($fmt, $offset, @elems, printablestr(substr $_, $offset, $n)) ;
2421             $offset += $width;
2422              
2423             $len -= $n;
2424             } # while
2425              
2426             push @retList, $str;
2427             } # for
2428              
2429             return $retList[0] unless wantarray ;
2430             return @retList ;
2431             } # end of hd
2432              
2433              
2434             sub setupEvalWindow {
2435             my($self) = @_;
2436             $self->{eval_window}->focus(), return if exists $self->{eval_window} ; # already running this window?
2437              
2438             my $top = $self->{main_window}->Toplevel(-title => "Evaluate Expressions...");
2439             $self->{eval_window} = $top;
2440             $self->{eval_text} = $top->Scrolled('Text',
2441             @Devel::tkdb::eval_text_font,
2442             -width => 50,
2443             -height => 10,
2444             -wrap => "none",
2445             )->pack(qw/-side top -fill both -expand 1/);
2446              
2447             $self->{eval_text}->insert('end', $self->{eval_saved_text}) if exists $self->{eval_saved_text} && defined $self->{eval_saved_text};
2448              
2449             $top->Label(-text => "Results:")->pack(qw/-side top -fill both -expand n/);
2450              
2451             $self->{eval_results} = $top->Scrolled('Text',
2452             -width => 50,
2453             -height => 10,
2454             -wrap => "none",
2455             @Devel::tkdb::eval_text_font
2456             )->pack(qw/-side top -fill both -expand 1/);
2457              
2458             my $btn = $top->Button(-text => 'Eval...', -command => sub { $DB::window->{event} = 'reeval' ; }
2459             )->pack(-side => 'left', -fill => 'x', -expand => 1);
2460              
2461             my $dismissSub = sub {
2462             $self->{eval_saved_text} = $self->{eval_text}->get('0.0', 'end') ;
2463             $self->{eval_window}->destroy ;
2464             delete $self->{eval_window} ;
2465             };
2466              
2467             $top->protocol('WM_DELETE_WINDOW', $dismissSub ) ;
2468              
2469             $top->Button(-text => 'Clear Eval', -command => sub { $self->{eval_text}->delete('0.0', 'end') }
2470             )->pack(-side => 'left', -fill => 'x', -expand => 1);
2471              
2472             $top->Button(-text => 'Clear Results', -command => sub { $self->{eval_results}->delete('0.0', 'end') }
2473             )->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2474              
2475             $top->Button(-text => 'Dismiss', -command => $dismissSub)->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2476             $top->Checkbutton(-text => 'Hex', -variable => \$self->{hexdump_evals})->pack(-side => 'left') ;
2477              
2478             } # end of setupEvalWindow ;
2479              
2480             sub filterBreakPts {
2481             my ($breakPtsListRef, $fname) = @_ ;
2482             my $dbline = $main::{'_<' . $fname}; # breakable lines
2483             local($^W) = 0 ;
2484             #
2485             # Go through the list of breaks and take out any that
2486             # are no longer breakable
2487             #
2488              
2489             for( @$breakPtsListRef ) {
2490             next unless defined $_ ;
2491              
2492             next if $dbline->[$_->{'line'}] != 0 ; # still breakable
2493              
2494             $_ = undef ;
2495             }
2496             } # end of filterBreakPts
2497              
2498             sub DoAbout {
2499             my $self = shift ;
2500             my $str = <<"__STR__" ;
2501             tkdb $tkdb::VERSION
2502             Copyright 1998,2003 by Andrew E. Page, 2010,2011 Vadim Konovalov.
2503              
2504             This program is free software; you can redistribute it and/or modify
2505             it under the terms of either:
2506              
2507             a) the GNU General Public License as published by the Free
2508             Software Foundation; either version 1, or (at your option) any
2509             later version, or
2510              
2511             b) the "Artistic License" which comes with this Kit.
2512              
2513             This program is distributed in the hope that it will be useful,
2514             but WITHOUT ANY WARRANTY; without even the implied warranty of
2515             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either
2516             the GNU General Public License or the Artistic License for more details.
2517              
2518             OS $^O
2519             Tcl/Tk Version $Tcl::Tk::TK_VERSION
2520             Tcl::Tk Version $Tcl::Tk::VERSION
2521             Perl Version $]
2522             __STR__
2523              
2524             $self->DoAlert($str, "About ptkdb") ;
2525             } # end of DoAbout
2526              
2527             #
2528             # return 1 if succesfully set,
2529             # return 0 if otherwise
2530             #
2531             sub SetBreakPoint {
2532             my ($self, $isTemp) = @_ ;
2533             my $dbw = $DB::window ;
2534             my $lineno = $dbw->get_lineno();
2535             my $expr = $dbw->clear_entry_text() ;
2536             local($^W) = 0 ;
2537              
2538             if( !&DB::checkdbline($DB::window->{current_file}, $lineno + $self->{'line_offset'}) ) {
2539             $dbw->DoAlert("line $lineno in $DB::window->{current_file} is not breakable") ;
2540             return 0 ;
2541             }
2542              
2543             if( !$isTemp ) {
2544             $dbw->insertBreakpoint($DB::window->{current_file}, $lineno, 1, $expr) ;
2545             return 1 ;
2546             }
2547             else {
2548             $dbw->insertTempBreakpoint($DB::window->{current_file}, $lineno) ;
2549             return 1 ;
2550             }
2551              
2552             return 0 ;
2553             } # end of SetBreakPoint
2554              
2555             sub UnsetBreakPoint {
2556             my ($self) = @_ ;
2557             my $lineno = $self->get_lineno();
2558              
2559             $self->removeBreakpoint($DB::window->{current_file}, $lineno) ;
2560             } # end of UnsetBreakPoint
2561              
2562             sub balloon_post {
2563             my $self = $DB::window ;
2564             my $txt = $DB::window->{'text'} ;
2565              
2566             return 0 if ($self->{'expr_ballon_msg'} eq "") || ($self->{'balloon_expr'} eq "") ; # don't post for an empty string
2567              
2568             return $self->{'balloon_coord'} ;
2569             }
2570              
2571             sub balloon_motion {
2572             my ($txt, $x, $y) = @_ ;
2573             my ($offset_x, $offset_y) = ($x + 4, $y + 4) ;
2574             my $self = $DB::window ;
2575             my $txt2 = $self->{'text'} ;
2576             my $data ;
2577              
2578             $self->{'balloon_coord'} = "$offset_x,$offset_y" ;
2579              
2580             $x -= $txt->rootx ;
2581             $y -= $txt->rooty ;
2582             #
2583             # Post an event that will cause us to put up a popup
2584             #
2585              
2586             if ($txt2->_tagRangesSel) { # check to see if 'sel' tag exists (return undef value)
2587             $data = $txt2->get("sel.first", "sel.last") ; # get the text between the 'first' and 'last' point of the sel (selection) tag
2588             }
2589             else {
2590             $data = $DB::window->retrieve_text_expr($x, $y) ;
2591             }
2592              
2593             if( !$data ) {
2594             $self->{'balloon_expr'} = "" ;
2595             return 0 ;
2596             }
2597              
2598             return 0 if ($data eq $self->{'balloon_expr'}) ; # nevermind if it's the same expression
2599              
2600             $self->{'balloon_expr'} = $data ;
2601             $self->{int}->Eval('set event balloon_eval');
2602              
2603             return 1 ; # ballon will be canceled and a new one put up(maybe)
2604             } # end of balloon_motion
2605              
2606             sub retrieve_text_expr {
2607             my($self, $x, $y) = @_ ;
2608             my $txt = $self->{'text'} ;
2609              
2610             my ($idx, $col) = $txt->index("\@$x,$y") =~ /^(\d*)\.(\d*)$/;
2611              
2612             my $offset = $Devel::tkdb::linenumber_length + 1 ; # line number text + 1 space
2613              
2614             return undef if $col < $offset ; # no posting
2615              
2616             $col -= $offset ;
2617              
2618             local(*dbline) = $main::{'_<' . $self->{current_file}} ;
2619              
2620             return undef if( !defined $dbline[$idx] || $dbline[$idx] == 0 ) ; # no executable text, no real variable(?)
2621              
2622             my $data = $dbline[$idx] ;
2623              
2624             # if we're sitting over white space, leave
2625             my $len = length $data ;
2626             return unless $data && $col && $len > 0 ;
2627              
2628             return if substr($data, $col, 1) =~ /\s/ ;
2629              
2630             # walk backwards till we find some whitespace
2631              
2632             $col = $len if $len < $col ;
2633             while( --$col >= 0 ) {
2634             last if substr($data, $col, 1) =~ /[\s\$\@\%]/ ;
2635             }
2636              
2637             substr($data, $col) =~ /^([\$\@\%]\w+)/ ;
2638              
2639             return $1 ;
2640             }
2641              
2642             #
2643             # after DB::eval get's us a result
2644             #
2645             sub code_motion_eval {
2646             my ($self, @result) = @_;
2647             my $d = new Data::Dumper([]);
2648             $d->Terse(1);
2649             $d->Indent(2);
2650             $d->Values( [ $#result == 0 ? @result : \@result ]);
2651             my $str = $d->Dump();
2652             chomp($str) ;
2653             # Cut the string down to 1024 characters to keep from overloading the balloon window
2654             $self->{'expr_ballon_msg'} = "$self->{'balloon_expr'} = " . substr $str, 0, 1024 ;
2655             } # end of code motion eval
2656              
2657             #
2658             # Subroutine called when we enter DB::DB()
2659             # In other words when the target script 'stops'
2660             # in the Debugger
2661             #
2662             sub EnterActions {
2663             my($self) = @_ ;
2664              
2665             # $self->{'main_window'}->Unbusy() ;
2666             }
2667              
2668             #
2669             # Subroutine called when we return from DB::DB()
2670             # When the target script resumes.
2671             #
2672             sub LeaveActions {
2673             my($self) = @_ ;
2674              
2675             # $self->{'main_window'}->Busy() ;
2676             }
2677              
2678              
2679             sub BEGIN {
2680             $Devel::tkdb::scriptName = $0 ;
2681             @Devel::tkdb::script_args = @ARGV ; # copy args
2682             }
2683              
2684             ##
2685             ## Save the ptkdb state file and restart the debugger
2686             ##
2687             sub DoRestart {
2688             my($fname) ;
2689              
2690             $fname = $ENV{'TMP'} || $ENV{'TMPDIR'} || $ENV{'TMP_DIR'} || $ENV{'TEMP'} || $ENV{'HOME'} ;
2691             $fname .= '/' if $fname ;
2692             $fname = "" unless $fname ;
2693              
2694             $fname .= "ptkdb_restart_state$$" ;
2695              
2696             # print "saving temp state file $fname\n" ;
2697              
2698             &DB::save_state_file($fname) ;
2699              
2700             $ENV{'PTKDB_RESTART_STATE_FILE'} = $fname ;
2701              
2702             ##
2703             ## build up the command to do the restart
2704             ##
2705              
2706             $fname = "perl -w -d:tkdb $Devel::tkdb::scriptName @Devel::tkdb::script_args" ;
2707              
2708             # print "$$ doing a restart with $fname\n" ;
2709              
2710             exec $fname ;
2711              
2712             } # end of DoRestart
2713              
2714             ##
2715             ## Enables/Disables the feature where we stop
2716             ## if we've encountered a perl warning such as:
2717             ## "Use of uninitialized value at undef_warn.pl line N"
2718             ##
2719              
2720             sub stop_on_warning_cb {
2721             &$DB::tkdb::warn_sig_save() if $DB::tkdb::warn_sig_save ; # call any previously registered warning
2722             $DB::window->DoAlert(@_) ;
2723             $DB::single = 1 ; # forces debugger to stop next time
2724             }
2725              
2726             sub set_stop_on_warning {
2727              
2728             if( $DB::tkdb::stop_on_warning ) {
2729              
2730             return if $DB::tkdb::warn_sig_save == \&stop_on_warning_cb ; # prevents recursion
2731              
2732             $DB::tkdb::warn_sig_save = $SIG{'__WARN__'} if $SIG{'__WARN__'} ;
2733             $SIG{'__WARN__'} = \&stop_on_warning_cb ;
2734             }
2735             else {
2736             ##
2737             ## Restore any previous warning signal
2738             ##
2739             local($^W) = 0 ;
2740             $SIG{'__WARN__'} = $DB::tkdb::warn_sig_save ;
2741             }
2742             } # end of set_stop_on_warning
2743              
2744             # end of Devel::tkdb
2745              
2746             package DB;
2747              
2748             use vars '$VERSION';
2749             use vars '@dbline', '%dbline';
2750              
2751             $VERSION = '2.0';
2752             $DB::window->{current_file} = "" ;
2753              
2754             #
2755             # Here's the clue...
2756             # eval only seems to eval the context of
2757             # the executing script while in the DB
2758             # package. When we had updateExprs in the Devel::tkdb
2759             # package eval would turn up an undef result.
2760             #
2761              
2762             sub updateExprs {
2763             my ($package) = @_ ;
2764             #
2765             # Update expressions
2766             #
2767             $DB::window->deleteAllExprs();
2768              
2769             foreach my $expr (@{$DB::window->{'expr_list'}}) {
2770             next if length $expr == 0 ;
2771              
2772             my @result = &DB::dbeval($package, $expr->{'expr'}) ;
2773              
2774             my $r = (@result==1?$result[0]:\@result);
2775             $DB::window->insertExpr([$r], $r, $expr->{'expr'}, $expr->{'depth'},'root');
2776             }
2777             } # end of updateExprs
2778              
2779             #no strict ; # turning strict off (shame shame) because we keep getting errrs for the local(*dbline)
2780              
2781             #
2782             # returns true if line is breakable
2783             #
2784             sub checkdbline($$) {
2785             my ($fname, $lineno) = @_ ;
2786              
2787             return 0 unless $fname; # we're getting an undef here on 'Restart...'
2788              
2789             local($^W) = 0 ; # spares us warnings under -w
2790             local(*dbline) = $main::{'_<' . $fname} ;
2791              
2792             my $flag = $dbline[$lineno] != 0 ;
2793              
2794             return $flag;
2795              
2796             } # end of checkdbline
2797              
2798             #
2799             # sets a breakpoint 'through' a magic
2800             # variable that perl is able to interpert
2801             #
2802             sub setdbline($$$) {
2803             my ($fname, $lineno, $value) = @_ ;
2804             local(*dbline) = $main::{'_<' . $fname};
2805              
2806             $dbline{$lineno} = $value ;
2807             } # end of setdbline
2808              
2809             sub getdbline($$) {
2810             my ($fname, $lineno) = @_ ;
2811             local(*dbline) = $main::{'_<' . $fname};
2812             return $dbline{$lineno} ;
2813             } # end of getdbline
2814              
2815             sub getdbtextline {
2816             my ($fname, $lineno) = @_ ;
2817             local(*dbline) = $main::{'_<' . $fname};
2818             return $dbline[$lineno] ;
2819             } # end of getdbline
2820              
2821              
2822             sub cleardbline($$;&) {
2823             my ($fname, $lineno, $clearsub) = @_ ;
2824             local(*dbline) = $main::{'_<' . $fname};
2825             my $value ; # just in case we want it for something
2826              
2827             $value = $dbline{$lineno} ;
2828             delete $dbline{$lineno} ;
2829             &$clearsub($value) if $value && $clearsub ;
2830              
2831             return $value ;
2832             } # end of cleardbline
2833              
2834             sub clearalldblines(;&) {
2835             my ($clearsub) = @_ ;
2836             my ($key, $value, $brkPt, $dbkey) ;
2837             local(*dbline) ;
2838              
2839             while ( ($key, $value) = each %main:: ) { # key loop
2840             next unless $key =~ /^_
2841             *dbline = $value ;
2842              
2843             foreach $dbkey (keys %dbline) {
2844             $brkPt = $dbline{$dbkey} ;
2845             delete $dbline{$dbkey} ;
2846             next unless $brkPt && $clearsub ;
2847             &$clearsub($brkPt) ; # if specificed, call the sub routine to clear the breakpoint
2848             }
2849              
2850             } # end of key loop
2851              
2852             } # end of clearalldblines
2853              
2854             sub getdblineindexes {
2855             my ($fname) = @_ ;
2856             local(*dbline) = $main::{'_<' . $fname} ;
2857             return keys %dbline ;
2858             } # end of getdblineindexes
2859              
2860             sub getbreakpoints {
2861             my (@fnames) = @_;
2862             my @retList;
2863              
2864             for my $fname (@fnames) {
2865             next unless $main::{'_<' . $fname};
2866             local(*dbline) = $main::{'_<' . $fname};
2867             push @retList, values %dbline;
2868             }
2869             return @retList;
2870             } # end of getbreakpoints
2871              
2872             #
2873             # Construct a hash of the files that have breakpoints to save
2874             #
2875             sub breakpoints_to_save {
2876             my (@breaks);
2877             my $brkList = {};
2878              
2879             for my $file ( keys %main:: ) { # file loop
2880             next unless $file =~ /^_
2881             local(*dbline) = $main::{$file};
2882              
2883             next unless @breaks = values %dbline;
2884              
2885             $brkList->{$file} = [map { { %$_ } } @breaks]; # list of anon.hashes
2886             } # end of file loop
2887              
2888             return $brkList;
2889              
2890             } # end of breakpoints_to_save
2891              
2892             #
2893             # When we restore breakpoints from a state file
2894             # they've often 'moved' because the file has been editted.
2895             #
2896             # We search for the line starting with the original line number,
2897             # then we walk it back 20 lines, then with line right after the
2898             # orginal line number and walk forward 20 lines.
2899             #
2900             # NOTE: dbline is expected to be 'local' when called
2901             #
2902             sub fix_breakpoints {
2903             my(@brkPts) = @_ ;
2904             my (@retList) ;
2905             local($^W) = 0;
2906              
2907             my $nLines = scalar @dbline;
2908              
2909             for my $brkPt (@brkPts) {
2910              
2911             my $startLine = $brkPt->{'line'} > 20 ? $brkPt->{'line'} - 20 : 0 ;
2912             my $endLine = $brkPt->{'line'} < $nLines - 20 ? $brkPt->{'line'} + 20 : $nLines;
2913              
2914             for( (reverse $startLine..$brkPt->{'line'}), $brkPt->{'line'} + 1 .. $endLine ) {
2915             next unless $brkPt->{'text'} eq $dbline[$_] ;
2916             $brkPt->{'line'} = $_ ;
2917             push @retList, $brkPt ;
2918             last;
2919             }
2920             } # end of breakpoint list
2921              
2922             return @retList;
2923             } # end of fix_breakpoints
2924              
2925             #
2926             # Restore breakpoints saved above
2927             #
2928             sub restore_breakpoints_from_save {
2929             my ($brkList) = @_ ;
2930             my ($key, $list, $brkPt, @newList) ;
2931              
2932             while ( ($key, $list) = each %$brkList ) { # reinsert loop
2933             next unless exists $main::{$key} ;
2934             local(*dbline) = $main::{$key} ;
2935              
2936             my $offset = 0;
2937             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?tkdb/ ;
2938              
2939             @newList = fix_breakpoints(@$list) ;
2940              
2941             foreach $brkPt ( @newList ) {
2942             if( !&DB::checkdbline($key, $brkPt->{'line'} + $offset) ) {
2943             print "Breakpoint $key:$brkPt->{'line'} in config file is not breakable.\n" ;
2944             next ;
2945             }
2946             $dbline{$brkPt->{'line'}} = { %$brkPt } ; # make a fresh copy
2947             }
2948             } # end of reinsert loop
2949              
2950             } # end of restore_breakpoints_from_save ;
2951              
2952             sub dbint_handler {
2953             my($sigName) = @_;
2954             $DB::single = 1;
2955             print STDERR "signalled\n";
2956             } # end of dbint_handler
2957              
2958             #
2959             # Do first time initialization at the startup of DB::DB
2960             #
2961             my $isInitialized=0;
2962             sub Initialize {
2963             my ($fName) = @_ ;
2964             $isInitialized = 1;
2965              
2966             $DB::window = new Devel::tkdb;
2967              
2968             $DB::window->do_user_init_files();
2969              
2970             $DB::dbint_handler_save = $SIG{'INT'} unless $DB::sigint_disable ; # saves the old handler
2971             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ;
2972              
2973             # Save the file name we started up with
2974             $DB::startupFname = $fName ;
2975              
2976             # Check for a 'restart' file
2977              
2978             if( $ENV{'PTKDB_RESTART_STATE_FILE'} && -e $ENV{'PTKDB_RESTART_STATE_FILE'} ) {
2979             ##
2980             ## Restore expressions and breakpoints in state file
2981             ##
2982             $DB::window->restoreStateFile($ENV{'PTKDB_RESTART_STATE_FILE'}) ;
2983             unlink $ENV{'PTKDB_RESTART_STATE_FILE'} ; # delete state file
2984              
2985             # print "restoring state from $ENV{'PTKDB_RESTART_STATE_FILE'}\n" ;
2986              
2987             $ENV{'PTKDB_RESTART_STATE_FILE'} = "" ; # clear entry
2988             }
2989             else {
2990             &DB::restoreState($fName);
2991             }
2992              
2993             } # end of Initialize
2994              
2995             sub restoreState {
2996             my ($fName) = @_;
2997              
2998             my $stateFile = makeFileSaveName($fName);
2999             if( -e $stateFile && -r $stateFile ) {
3000             my ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $DB::window->get_state($stateFile) ;
3001             &DB::restore_breakpoints_from_save($files) ;
3002             $DB::window->{'expr_list'} = $expr_list if defined $expr_list ;
3003             $DB::window->{eval_saved_text} = $eval_saved_text ;
3004              
3005             if ($main_win_geometry) {
3006             # restore the height and width of the window
3007             $DB::window->{main_window}->geometry($main_win_geometry) ;
3008             }
3009             }
3010              
3011             } # end of Restore State
3012              
3013             sub makeFileSaveName {
3014             return "$_[0].ptkdb";
3015             }
3016              
3017             sub save_state_file {
3018             my($fname) = @_ ;
3019              
3020             my $files = &DB::breakpoints_to_save();
3021              
3022             my $d = Data::Dumper->new( [ $files, $DB::window->{'expr_list'}, "" ],
3023             [ "files", "expr_list", "eval_saved_text" ] ) ;
3024             $d->Purity(1) ;
3025              
3026             local(*F) ;
3027             open F, ">$fname" || die "Couldn't open file $fname" ;
3028             print F $d->Dump() || die "Couldn't write file" ;
3029             close F ;
3030             } # end of save_state_file
3031              
3032             sub SaveState {
3033             my($name_in) = @_ ;
3034             my ($eval_saved_text);
3035             my $win = $DB::window;
3036              
3037             #
3038             # Extract the height and width of our window
3039             #
3040             my $main_win_geometry = $win->{main_window}->geometry ;
3041              
3042             if ( defined $win->{save_box} ) {
3043             $win->{save_box}->raise ;
3044             $win->{save_box}->focus ;
3045             return ;
3046             }
3047              
3048             my $saveName = $name_in || makeFileSaveName($DB::startupFname) ;
3049              
3050              
3051             my $saveSub = sub {
3052             delete $win->{save_box} ;
3053              
3054             if( exists $win->{eval_window} ) {
3055             $eval_saved_text = $win->{eval_text}->get('1.0', 'end') ;
3056             }
3057             else {
3058             $eval_saved_text = $win->{eval_saved_text} ;
3059             }
3060              
3061             my $files = &DB::breakpoints_to_save();
3062              
3063             my $d = Data::Dumper->new( [ $files, $win->{'expr_list'}, $eval_saved_text, $main_win_geometry ],
3064             [ "files", "expr_list", "eval_saved_text", "main_win_geometry"] ) ;
3065             $d->Purity(1) ;
3066             local(*F) ;
3067             eval {
3068             open F, ">$saveName" || die "Couldn't open file $saveName" ;
3069             print F $d->Dump() || die "Couldn't write file" ;
3070             close F ;
3071             };
3072             $win->DoAlert($@) if $@ ;
3073             $win->{int}->Eval('set event null');
3074             }; # end of save sub
3075              
3076             my $cancelSub = sub {
3077             delete $win->{'save_box'}
3078             } ; # end of cancel sub
3079              
3080             #
3081             # Create a dialog
3082             #
3083              
3084             $win->{'save_box'} = $win->simplePromptBox("Save Config?", $saveName, $saveSub, $cancelSub) ;
3085              
3086             } # end of SaveState
3087              
3088             sub RestoreState {
3089             $DB::window->simplePromptBox("Restore Config?", makeFileSaveName($DB::startupFname), sub {
3090             $DB::window->restoreStateFile($Devel::tkdb::promptString);
3091             });
3092             } # end of RestoreState
3093              
3094             sub SetStepOverBreakPoint {
3095             my ($offset) = @_ ;
3096             $DB::step_over_depth = $DB::subroutine_depth + ($offset ? $offset : 0) ;
3097             } # end of SetStepOverBreakPoint
3098              
3099             #
3100             # NOTE: It may be logical and somewhat more economical
3101             # lines of codewise to set $DB::step_over_depth_saved
3102             # when we enter the subroutine, but this gets called
3103             # for EVERY callable line of code in a program that
3104             # is being debugged, so we try to save every line of
3105             # execution that we can.
3106             #
3107             sub isBreakPoint {
3108             my ($fname, $line, $package) = @_ ;
3109              
3110             if ( $DB::single && ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth > 0) && !$DB::on) {
3111             $DB::single = 0 ;
3112             return 0 ;
3113             }
3114             #
3115             # doing a step over/in
3116             #
3117              
3118             if( $DB::single || $DB::signal ) {
3119             $DB::single = 0 ;
3120             $DB::signal = 0 ;
3121             $DB::subroutine_depth = $DB::subroutine_depth ;
3122             return 1 ;
3123             }
3124             #
3125             # 1st Check to see if there is even a breakpoint there.
3126             # 2nd If there is a breakpoint check to see if it's check box control is 'on'
3127             # 3rd If there is any kind of expression, evaluate it and see if it's true.
3128             #
3129             my $brkPt = &DB::getdbline($fname, $line) ;
3130              
3131             return 0 if( !$brkPt || !$brkPt->{'value'} || !breakPointEvalExpr($brkPt, $package) ) ;
3132              
3133             &DB::cleardbline($fname, $line) if( $brkPt->{'type'} eq 'temp' ) ;
3134              
3135             $DB::subroutine_depth = $DB::subroutine_depth ;
3136              
3137             return 1 ;
3138             } # end of isBreakPoint
3139              
3140             #
3141             # Check the breakpoint expression to see if it is true.
3142             #
3143             sub breakPointEvalExpr {
3144             my ($brkPt, $package) = @_ ;
3145             my (@result) ;
3146              
3147             return 1 unless $brkPt->{expr} ; # return if there is no expression
3148              
3149             no strict ;
3150             @result = &DB::dbeval($package, $brkPt->{'expr'}) ;
3151             use strict ;
3152              
3153             $DB::window->DoAlert($@) if $@ ;
3154              
3155             return $result[0] or @result ; # we could have a case where the 1st element is undefined
3156             # but subsequent elements are defined
3157              
3158             } # end of breakPointEvalExpr
3159              
3160             #
3161             # Evaluate the given expression, return the result.
3162             # MUST BE CALLED from within DB::DB in order for it
3163             # to properly interpret the vars
3164             #
3165             sub dbeval {
3166             my($ptkdb__package, $ptkdb__expr) = @_ ;
3167             my(@ptkdb__result, $ptkdb__str) ;
3168             my(@ptkdb_args) ;
3169             local($^W) = 0 ; # temporarily turn off warnings
3170              
3171             no strict ;
3172             #
3173             # This substitution is done so that
3174             # we return HASH, as opposed to an ARRAY.
3175             # An expression of %hash results in a
3176             # list of key/value pairs.
3177             #
3178              
3179             $ptkdb__expr =~ s/^\s*%/\\%/;
3180              
3181             @_ = @DB::saved_args ; # replace @_ arg array with what we came in with
3182              
3183             @ptkdb__result = eval <<__EVAL__ ;
3184              
3185              
3186             \$\@ = \$DB::save_err ;
3187              
3188             package $ptkdb__package;
3189              
3190             $ptkdb__expr;
3191              
3192             __EVAL__
3193              
3194             @ptkdb__result = ("ERROR ($@)") if $@ ;
3195              
3196             use strict ;
3197              
3198             return @ptkdb__result ;
3199             } # end of dbeval
3200              
3201             #
3202             # Call back we give to our 'quit' button
3203             # and binding to the WM_DELETE_WINDOW protocol
3204             # to quit the debugger.
3205             #
3206             sub dbexit {
3207             print STDERR "dbexit\n";
3208             exit ;
3209             } # end of dbexit
3210              
3211             #
3212             # This is the primary entry point for the debugger. When a perl program
3213             # is parsed with the -d(in our case -d:tkdb) option set the parser will
3214             # insert a call to DB::DB in front of every excecutable statement.
3215             #
3216             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
3217             #
3218              
3219             sub DB {
3220             @DB::saved_args = @_ ; # save arg context
3221             $DB::save_err = $@ ; # save value of $@
3222             my ($package, $filename, $line) = caller ;
3223              
3224             unless( $isInitialized ) {
3225             return if( $filename ne $0 ) ; # not in our target file
3226             &DB::Initialize($filename) ;
3227             }
3228              
3229             if (!isBreakPoint($filename, $line, $package) ) {
3230             $DB::single = 0;
3231             $@ = $DB::save_err;
3232             return;
3233             }
3234              
3235             if ( !$DB::window ) { # not setup yet
3236             $@ = $DB::save_err;
3237             return;
3238             }
3239              
3240             $DB::window->setup_main_window() unless $DB::window->{'main_window'} ;
3241              
3242             $DB::window->EnterActions() ;
3243              
3244             my ($saveP) = $^P;
3245             $^P = 0 ;
3246              
3247             $DB::on = 1 ;
3248              
3249             #
3250             # The user can specify this variable in one of the startup files,
3251             # this will make the debugger run right after startup without
3252             # the user having to press the 'run' button.
3253             #
3254             if( $DB::no_stop_at_start ) {
3255             $DB::no_stop_at_start = 0 ;
3256             $DB::on = 0 ;
3257             $@ = $DB::save_err ;
3258             return ;
3259             }
3260              
3261             if( !$DB::sigint_disable ) {
3262             $SIG{'INT'} = $DB::dbint_handler_save if $DB::dbint_handler_save ; # restore original signal handler
3263             $SIG{'INT'} = "DB::dbexit" unless $DB::dbint_handler_save ;
3264             }
3265              
3266             #$DB::window->{main_window}->raise() ; # bring us to the top make sure OUR event loop runs
3267             $DB::window->{main_window}->focus() ;
3268              
3269             $DB::window->set_file($filename, $line) ;
3270             #
3271             # Refresh the exprs to see if anything has changed
3272             #
3273             updateExprs($package) ;
3274              
3275             #
3276             # Update subs Page if necessary
3277             #
3278             my $cnt = scalar keys %DB::sub ;
3279             if ( $cnt != $DB::window->{'subs_list_cnt'} && $DB::window->{'subs_page_activated'} ) {
3280             $DB::window->fill_subs_page();
3281             $DB::window->{'subs_list_cnt'} = $cnt;
3282             }
3283             #
3284             # Update the subroutine stack menu
3285             #
3286             $DB::window->refresh_stack_menu() ;
3287             $DB::window->{run_flag} = 1 ;
3288              
3289             my ($evt, @result, $r) ;
3290              
3291             for( ; ; ) {
3292             #
3293             # we wait here for something to do
3294             #
3295             $evt = $DB::window->main_loop() ;
3296              
3297             last if( $evt eq 'step' ) ;
3298              
3299             $DB::single = 0 if ($evt eq 'run' ) ;
3300              
3301             if ($evt eq 'balloon_eval' ) {
3302             $DB::window->code_motion_eval(&DB::dbeval($package, $DB::window->{'balloon_expr'})) ;
3303             next ;
3304             }
3305              
3306             if ( $evt eq 'qexpr' ) {
3307             @result = &DB::dbeval($package, $DB::window->{'qexpr'}) ;
3308             $DB::window->{'quick_entry'}->delete(0, 'end') ; # clear old text
3309             $DB::window->{'quick_dumper'}->Reset() ;
3310             $DB::window->{'quick_dumper'}->Values( [ $#result == 0 ? @result : \@result ] ) ;
3311             $DB::window->{'quick_entry'}->insert(0, $DB::window->{'quick_dumper'}->Dump());
3312             $DB::window->{'quick_entry'}->selectionRange(0, 'end') ; # select it
3313             $evt = 'update' ; # force an update on the expressions
3314             }
3315              
3316             if( $evt eq 'expr' ) {
3317             #
3318             # Append the new expression to the list
3319             # but first check to make sure that we don't already have it.
3320             #
3321              
3322             if ( grep $_->{'expr'} eq $DB::window->{'expr'}, @{$DB::window->{'expr_list'}} ) {
3323             $DB::window->DoAlert("$DB::window->{'expr'} is already listed") ;
3324             next ;
3325             }
3326              
3327             @result = &DB::dbeval($package, $DB::window->{expr}) ;
3328             my $rr = (@result == 1? $result[0] : \@result);
3329             $r = $DB::window->insertExpr([ $rr ], $rr, $DB::window->{'expr'}, -1,'root') ;
3330              
3331             #
3332             # $r will be 1 if the expression was added succesfully, 0 if not,
3333             # and it if wasn't added sucessfully it won't be reevalled the
3334             # next time through.
3335             #
3336             push @{$DB::window->{'expr_list'}}, { 'expr' => $DB::window->{'expr'}, 'depth' => -1 } if $r;
3337              
3338             next;
3339             }
3340             if( $evt eq 'update' ) {
3341             updateExprs($package);
3342             next;
3343             }
3344             if( $evt eq 'reeval' ) {
3345             #
3346             # Reevaluate the contents of the expression eval window
3347             my $txt = $DB::window->{'eval_text'}->get('1.0', 'end') ;
3348             my @result = &DB::dbeval($package, $txt) ;
3349              
3350             $DB::window->updateEvalWindow(@result) ;
3351              
3352             next ;
3353             }
3354             last ;
3355             }
3356             $^P = $saveP ;
3357             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ; # set our signal handler
3358              
3359             $DB::window->LeaveActions() ;
3360              
3361             $@ = $DB::save_err ;
3362             $DB::on = 0 ;
3363             } # end of DB
3364              
3365             ##
3366             ## in this case we do not use local($^W) since we would like warnings
3367             ## to be issued past this point, and the localized copy of $^W will not
3368             ## go out of scope until the end of compilation
3369             ##
3370             ##
3371              
3372             #
3373             # This is another place where we'll try and keep the
3374             # code as 'lite' as possible to prevent the debugger
3375             # from slowing down the user's application
3376             #
3377             # When a perl program is parsed with the -d(in our case a -d:tkdb) option
3378             # the parser will route all subroutine calls through here, setting $DB::sub
3379             # to the name of the subroutine to be called, leaving it to the debugger to
3380             # make the actual subroutine call and do any pre or post processing it may
3381             # need to do. In our case we take the opportunity to track the depth of the call
3382             # stack so that we can update our 'Stack' menu when we stop.
3383             #
3384             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
3385             #
3386             #
3387             sub sub {
3388             my ($result, @result) ;
3389             #
3390             # See NOTES(1)
3391             #
3392             $DB::subroutine_depth += 1 unless $DB::on ;
3393             $DB::single = 0 if ( ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth >= 0) && !$DB::on) ;
3394              
3395             if( wantarray ) {
3396             # array context
3397              
3398             no strict ; # otherwise perl gripes about calling the sub by the reference
3399             @result = &$DB::sub ; # call the subroutine by name
3400             use strict ;
3401              
3402             $DB::subroutine_depth -= 1 unless $DB::on ;
3403             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3404             return @result;
3405              
3406             } elsif(defined wantarray) {
3407             # scalar context
3408              
3409             no strict;
3410             $result = &$DB::sub;
3411             use strict;
3412              
3413             $DB::subroutine_depth -= 1 unless $DB::on;
3414             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3415             return $result;
3416              
3417             } else {
3418             # void context
3419              
3420             no strict;
3421             &$DB::sub;
3422             use strict;
3423              
3424             $DB::subroutine_depth -= 1 unless $DB::on ;
3425             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on);
3426             return;
3427              
3428             }
3429              
3430             } # end of sub
3431              
3432             1; # return true value
3433