File Coverage

blib/lib/Devel/ptkdb.pm
Criterion Covered Total %
statement 1 4 25.0
branch n/a
condition n/a
subroutine 1 2 50.0
pod 0 1 0.0
total 2 7 28.5


line stmt bran cond sub pod time code
1              
2             package DB ;
3              
4             ##
5             ## Expedient fix for perl 5.8.0. True DB::DB is further down.
6             ##
7             ##
8 0     0 0   sub DB {}
9              
10              
11              
12 1     1   2941 use Tk ;
  0            
  0            
13              
14             #
15             # If you've loaded this file via a browser
16             # select "Save As..." from your file menu
17             #
18             # ptkdb Perl Tk perl Debugger
19             #
20             # Copyright 1998, 2003, Andrew E. Page
21             # All rights reserved.
22             #
23             # This program is free software; you can redistribute it and/or modify
24             # it under the terms of either:
25             #
26             # a) the GNU General Public License as published by the Free
27             # Software Foundation; either version 1, or (at your option) any
28             # later version, or
29             #
30             # b) the "Artistic License" which comes with this Kit.
31             #
32             # This program is distributed in the hope that it will be useful,
33             # but WITHOUT ANY WARRANTY; without even the implied warranty of
34             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either
35             # the GNU General Public License or the Artistic License for more details.
36             #
37              
38              
39             ####################################
40             ### Sample .Xresources for ptkdb ###
41             ####################################
42             # /*
43             # * Perl Tk Debugger XResources.
44             # * Note... These resources are subject to change.
45             # *
46             # * Use 'xfontsel' to select different fonts.
47             # *
48             # * Append these resource to ~/.Xdefaults | ~/.Xresources
49             # * and use xrdb -override ~/.Xdefaults | ~/.Xresources
50             # * to activate them.
51             # */
52             # /* Set Value to se to place scrollbars on the right side of windows
53             # CAUTION: extra whitespace at the end of the line is causing
54             # failures with Tk800.011.
55             # */
56             # ptkdb*scrollbars: sw
57             #
58             # /* controls where the code pane is oriented, down the left side, or across the top */
59             # /* values can be set to left, right, top, bottom */
60             # ptkdb*codeside: left
61             # /*
62             # * Background color for the balloon
63             # * CAUTION: For certain versions of Tk trailing
64             # * characters after the color produces an error
65             # */
66             # ptkdb.frame2.frame1.rotext.balloon.background: green
67             # ptkdb.frame2.frame1.rotext.balloon.font: fixed /* Hot Variable Balloon Font */
68             #
69             #
70             # ptkdb.frame*font: fixed /* Menu Bar */
71             # ptkdb.frame.menubutton.font: fixed /* File menu */
72             # ptkdb.frame2.frame1.rotext.font: fixed /* Code Pane */
73             # ptkdb.notebook.datapage.frame1.hlist.font: fixed /* Expression Notebook Page */
74             #
75             # ptkdb.notebook.subspage*font: fixed /* Subroutine Notebook Page */
76             # ptkdb.notebook.brkptspage*entry.font: fixed /* Delete Breakpoint Buttons */
77             # ptkdb.notebook.brkptspage*button.font: fixed /* Breakpoint Expression Entries */
78             # ptkdb.notebook.brkptspage*button1.font: fixed /* Breakpoint Expression Entries */
79             # ptkdb.notebook.brkptspage*checkbutton.font: fixed /* Breakpoint Checkbuttons */
80             # ptkdb.notebook.brkptspage*label.font: fixed /* Breakpoint "Cond" label */
81             #
82             # ptkdb.toplevel.frame.textundo.font: fixed /* Eval Expression Entry Window */
83             # ptkdb.toplevel.frame1.text.font: fixed /* Eval Expression Results Window */
84             # ptkdb.toplevel.button.font: fixed /* "Eval..." Button */
85             # ptkdb.toplevel.button1.font: fixed /* "Clear Eval" Button */
86             # ptkdb.toplevel.button2.font: fixed /* "Clear Results" Button */
87             # ptkdb.toplevel.button3.font: fixed /* "Clear Dismiss" Button */
88             #
89             #
90             # /*
91             # * Background color for where the debugger has stopped
92             # */
93             # ptkdb*stopcolor: blue
94             #
95             # /*
96             # * Background color for set breakpoints
97             # */
98             # ptkdb*breaktagcolor: red
99             #
100             # /*
101             # * Font for where the debugger has stopped
102             # */
103             # ptkdb*stopfont: -*-fixed-bold-*-*-*-*-*-*-*-*-*-*-*
104             #
105             # /*
106             # * Background color for the search tag
107             # */
108             # ptkdb*searchtagcolor: green
109              
110             use strict ;
111             use vars qw($VERSION @dbline %dbline);
112              
113              
114             #
115             # This package is the main_window object
116             # for the debugger. We start with the Devel::
117             # prefix because we want to install it with
118             # the DB:: package that is required to be in a Devel/
119             # subdir of a directory in the @INC set.
120             #
121             package Devel::ptkdb ;
122              
123             ##
124             ## do this check once, rather than repeating the string comparison again and again
125             ##
126              
127              
128             my $isWin32 = $^O eq 'MSWin32' ;
129              
130             =head1 NAME
131              
132             Devel::ptkdb - Perl debugger using a Tk GUI
133              
134             =head1 DESCRIPTION
135              
136             ptkdb is a debugger for perl that uses perlTk for a user interface.
137             Features include:
138              
139             Hot Variable Inspection
140             Breakpoint Control Panel
141             Expression List
142             Subroutine Tree
143            
144              
145             =begin html
146              
147            
148              
149             =end html
150              
151             =head1 SYNOPSIS
152              
153             To debug a script using ptkdb invoke perl like this:
154              
155             perl -d:ptkdb myscript.pl
156              
157             =head1 Usage
158              
159             perl -d:ptkdb myscript.pl
160              
161             =head1 Code Pane
162              
163             =over 4
164              
165             =item Line Numbers
166              
167             Line numbers are presented on the left side of the window. Lines that
168             have lines through them are not breakable. Lines that are plain text
169             are breakable. Clicking on these line numbers will insert a
170             breakpoint on that line and change the line number color to
171             $ENV{'PTKDB_BRKPT_COLOR'} (Defaults to Red). Clicking on the number
172             again will remove the breakpoint. If you disable the breakpoint with
173             the controls on the BrkPt notebook page the color will change to
174             $ENV{'PTKDB_DISABLEDBRKPT_COLOR'}(Defaults to Green).
175              
176             =item Cursor Motion
177              
178             If you place the cursor over a variable (i.e. $myVar, @myVar, or
179             %myVar) and pause for a second the debugger will evaluate the current
180             value of the variable and pop a balloon up with the evaluated
181             result. I
182              
183             If Data::Dumper(standard with perl5.00502)is available it will be used
184             to format the result. If there is an active selection, the text of
185             that selection will be evaluated.
186              
187             =back
188              
189             =head1 Notebook Pane
190              
191             =over 2
192              
193             =item Exprs
194              
195             This is a list of expressions that are evaluated each time the
196             debugger stops. The results of the expresssion are presented
197             heirarchically for expression that result in hashes or lists. Double
198             clicking on such an expression will cause it to collapse; double
199             clicking again will cause the expression to expand. Expressions are
200             entered through B entry, or by Alt-E when text is
201             selected in the code pane.
202              
203             The B entry, will take an expression, evaluate it, and
204             replace the entries contents with the result. The result is also
205             transfered to the 'clipboard' for pasting.
206              
207             =item Subs
208              
209             Displays a list of all the packages invoked with the script
210             heirarchially. At the bottom of the heirarchy are the subroutines
211             within the packages. Double click on a package to expand
212             it. Subroutines are listed by their full package names.
213              
214             =item BrkPts
215              
216             Presents a list of the breakpoints current in use. The pushbutton
217             allows a breakpoint to be 'disabled' without removing it. Expressions
218             can be applied to the breakpoint. If the expression evaluates to be
219             'true'(results in a defined value that is not 0) the debugger will
220             stop the script. Pressing the 'Goto' button will set the text pane
221             to that file and line where the breakpoint is set. Pressing the
222             'Delete' button will delete the breakpoint.
223              
224             =back
225              
226             =head1 Menus
227              
228             =head2 File Menu
229              
230             =over
231              
232             =item About...
233              
234             Presents a dialog box telling you about the version of ptkdb. It
235             recovers your OS name, version of perl, version of Tk, and some other
236             information
237              
238             =item Open
239              
240             Presents a list of files that are part of the invoked perl
241             script. Selecting a file from this list will present this file in the
242             text window.
243              
244             =item Save Config...
245              
246             Requires Data::Dumper. Prompts for a filename to save the
247             configuration to. Saves the breakpoints, expressions, eval text and
248             window geometry. If the name given as the default is used and the
249             script is reinvoked, this configuration will be reloaded
250             automatically.
251              
252             B You may find this preferable to using
253              
254             =item Restore Config...
255              
256             Requires Data::Dumper. Prompts for a filename to restore a configuration saved with
257             the "Save Config..." menu item.
258              
259             =item Goto Line...
260              
261             Prompts for a line number. Pressing the "Okay" button sends the window to the line number entered.
262             item Find Text...
263              
264             Prompts for text to search for. Options include forward search,
265             backwards search, and regular expression searching.
266              
267             =item Quit
268              
269             Causes the debugger and the target script to exit.
270              
271             =back
272              
273             =head2 Control Menu
274              
275             =over
276              
277             =item Run
278              
279             The debugger allows the script to run to the next breakpoint or until the script exits.
280             item Run To Here
281              
282             Runs the debugger until it comes to wherever the insertion cursor
283             in text window is placed.
284              
285             =item Set Breakpoint
286              
287             Sets a breakpoint on the line at the insertion cursor.
288             item Clear Breakpoint
289              
290             Remove a breakpoint on the at the insertion cursor.
291              
292             =item Clear All Breakpoints
293              
294             Removes all current breakpoints
295              
296             =item Step Over
297              
298             Causes the debugger to step over the next line. If the line is a
299             subroutine call it steps over the call, stopping when the subroutine
300             returns.
301              
302             =item Step In
303              
304             Causes the debugger to step into the next line. If the line is a
305             subroutine call it steps into the subroutine, stopping at the first
306             executable line within the subroutine.
307              
308             =item Return
309              
310             Runs the script until it returns from the currently executing
311             subroutine.
312              
313             =item Restart
314              
315             Saves the breakpoints and expressions in a temporary file and restarts
316             the script from the beginning. CAUTION: This feature will not work
317             properly with debugging of CGI Scripts.
318              
319             =item Stop On Warning
320              
321             When C<-w> is enabled the debugger will stop when warnings such as, "Use
322             of uninitialized value at undef_warn.pl line N" are encountered. The debugger
323             will stop on the NEXT line of execution since the error can't be detected
324             until the current line has executed.
325              
326             This feature can be turned on at startup by adding:
327              
328             $DB::ptkdb::stop_on_warning = 1 ;
329              
330             to a .ptkdbrc file
331              
332             =back
333              
334             =head2 Data Menu
335              
336             =over
337              
338             =item Enter Expression
339              
340             When an expression is entered in the "Enter Expression:" text box,
341             selecting this item will enter the expression into the expression
342             list. Each time the debugger stops this expression will be evaluated
343             and its result updated in the list window.
344              
345             =item Delete Expression
346              
347             Deletes the highlighted expression in the expression window.
348              
349             =item Delete All Expressions
350              
351             Delete all expressions in the expression window.
352              
353             =item Expression Eval Window
354              
355             Pops up a two pane window. Expressions of virtually unlimitted length
356             can be entered in the top pane. Pressing the 'Eval' button will cause
357             the expression to be evaluated and its placed in the lower pane. If
358             Data::Dumper is available it will be used to format the resulting
359             text. Undo is enabled for the text in the upper pane.
360              
361             HINT: You can enter multiple expressions by separating them with commas.
362              
363             =item Use Data::Dumper for Eval Window
364              
365             Enables or disables the use of Data::Dumper for formatting the results
366             of expressions in the Eval window.
367              
368             =back
369              
370             =head2 Stack Menu
371              
372             Maintains a list of the current subroutine stack each time the
373             debugger stops. Selecting an item from this menu will set the text in
374             the code window to that particular subourtine entry point.
375              
376             =head2 Bookmarks Menu
377              
378             Maintains a list of bookmarks. The booksmarks are saved in ~/.ptkdb_bookmarks
379              
380             =over
381              
382             =item Add Bookmark
383              
384             Adds a bookmark to the bookmark list.
385              
386             =back
387              
388             =head1 Options
389              
390             Here is a list of the current active XResources options. Several of
391             these can be overridden with environmental variables. Resources can be
392             added to .Xresources or .Xdefaults depending on your X configuration.
393             To enable these resources you must either restart your X server or use
394             the xrdb -override resFile command. xfontsel can be used to select
395             fonts.
396              
397             /*
398             * Perl Tk Debugger XResources.
399             * Note... These resources are subject to change.
400             *
401             * Use 'xfontsel' to select different fonts.
402             *
403             * Append these resource to ~/.Xdefaults | ~/.Xresources
404             * and use xrdb -override ~/.Xdefaults | ~/.Xresources
405             * to activate them.
406             */
407             /* Set Value to se to place scrollbars on the right side of windows
408             CAUTION: extra whitespace at the end of the line is causing
409             failures with Tk800.011.
410            
411             sw -> puts scrollbars on left, se puts scrollars on the right
412            
413             */
414             ptkdb*scrollbars: sw
415             /* controls where the code pane is oriented, down the left side, or across the top */
416             /* values can be set to left, right, top, bottom */
417             ptkdb*codeside: left
418            
419             /*
420             * Background color for the balloon
421             * CAUTION: For certain versions of Tk trailing
422             * characters after the color produces an error
423             */
424             ptkdb.frame2.frame1.rotext.balloon.background: green
425             ptkdb.frame2.frame1.rotext.balloon.font: fixed /* Hot Variable Balloon Font */
426            
427            
428             ptkdb.frame*font: fixed /* Menu Bar */
429             ptkdb.frame.menubutton.font: fixed /* File menu */
430             ptkdb.frame2.frame1.rotext.font: fixed /* Code Pane */
431             ptkdb.notebook.datapage.frame1.hlist.font: fixed /* Expression Notebook Page */
432            
433             ptkdb.notebook.subspage*font: fixed /* Subroutine Notebook Page */
434             ptkdb.notebook.brkptspage*entry.font: fixed /* Delete Breakpoint Buttons */
435             ptkdb.notebook.brkptspage*button.font: fixed /* Breakpoint Expression Entries */
436             ptkdb.notebook.brkptspage*button1.font: fixed /* Breakpoint Expression Entries */
437             ptkdb.notebook.brkptspage*checkbutton.font: fixed /* Breakpoint Checkbuttons */
438             ptkdb.notebook.brkptspage*label.font: fixed /* Breakpoint Checkbuttons */
439            
440             ptkdb.toplevel.frame.textundo.font: fixed /* Eval Expression Entry Window */
441             ptkdb.toplevel.frame1.text.font: fixed /* Eval Expression Results Window */
442             ptkdb.toplevel.button.font: fixed /* "Eval..." Button */
443             ptkdb.toplevel.button1.font: fixed /* "Clear Eval" Button */
444             ptkdb.toplevel.button2.font: fixed /* "Clear Results" Button */
445             ptkdb.toplevel.button3.font: fixed /* "Clear Dismiss" Button */
446            
447             /*
448             * Background color for where the debugger has stopped
449             */
450             ptkdb*stopcolor: blue
451            
452             /*
453             * Background color for set breakpoints
454             */
455             ptkdb*breaktagcolor*background: yellow
456             ptkdb*disabledbreaktagcolor*background: white
457             /*
458             * Font for where the debugger has stopped
459             */
460             ptkdb*stopfont: -*-fixed-bold-*-*-*-*-*-*-*-*-*-*-*
461            
462             /*
463             * Background color for the search tag
464             */
465             ptkdb*searchtagcolor: green
466              
467             =head1 Environmental Variables
468              
469             =over 4
470              
471             =item PTKDB_BRKPT_COLOR
472              
473             Sets the background color of a set breakpoint
474              
475             =item PTKDB_DISABLEDBRKPT_COLOR
476              
477             Sets the background color of a disabled breakpoint
478              
479             =item PTKDB_CODE_FONT
480              
481             Sets the font of the Text in the code pane.
482              
483             =item PTKDB_CODE_SIDE
484              
485             Sets which side the code pane is packed onto. Defaults to 'left'.
486             Can be set to 'left', 'right', 'top', 'bottom'.
487              
488             Overrides the Xresource ptkdb*codeside: I.
489              
490             =item PTKDB_EXPRESSION_FONT
491              
492             Sets the font used in the expression notebook page.
493              
494             =item PTKDB_EVAL_FONT
495              
496             Sets the font used in the Expression Eval Window
497              
498             =item PTKDB_EVAL_DUMP_INDENT
499              
500             Sets the value used for Data::Dumper 'indent' setting. See man Data::Dumper
501              
502             =item PTKDB_SCROLLBARS_ONRIGHT
503              
504             A non-zero value Sets the scrollbars of all windows to be on the
505             right side of the window. Useful for Windows users using ptkdb in an
506             XWindows environment.
507              
508             =item PTKDB_LINENUMBER_FORMAT
509              
510             Sets the format of line numbers on the left side of the window. Default value is %05d. useful
511             if you have a script that contains more than 99999 lines.
512              
513             =item PTKDB_DISPLAY
514              
515             Sets the X display that the ptkdb window will appear on when invoked. Useful for debugging CGI
516             scripts on remote systems.
517              
518             =item PTKDB_BOOKMARKS_PATH
519              
520             Sets the path of the bookmarks file. Default is $ENV{'HOME'}/.ptkdb_bookmarks
521              
522             =item PTKDB_STOP_TAG_COLOR
523              
524             Sets the color that highlights the line where the debugger is stopped
525              
526             =back
527              
528             =head1 FILES
529              
530             =head2 .ptkdbrc
531              
532             If this file is present in ~/ or in the directory where perl is
533             invoked the file will be read and executed as a perl script before the
534             debugger makes its initial stop at startup. There are several 'api'
535             calls that can be used with such scripts. There is an internal
536             variable $DB::no_stop_at_start that may be set to non-zero to prevent
537             the debugger from stopping at the first line of the script. This is
538             useful for debugging CGI scripts.
539              
540             There is a system ptkdbrc file in $PREFIX/lib/perl5/$VERS/Devel/ptkdbrc
541              
542             =over 4
543              
544             =item brkpt($fname, @lines)
545              
546             Sets breakspoints on the list of lines in $fname. A warning message
547             is generated if a line is not breakable.
548              
549             =item condbrkpt($fname, @($line, $expr) )
550              
551             Sets conditional breakpoints in $fname on pairs of $line and $expr. A
552             warning message is generated if a line is not breakable. NOTE: the
553             validity of the expression will not be determined until execution of
554             that particular line.
555              
556             =item brkonsub(@names)
557              
558             Sets a breakpoint on each subroutine name listed. A warning message is
559             generated if a subroutine does not exist. NOTE: for a script with no
560             other packages the default package is "main::" and the subroutines
561             would be "main::mySubs".
562              
563             =item brkonsub_regex(@regExprs)
564              
565             Uses the list of @regExprs as a list of regular expressions to set breakpoints. Sets breakpoints
566             on every subroutine that matches any of the listed regular expressions.
567              
568             =item textTagConfigure(tag, ?option?, ?value?)
569              
570             Allows the user to format the text in the code window. The option
571             value pairs are the same values as the option for the tagConfigure
572             method documented in Tk::Text. Currently the following tags are in
573             effect:
574              
575            
576             'code' Format for code in the text pane
577             'stoppt' Format applied to the line where the debugger is currently stopped
578             'breakableLine' Format applied to line numbers where the code is 'breakable'
579             'nonbreakableLine' Format applied to line numbers where the code is no breakable
580             'breaksetLine' Format applied to line numbers were a breakpoint is set
581             'breakdisabledLine' Format applied to line numbers were a disabled breakpoint is set
582             'search_tag' Format applied to text when located by a search.
583              
584             Example:
585              
586             #
587             # Turns off the overstrike on lines that you can't set a breakpoint on
588             # and makes the text color yellow.
589             #
590             textTagConfigure('nonbreakableLine', -overstrike => 0, -foreground => "yellow") ;
591              
592             =item add_exprs(@exprList)
593              
594             Add a list of expressions to the 'Exprs' window. NOTE: use the single
595             quote character \' to prevent the expression from being "evaluated" in
596             the string context.
597              
598              
599             Example:
600              
601             #
602             # Adds the $_ and @_ expressions to the active list
603             #
604              
605             add_exprs('$_', '@_') ;
606              
607             =back
608              
609             =head1 NOTES
610              
611             =head2 Debugging Other perlTk Applications
612              
613             ptkdb can be used to debug other perlTk applications if some cautions
614             are observed. Basically, do not click the mouse in the application's
615             window(s) when you've entered the debugger and do not click in the
616             debugger's window(s) while the application is running. Doing either
617             one is not necessarily fatal, but it can confuse things that are going
618             on and produce unexpected results.
619              
620             Be aware that most perlTk applications have a central event loop.
621             User actions, such as mouse clicks, key presses, window exposures, etc
622             will generate 'events' that the script will process. When a perlTk
623             application is running, its 'MainLoop' call will accept these events
624             and then dispatch them to appropriate callbacks associated with the
625             appropriate widgets.
626              
627             Ptkdb has its own event loop that runs whenever you've stopped at a
628             breakpoint and entered the debugger. However, it can accept events
629             that are generated by other perlTk windows and dispatch their
630             callbacks. The problem here is that the application is supposed to be
631             'stopped', and logically the application should not be able to process
632             events.
633              
634             A future version of ptkdb will have an extension that will 'filter'
635             events so that application events are not processed while the debugger
636             is active, and debugger events will not be processed while the target
637             script is active.
638              
639             =head2 Debugging CGI Scripts
640              
641             One advantage of ptkdb over the builtin debugger(-d) is that it can be
642             used to debug CGI perl scripts as they run on a web server. Be sure
643             that that your web server's perl instalation includes Tk.
644              
645             Change your
646              
647             #! /usr/local/bin/perl
648              
649             to
650              
651             #! /usr/local/bin/perl -d:ptkdb
652              
653             TIP: You can debug scripts remotely if you're using a unix based
654             Xserver and where you are authoring the script has an Xserver. The
655             Xserver can be another unix workstation, a Macintosh or Win32 platform
656             with an appropriate XWindows package. In your script insert the
657             following BEGIN subroutine:
658              
659             sub BEGIN {
660             $ENV{'DISPLAY'} = "myHostname:0.0" ;
661             }
662              
663             Be sure that your web server has permission to open windows on your
664             Xserver (see the xhost manpage).
665              
666             Access your web page with your browswer and 'submit' the script as
667             normal. The ptkdb window should appear on myHostname's monitor. At
668             this point you can start debugging your script. Be aware that your
669             browser may timeout waiting for the script to run.
670              
671             To expedite debugging you may want to setup your breakpoints in
672             advance with a .ptkdbrc file and use the $DB::no_stop_at_start
673             variable. NOTE: for debugging web scripts you may have to have the
674             .ptkdbrc file installed in the server account's home directory (~www)
675             or whatever username your webserver is running under. Also try
676             installing a .ptkdbrc file in the same directory as the target script.
677              
678             =head1 KNOWN PROBLEMS
679              
680             =over
681              
682             =item I
683              
684             If the size of the right hand pane is too small the breakpoint controls
685             are not visible. The breakpoints are still there, the window may have
686             to be enlarged in order for them to be visible.
687              
688             =item Balloons and Tk400
689              
690             The Balloons in Tk400 will not work with ptkdb. All other functions
691             are supported, but the Balloons require Tk800 or higher.
692              
693             =back
694              
695             =head1 AUTHOR
696              
697             Andrew E. Page, aepage@users.sourceforge.net
698              
699             =head1 ACKNOWLEDGEMENTS
700              
701             Matthew Persico For suggestions, and beta testing.
702              
703             =head1 BUG REPORTING
704              
705             Please report bugs through the following URL:
706              
707             http://sourceforge.net/tracker/?atid=437609&group_id=43854&func=browse
708              
709             =cut
710              
711              
712             require 5.004 ;
713              
714              
715             ##
716             ## Perform a check to see if we have the Tk library, if not, attempt
717             ## to load it for the user
718             ##
719              
720             sub BEGIN {
721              
722             eval {
723             require Tk ;
724             } ;
725             if( $@ ) {
726             print << "__PTKDBTK_INSTALL__" ;
727             ***
728             *** The PerlTk library could not be found. Ptkdb requires the PerlTk library.
729             ***
730             Preferably Tk800.015 or better:
731              
732             In order to install this the following conditions must be met:
733              
734             1. You have to have access to a C compiler.
735             2. You must have sufficient permissions to install the libraries on your system.
736              
737             To install PerlTk:
738              
739             a Download the Tk library source from http://www.perl.com/CPAN/modules/by-category/08_User_Interfaces/Tk
740             b Uncompress the archive and run "perl Makefile.PL"
741             c run "make install"
742              
743             If this process completes successfully ptkdb should be operational now.
744              
745             We can attempt to run the CPAN module for you. This will, after some questions, download
746             and install the Tk library automatically.
747              
748             Would you like to run the CPAN module? (y/n)
749             __PTKDBTK_INSTALL__
750              
751             my $answer = ;
752             chomp $answer ;
753             if( $answer =~ /y|yes/i) {
754             require CPAN ;
755             CPAN::install Tk ;
756             } # if
757              
758             } # if $@
759              
760              
761             } # end of sub BEGIN
762              
763             use Tk 800 ;
764             use Data::Dumper ;
765             use FileHandle ;
766              
767             require Tk::Dialog;
768             require Tk::TextUndo ;
769             require Tk::ROText;
770             require Tk::NoteBook ;
771             require Tk::HList ;
772             require Tk::Table ;
773              
774             use vars qw(@dbline) ;
775              
776             use Config ;
777              
778             sub DoBugReport {
779             my($str) = 'sourceforge.net/tracker/?atid=437609&group_id=43854&func=browse' ;
780             my(@browsers) = qw/netscape mozilla/ ;
781             my($fh, $pid, $sh) ;
782            
783             if( $isWin32 ) {
784             $sh = '' ;
785             @browsers = '"' . $ENV{'PROGRAMFILES'} . '\\Internet Explorer\\IEXPLORE.EXE' . '"' ;
786            
787             }
788             else {
789             $sh = 'sh' ;
790             $str = "\'http://" . $str . "\'" ;
791             }
792              
793             $fh = new FileHandle() ;
794              
795             for( @browsers ) {
796             $pid = open($fh, "$sh $_ $str 2&> /dev/null |") ;
797             sleep(2) ;
798             waitpid $pid, 0 ;
799             return if( $? == 0 ) ;
800             }
801              
802             print "##\n" ;
803             print "## Please submit a bug report through the following URL:\n" ;
804             print '## http://sourceforge.net/tracker/?atid=437609&group_id=43854&func=browse', "\n" ;
805             print "##\n" ;
806             }
807              
808             #
809             # Check to see if the package actually
810             # exists. If it does import the routines
811             # and return a true value ;
812             #
813             # NOTE: this needs to be above the 'BEGIN' subroutine,
814             # otherwise it will not have been compiled by the time
815             # that it is called by sub BEGIN.
816             #
817             sub check_avail {
818             my ($mod, @list) = @_ ;
819              
820             eval {
821             require $mod ; import $mod @list ;
822             } ;
823              
824             return 0 if $@ ;
825             return 1 ;
826              
827             } # end of check_avail
828              
829             sub BEGIN {
830              
831             $DB::on = 0 ;
832            
833             $DB::subroutine_depth = 0 ; # our subroutine depth counter
834             $DB::step_over_depth = -1 ;
835              
836             #
837             # the bindings and font specs for these operations have been placed here
838             # to make them accessible to people who might want to customize the
839             # operations. REF The 'bind.html' file, included in the perlTk FAQ has
840             # a fairly good explanation of the binding syntax.
841             #
842              
843             #
844             # These lists of key bindings will be applied
845             # to the "Step In", "Step Out", "Return" Commands
846             #
847             $Devel::ptkdb::pathSep = '\x00' ;
848             $Devel::ptkdb::pathSepReplacement = "\0x01" ;
849              
850             @Devel::ptkdb::step_in_keys = ( '', '', '' ) ; # step into a subroutine
851             @Devel::ptkdb::step_over_keys = ( '', '', '' ) ; # step over a subroutine
852             @Devel::ptkdb::return_keys = ( '', '' ) ; # return from a subroutine
853             @Devel::ptkdb::toggle_breakpt_keys = ( '' ) ; # set or unset a breakpoint
854              
855             # Fonts used in the displays
856            
857             #
858             # NOTE: The environmental variable syntax here works like this:
859             # $ENV{'NAME'} accesses the environmental variable "NAME"
860             #
861             # $ENV{'NAME'} || 'string' results in $ENV{'NAME'} or 'string' if $ENV{'NAME'} is not defined.
862             #
863             #
864            
865             @Devel::ptkdb::button_font = $ENV{'PTKDB_BUTTON_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ; # font for buttons
866             @Devel::ptkdb::code_text_font = $ENV{'PTKDB_CODE_FONT'} ? ( "-font" => $ENV{'PTKDB_CODE_FONT'} ) : () ;
867              
868             @Devel::ptkdb::expression_text_font = $ENV{'PTKDB_EXPRESSION_FONT'} ? ( "-font" => $ENV{'PTKDB_EXPRESSION_FONT'} ) : () ;
869             @Devel::ptkdb::eval_text_font = $ENV{'PTKDB_EVAL_FONT'} ? ( -font => $ENV{'PTKDB_EVAL_FONT'} ) : () ; # text for the expression eval window
870              
871             $Devel::ptkdb::eval_dump_indent = $ENV{'PTKDB_EVAL_DUMP_INDENT'} || 1 ;
872              
873             #
874             # Windows users are more used to having scroll bars on the right.
875             # If they've set PTKDB_SCROLLBARS_ONRIGHT to a non-zero value
876             # this will configure our scrolled windows with scrollbars on the right
877             #
878             # this can also be done by setting:
879             #
880             # ptkdb*scrollbars: se
881             #
882             # in the .Xdefaults/.Xresources file on X based systems
883             #
884             if( exists $ENV{'PTKDB_SCROLLBARS_ONRIGHT'} && $ENV{'PTKDB_SCROLLBARS_ONRIGHT'} ) {
885             @Devel::ptkdb::scrollbar_cfg = ('-scrollbars' => 'se') ;
886             }
887             else {
888             @Devel::ptkdb::scrollbar_cfg = ( ) ;
889             }
890              
891             #
892             # Controls how far an expression result will be 'decomposed'. Setting it
893             # to 0 will take it down only one level, setting it to -1 will make it
894             # decompose it all the way down. However, if you have a situation where
895             # an element is a ref back to the array or a root of the array
896             # you could hang the debugger by making it recursively evaluate an expression
897             #
898             $Devel::ptkdb::expr_depth = -1 ;
899             $Devel::ptkdb::add_expr_depth = 1 ; # how much further to expand an expression when clicked
900              
901             $Devel::ptkdb::linenumber_format = $ENV{'PTKDB_LINENUMBER_FORMAT'} || "%05d " ;
902             $Devel::ptkdb::linenumber_length = 5 ;
903              
904             $Devel::ptkdb::linenumber_offset = length sprintf($Devel::ptkdb::linenumber_format, 0) ;
905             $Devel::ptkdb::linenumber_offset -= 1 ;
906              
907             #
908             # Check to see if "Data Dumper" is available
909             # if it is we can save breakpoints and other
910             # various "functions". This call will also
911             # load the subroutines needed.
912             #
913             $Devel::ptkdb::DataDumperAvailable = 1 ; # assuming that it is now
914             $Devel::ptkdb::useDataDumperForEval = $Devel::ptkdb::DataDumperAvailable ;
915              
916             #
917             # DB Options (things not directly involving the window)
918             #
919              
920             # Flag to disable us from intercepting $SIG{'INT'}
921              
922             $DB::sigint_disable = defined $ENV{'PTKDB_SIGINT_DISABLE'} && $ENV{'PTKDB_SIGINT_DISABLE'} ;
923             #
924             # Possibly for debugging perl CGI Web scripts on
925             # remote machines.
926             #
927             $ENV{'DISPLAY'} = $ENV{'PTKDB_DISPLAY'} if exists $ENV{'PTKDB_DISPLAY'} ;
928              
929             } # end of BEGIN
930              
931             ##
932             ## subroutine provided to the user for initializing
933             ## files in .ptkdbrc
934             ##
935             sub brkpt {
936             my ($fName, @idx) = @_ ;
937             my($offset) ;
938             local(*dbline) = $main::{'_<' . $fName} ;
939              
940             $offset = $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ? 1 : 0 ;
941              
942             for( @idx ) {
943             if( !&DB::checkdbline($fName, $_ + $offset) ) {
944             my ($package, $filename, $line) = caller ;
945             print "$filename:$line: $fName line $_ is not breakable\n" ;
946             next ;
947             }
948             $DB::window->insertBreakpoint($fName, $_, 1) ; # insert a simple breakpoint
949             }
950             } # end of brkpt
951              
952             #
953             # Set conditional breakpoint(s)
954             #
955             sub condbrkpt {
956             my ($fname) = shift ;
957             my($offset) ;
958             local(*dbline) = $main::{'_<' . $fname} ;
959              
960             $offset = $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ? 1 : 0 ;
961              
962             while( @_ ) { # arg loop
963             my($index, $expr) = splice @_, 0, 2 ; # take args 2 at a time
964              
965             if( !&DB::checkdbline($fname, $index + $offset) ) {
966             my ($package, $filename, $line) = caller ;
967             print "$filename:$line: $fname line $index is not breakable\n" ;
968             next ;
969             }
970             $DB::window->insertBreakpoint($fname, $index, 1, $expr) ; # insert a simple breakpoint
971             } # end of arg loop
972              
973             } # end of conditionalbrkpt
974              
975             sub brkonsub {
976             my(@names) = @_ ;
977            
978             for( @names ) {
979            
980             # get the filename and line number range of the target subroutine
981            
982             if( !exists $DB::sub{$_} ) {
983             print "No subroutine $_. Try main::$_\n" ;
984             next ;
985             }
986              
987             $DB::sub{$_} =~ /(.*):([0-9]+)-([0-9]+)$/o ; # file name will be in $1, start line $2, end line $3
988              
989             for( $2..$3 ) {
990             next unless &DB::checkdbline($1, $_) ;
991             $DB::window->insertBreakpoint($1, $_, 1) ;
992             last ; # only need the one breakpoint
993             }
994             } # end of name loop
995              
996             } # end of brkonsub
997              
998             #
999             # set breakpoints on subroutines matching a regular
1000             # expression
1001             #
1002             sub brkonsub_regex {
1003             my(@regexps) = @_ ;
1004             my($regexp, @subList) ;
1005              
1006             #
1007             # accumulate matching subroutines
1008             #
1009             foreach $regexp ( @regexps ) {
1010             study $regexp ;
1011             push @subList, grep /$regexp/, keys %DB::sub ;
1012             } # end of brkonsub_regex
1013              
1014             brkonsub(@subList) ; # set breakpoints on matching subroutines
1015              
1016             } # end of brkonsub_regex
1017              
1018             #
1019             # Allow the user Access to our tag configurations
1020             #
1021             sub textTagConfigure {
1022             my ($tag, @config) = @_ ;
1023              
1024             $DB::window->{'text'}->tagConfigure($tag, @config) ;
1025            
1026             } # end of textTagConfigure
1027              
1028             ##
1029             ## Change the tabs in the text field
1030             ##
1031             sub setTabs {
1032            
1033             $DB::window->{'text'}->configure(-tabs => [ @_ ]) ;
1034              
1035             }
1036              
1037             #
1038             # User .ptkdbrc API
1039             # allows the user to add expressions to
1040             # the expression list window.
1041             #
1042             sub add_exprs {
1043             push @{$DB::window->{'expr_list'}}, map { 'expr' => $_, 'depth' => $Devel::ptkdb::expr_depth }, @_ ;
1044             } # end of add_exprs
1045              
1046              
1047             ##
1048             ## register a subroutine reference that will be called whenever
1049             ## ptkdb sets up it's windows
1050             ##
1051             sub register_user_window_init {
1052             push @{$DB::window->{'user_window_init_list'}}, @_ ;
1053             } # end of register_user_window_init
1054              
1055             ##
1056             ## register a subroutine reference that will be called whenever
1057             ## ptkdb enters from code
1058             ##
1059             sub register_user_DB_entry {
1060             push @{$DB::window->{'user_window_DB_entry_list'}}, @_ ;
1061             } # end of register_user_DB_entry
1062              
1063             sub get_notebook_widget {
1064             return $DB::window->{'notebook'} ;
1065             } # end of get_notebook_widget
1066              
1067              
1068             #
1069             # Run files provided by the user
1070             #
1071             sub do_user_init_files {
1072             use vars qw($dbg_window) ;
1073             local $dbg_window = shift ;
1074              
1075             eval {
1076             do "$Config{'installprivlib'}/Devel/ptkdbrc" ;
1077             } if -e "$Config{'installprivlib'}/Devel/ptkdbrc" ;
1078              
1079             if( $@ ) {
1080             print "System init file $Config{'installprivlib'}/ptkdbrc failed: $@\n" ;
1081             }
1082              
1083             eval {
1084             do "$ENV{'HOME'}/.ptkdbrc" ;
1085             } if exists $ENV{'HOME'} && -e "$ENV{'HOME'}/.ptkdbrc" ;
1086              
1087             if( $@ ) {
1088             print "User init file $ENV{'HOME'}/.ptkdbrc failed: $@\n" ;
1089             }
1090              
1091             eval {
1092             do ".ptkdbrc" ;
1093             } if -e ".ptkdbrc" ;
1094              
1095             if( $@ ) {
1096             print "User init file .ptkdbrc failed: $@\n" ;
1097             }
1098              
1099             &set_stop_on_warning() ;
1100             }
1101              
1102             #
1103             # Constructor for our Devel::ptkdb
1104             #
1105             sub new {
1106             my($type) = @_ ;
1107             my($self) = {} ;
1108            
1109             bless $self, $type ;
1110              
1111             # Current position of the executing program
1112              
1113             $self->{DisableOnLeave} = [] ; # List o' Widgets to disable when leaving the debugger
1114              
1115             $self->{current_file} = "" ;
1116             $self->{current_line} = -1 ; # initial value indicating we haven't set our line/tag
1117             $self->{window_pos_offset} = 10 ; # when we enter how far from the top of the text are we positioned down
1118             $self->{search_start} = "0.0" ;
1119             $self->{fwdOrBack} = 1 ;
1120             $self->{BookMarksPath} = $ENV{'PTKDB_BOOKMARKS_PATH'} || "$ENV{'HOME'}/.ptkdb_bookmarks" || '.ptkdb_bookmarks' ;
1121              
1122             $self->{'expr_list'} = [] ; # list of expressions to eval in our window fields: {'expr'} The expr itself {'depth'} expansion depth
1123              
1124              
1125             $self->{'brkPtCnt'} = 0 ;
1126             $self->{'brkPtSlots'} = [] ; # open slots for adding breakpoints to the table
1127              
1128             $self->{'main_window'} = undef ;
1129              
1130             $self->{'user_window_init_list'} = [] ;
1131             $self->{'user_window_DB_entry_list'} = [] ;
1132              
1133             $self->{'subs_list_cnt'} = 0 ;
1134              
1135             $self->setup_main_window() ;
1136              
1137             return $self ;
1138              
1139             } # end of new
1140              
1141             sub setup_main_window {
1142             my($self) = @_ ;
1143              
1144             # Main Window
1145            
1146              
1147             $self->{main_window} = MainWindow->new() ;
1148             $self->{main_window}->geometry($ENV{'PTKDB_GEOMETRY'} || "800x600") ;
1149              
1150             $self->setup_options() ; # must be done after MainWindow and before other frames are setup
1151              
1152             $self->{main_window}->bind('', \&DB::dbint_handler) ;
1153              
1154             #
1155             # Bind our 'quit' routine to a close command from the window manager (Alt-F4)
1156             #
1157             $self->{main_window}->protocol('WM_DELETE_WINDOW', sub { $self->close_ptkdb_window() ; } ) ;
1158              
1159             # Menu bar
1160              
1161             $self->setup_menu_bar() ;
1162              
1163             #
1164             # setup Frames
1165             #
1166             # Setup our Code, Data, and breakpoints
1167              
1168             $self->setup_frames() ;
1169              
1170             }
1171              
1172             #
1173             # Check for changes to the bookmarks and quit
1174             #
1175             sub DoQuit {
1176             my($self) = @_ ;
1177              
1178             $self->save_bookmarks($self->{BookMarksPath}) if $Devel::ptkdb::DataDumperAvailable && $self->{'bookmarks_changed'};
1179             $self->{main_window}->destroy if $self->{main_window} ;
1180             $self->{main_window} = undef if defined $self->{main_window} ;
1181              
1182             exit ;
1183             }
1184              
1185             #
1186             # This supports the File -> Open menu item
1187             # We create a new window and list all of the files
1188             # that are contained in the program. We also
1189             # pick up all of the perlTk files that are supporting
1190             # the debugger.
1191             #
1192             sub DoOpen {
1193             my $self = shift ;
1194             my ($topLevel, $listBox, $frame, $selectedFile, @fList) ;
1195              
1196             #
1197             # subroutine we call when we've selected a file
1198             #
1199              
1200             my $chooseSub = sub { $selectedFile = $listBox->get('active') ;
1201             print "attempting to open $selectedFile\n" ;
1202             $DB::window->set_file($selectedFile, 0) ;
1203             destroy $topLevel ;
1204             } ;
1205              
1206             #
1207             # Take the list the files and resort it.
1208             # we put all of the local files first, and
1209             # then list all of the system libraries.
1210             #
1211             @fList = sort {
1212             # sort comparison function block
1213             my $fa = substr($a, 0, 1) ;
1214             my $fb = substr($b, 0, 1) ;
1215              
1216             return $a cmp $b if ($fa eq '/') && ($fb eq '/') ;
1217              
1218             return -1 if ($fb eq '/') && ($fa ne '/') ;
1219             return 1 if ($fa eq '/' ) && ($fb ne '/') ;
1220              
1221             return $a cmp $b ;
1222              
1223             } grep s/^_
1224              
1225             #
1226             # Create a list box with all of our files
1227             # to select from
1228             #
1229             $topLevel = $self->{main_window}->Toplevel(-title => "File Select", -overanchor => 'cursor') ;
1230              
1231             $listBox = $topLevel->Scrolled('Listbox',
1232             @Devel::ptkdb::scrollbar_cfg,
1233             @Devel::ptkdb::expression_text_font,
1234             -width => 30)->pack(-side => 'top', -fill => 'both', -expand => 1) ;
1235              
1236              
1237             # Bind a double click on the mouse button to the same action
1238             # as pressing the Okay button
1239              
1240             $listBox->bind('' => $chooseSub) ;
1241            
1242             $listBox->insert('end', @fList) ;
1243              
1244             $topLevel->Button( -text => "Okay", -command => $chooseSub, @Devel::ptkdb::button_font,
1245             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
1246              
1247             $topLevel->Button( -text => "Cancel", @Devel::ptkdb::button_font,
1248             -command => sub { destroy $topLevel ; } )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
1249             } # end of DoOpen
1250              
1251             sub do_tabs {
1252             my($tabs_str) ;
1253             my($w, $result, $tabs_cfg) ;
1254             require Tk::Dialog ;
1255              
1256             $w = $DB::window->{'main_window'}->DialogBox(-title => "Tabs", -buttons => [qw/Okay Cancel/]) ;
1257            
1258             $tabs_cfg = $DB::window->{'text'}->cget(-tabs) ;
1259              
1260             $tabs_str = join " ", @$tabs_cfg if $tabs_cfg ;
1261              
1262             $w->add('Label', -text => 'Tabs:')->pack(-side => 'left') ;
1263              
1264             $w->add('Entry', -textvariable => \$tabs_str)->pack(-side => 'left')->selectionRange(0,'end') ;
1265              
1266             $result = $w->Show() ;
1267              
1268             return unless $result eq 'Okay' ;
1269              
1270             $DB::window->{'text'}->configure(-tabs => [ split /\s/, $tabs_str ]) ;
1271             }
1272              
1273             sub close_ptkdb_window {
1274             my($self) = @_ ;
1275              
1276             $DB::window->{'event'} = 'run' ;
1277             $self->{current_file} = "" ; # force a file reset
1278             $self->{'main_window'}->destroy ;
1279             $self->{'main_window'} = undef ;
1280             }
1281              
1282             sub setup_menu_bar {
1283             my ($self) = @_ ;
1284             my $mw = $self->{main_window} ;
1285             my ($mb, $items) ;
1286            
1287             #
1288             # We have menu items/features that are not available if the Data::DataDumper module
1289             # isn't present. For any feature that requires it we add this option list.
1290             #
1291             my @dataDumperEnableOpt = ( state => 'disabled' ) unless $Devel::ptkdb::DataDumperAvailable ;
1292              
1293              
1294             $self->{menu_bar} = $mw->Frame(-relief => 'raised', -borderwidth => '1')->pack(-side => 'top', -fill => 'x') ;
1295              
1296             $mb = $self->{menu_bar} ;
1297              
1298             # file menu in menu bar
1299              
1300             $items = [ [ 'command' => 'About...', -command => sub { $self->DoAbout() ; } ],
1301             [ 'command' => 'Bug Report...', -command => \&DoBugReport ],
1302             "-",
1303              
1304             [ 'command' => 'Open', -accelerator => 'Alt+O',
1305             -underline => 0,
1306             -command => sub { $self->DoOpen() ; } ],
1307              
1308             [ 'command' => 'Save Config...',
1309             -underline => 0,
1310             -command => \&DB::SaveState,
1311             @dataDumperEnableOpt ],
1312              
1313             [ 'command' => 'Restore Config...',
1314             -underline => 0,
1315             -command => \&DB::RestoreState,
1316             @dataDumperEnableOpt ],
1317              
1318             [ 'command' => 'Goto Line...',
1319             -underline => 0,
1320             -accelerator => 'Alt-g',
1321             -command => sub { $self->GotoLine() ; },
1322             @dataDumperEnableOpt ] ,
1323              
1324             [ 'command' => 'Find Text...',
1325             -accelerator => 'Ctrl-f',
1326             -underline => 0,
1327             -command => sub { $self->FindText() ; } ],
1328              
1329             [ 'command' => "Tabs...", -command => \&do_tabs ],
1330              
1331             "-",
1332              
1333             [ 'command' => 'Close Window and Run', -accelerator => 'Alt+W',
1334             -underline => 6, -command => sub { $self->close_ptkdb_window ; } ],
1335            
1336             [ 'command' => 'Quit...', -accelerator => 'Alt+Q',
1337             -underline => 0,
1338             -command => sub { $self->DoQuit } ]
1339             ] ;
1340              
1341            
1342             $mw->bind('' => sub { $self->GotoLine() ; }) ;
1343             $mw->bind('' => sub { $self->FindText() ; }) ;
1344             $mw->bind('' => \&Devel::ptkdb::DoRestart) ;
1345             $mw->bind('' => sub { $self->{'event'} = 'quit' } ) ;
1346             $mw->bind('' => sub { $self->close_ptkdb_window ; }) ;
1347              
1348             $self->{file_menu_button} = $mb->Menubutton(-text => 'File',
1349             -underline => 0,
1350             -menuitems => $items
1351             )->pack(-side =>, 'left',
1352             -anchor => 'nw',
1353             -padx => 2) ;
1354              
1355             # Control Menu
1356              
1357             my $runSub = sub { $DB::step_over_depth = -1 ; $self->{'event'} = 'run' } ;
1358              
1359             my $runToSub = sub { $DB::window->{'event'} = 'run' if $DB::window->SetBreakPoint(1) ; } ;
1360              
1361             my $stepOverSub = sub { &DB::SetStepOverBreakPoint(0) ;
1362             $DB::single = 1 ;
1363             $DB::window->{'event'} = 'step' ;
1364             } ;
1365            
1366              
1367             my $stepInSub = sub {
1368             $DB::step_over_depth = -1 ;
1369             $DB::single = 1 ;
1370             $DB::window->{'event'} = 'step' ; } ;
1371              
1372              
1373             my $returnSub = sub {
1374             &DB::SetStepOverBreakPoint(-1) ;
1375             $self->{'event'} = 'run' ;
1376             } ;
1377              
1378              
1379             $items = [ [ 'command' => 'Run', -accelerator => 'Alt+r', -underline => 0, -command => $runSub ],
1380             [ 'command' => 'Run To Here', -accelerator => 'Alt+t', -underline => 5, -command => $runToSub ],
1381             '-',
1382             [ 'command' => 'Set Breakpoint', -underline => 4, -command => sub { $self->SetBreakPoint ; }, -accelerator => 'Ctrl-b' ],
1383             [ 'command' => 'Clear Breakpoint', -command => sub { $self->UnsetBreakPoint } ],
1384             [ 'command' => 'Clear All Breakpoints', -underline => 6, -command => sub {
1385             $DB::window->removeAllBreakpoints($DB::window->{current_file}) ;
1386             &DB::clearalldblines() ;
1387             } ],
1388             '-',
1389             [ 'command' => 'Step Over', -accelerator => 'Alt+N', -underline => 0, -command => $stepOverSub ],
1390             [ 'command' => 'Step In', -accelerator => 'Alt+S', -underline => 5, -command => $stepInSub ],
1391             [ 'command' => 'Return', -accelerator => 'Alt+U', -underline => 3, -command => $returnSub ],
1392             '-',
1393             [ 'command' => 'Restart...', -accelerator => 'Ctrl-r', -underline => 0, -command => \&Devel::ptkdb::DoRestart ],
1394             '-',
1395             [ 'checkbutton' => 'Stop On Warning', -variable => \$DB::ptkdb::stop_on_warning, -command => \&set_stop_on_warning ]
1396              
1397            
1398             ] ; # end of control menu items
1399              
1400            
1401             $self->{control_menu_button} = $mb->Menubutton(-text => 'Control',
1402             -underline => 0,
1403             -menuitems => $items,
1404             )->pack(-side =>, 'left',
1405             -padx => 2) ;
1406              
1407              
1408             $mw->bind('' => $runSub) ;
1409             $mw->bind('', $runToSub) ;
1410             $mw->bind('', sub { $self->SetBreakPoint ; }) ;
1411              
1412             for( @Devel::ptkdb::step_over_keys ) {
1413             $mw->bind($_ => $stepOverSub );
1414             }
1415              
1416             for( @Devel::ptkdb::step_in_keys ) {
1417             $mw->bind($_ => $stepInSub );
1418             }
1419              
1420             for( @Devel::ptkdb::return_keys ) {
1421             $mw->bind($_ => $returnSub );
1422             }
1423              
1424             # Data Menu
1425              
1426             $items = [ [ 'command' => 'Enter Expression', -accelerator => 'Alt+E', -command => sub { $self->EnterExpr() } ],
1427             [ 'command' => 'Delete Expression', -accelerator => 'Ctrl+D', -command => sub { $self->deleteExpr() } ],
1428             [ 'command' => 'Delete All Expressions', -command => sub {
1429             $self->deleteAllExprs() ;
1430             $self->{'expr_list'} = [] ; # clears list by dropping ref to it, replacing it with a new one
1431             } ],
1432             '-',
1433             [ 'command' => 'Expression Eval Window...', -accelerator => 'F8', -command => sub { $self->setupEvalWindow() ; } ],
1434             [ 'checkbutton' => "Use DataDumper for Eval Window?", -variable => \$Devel::ptkdb::useDataDumperForEval, @dataDumperEnableOpt ]
1435             ] ;
1436              
1437              
1438             $self->{data_menu_button} = $mb->Menubutton(-text => 'Data', -menuitems => $items,
1439             -underline => 0,
1440             )->pack(-side => 'left',
1441             -padx => 2) ;
1442              
1443             $mw->bind('' => sub { $self->EnterExpr() } ) ;
1444             $mw->bind('' => sub { $self->deleteExpr() } );
1445             $mw->bind('', sub { $self->setupEvalWindow() ; }) ;
1446             #
1447             # Stack menu
1448             #
1449             $self->{stack_menu} = $mb->Menubutton(-text => 'Stack',
1450             -underline => 2,
1451             )->pack(-side => 'left',
1452             -padx => 2) ;
1453              
1454             #
1455             # Bookmarks menu
1456             #
1457             $self->{bookmarks_menu} = $mb->Menubutton(-text => 'Bookmarks',
1458             -underline => 0,
1459             @dataDumperEnableOpt
1460             )->pack(-side => 'left',
1461             -padx => 2) ;
1462             $self->setup_bookmarks_menu() ;
1463              
1464             #
1465             # Windows Menu
1466             #
1467             my($bsub) = sub { $self->{'text'}->focus() } ;
1468             my($csub) = sub { $self->{'quick_entry'}->focus() } ;
1469             my($dsub) = sub { $self->{'entry'}->focus() } ;
1470              
1471             $items = [ [ 'command' => 'Code Pane', -accelerator => 'Alt+0', -command => $bsub ],
1472             [ 'command' => 'Quick Entry', -accelerator => 'F9', -command => $csub ],
1473             [ 'command' => 'Expr Entry', -accelerator => 'F11', -command => $dsub ]
1474             ] ;
1475              
1476             $mb->Menubutton(-text => 'Windows', -menuitems => $items
1477             )->pack(-side => 'left',
1478             -padx => 2) ;
1479              
1480             $mw->bind('', $bsub) ;
1481             $mw->bind('', $csub) ;
1482             $mw->bind('', $dsub) ;
1483              
1484             #
1485             # Bar for some popular controls
1486             #
1487              
1488             $self->{button_bar} = $mw->Frame()->pack(-side => 'top') ;
1489              
1490             $self->{stepin_button} = $self->{button_bar}->Button(-text, => "Step In", @Devel::ptkdb::button_font,
1491             -command => $stepInSub) ;
1492             $self->{stepin_button}->pack(-side => 'left') ;
1493              
1494             $self->{stepover_button} = $self->{button_bar}->Button(-text, => "Step Over", @Devel::ptkdb::button_font,
1495             -command => $stepOverSub) ;
1496             $self->{stepover_button}->pack(-side => 'left') ;
1497              
1498             $self->{return_button} = $self->{button_bar}->Button(-text, => "Return", @Devel::ptkdb::button_font,
1499             -command => $returnSub) ;
1500             $self->{return_button}->pack(-side => 'left') ;
1501              
1502             $self->{run_button} = $self->{button_bar}->Button(-background => 'green', -text, => "Run", @Devel::ptkdb::button_font,
1503             -command => $runSub) ;
1504             $self->{run_button}->pack(-side => 'left') ;
1505              
1506             $self->{run_to_button} = $self->{button_bar}->Button(-text, => "Run To", @Devel::ptkdb::button_font,
1507             -command => $runToSub) ;
1508             $self->{run_to_button}->pack(-side => 'left') ;
1509              
1510             $self->{breakpt_button} = $self->{button_bar}->Button(-text, => "Break", @Devel::ptkdb::button_font,
1511             -command => sub { $self->SetBreakPoint ; } ) ;
1512             $self->{breakpt_button}->pack(-side => 'left') ;
1513              
1514             push @{$self->{DisableOnLeave}}, @$self{'stepin_button', 'stepover_button', 'return_button', 'run_button', 'run_to_button', 'breakpt_button'} ;
1515            
1516             } # end of setup_menu_bar
1517              
1518             sub edit_bookmarks {
1519             my ($self) = @_ ;
1520              
1521             my ($top) = $self->{main_window}->Toplevel(-title => "Edit Bookmarks") ;
1522            
1523             my $list = $top->Scrolled('Listbox', -selectmode => 'multiple')->pack(-side => 'top', -fill => 'both', -expand => 1) ;
1524            
1525             my $deleteSub = sub {
1526             my $cnt = 0 ;
1527             for( $list->curselection ) {
1528             $list->delete($_ - $cnt++) ;
1529             }
1530             } ;
1531              
1532             my $okaySub = sub {
1533             $self->{'bookmarks'} = [ $list->get(0, 'end') ] ; # replace the bookmarks
1534             } ;
1535            
1536             my $frm = $top->Frame()->pack(-side => 'top', -fill => 'x', -expand => 1 ) ;
1537            
1538             my $deleteBtn = $frm->Button(-text => 'Delete', -command => $deleteSub)->pack(-side => 'left', -fill => 'x', -expand => 1 ) ;
1539             my $cancelBtn = $frm->Button(-text => 'Cancel', -command => sub { destroy $top ; })->pack(-side =>'left', -fill => 'x', -expand => 1 ) ;
1540             my $dismissBtn = $frm->Button(-text => 'Okay', -command => $okaySub)->pack(-side => 'left', -fill => 'x', -expand => 1 ) ;
1541            
1542             $list->insert('end', @{$self->{'bookmarks'}}) ;
1543              
1544             } # end of edit_bookmarks
1545              
1546             sub setup_bookmarks_menu {
1547             my ($self) = @_ ;
1548              
1549             #
1550             # "Add bookmark" item
1551             #
1552             my $bkMarkSub = sub { $self->add_bookmark() ; } ;
1553              
1554             $self->{'bookmarks_menu'}->command(-label => "Add Bookmark",
1555             -accelerator => 'Alt+k',
1556             -command => $bkMarkSub
1557             ) ;
1558              
1559             $self->{'main_window'}->bind('', $bkMarkSub) ;
1560              
1561             $self->{'bookmarks_menu'}->command(-label => "Edit Bookmarks",
1562             -command => sub { $self->edit_bookmarks() } ) ;
1563              
1564             $self->{'bookmarks_menu'}->separator() ;
1565              
1566             #
1567             # Check to see if there is a bookmarks file
1568             #
1569             return unless -e $self->{BookMarksPath} && -r $self->{BookMarksPath} ;
1570            
1571             use vars qw($ptkdb_bookmarks) ;
1572             local($ptkdb_bookmarks) ; # ref to hash of bookmark entries
1573              
1574             do $self->{BookMarksPath} ; # eval the file
1575              
1576             $self->add_bookmark_items(@$ptkdb_bookmarks) ;
1577              
1578             } # end of setup_bookmarks_menu
1579              
1580             #
1581             # $item = "$fname:$lineno"
1582             #
1583             sub add_bookmark_items {
1584             my($self, @items) = @_ ;
1585             my($menu) = ( $self->{'bookmarks_menu'} ) ;
1586              
1587             $self->{'bookmarks_changed'} = 1 ;
1588              
1589             for( @items ) {
1590             my $item = $_ ;
1591             $menu->command( -label => $_,
1592             -command => sub { $self->bookmark_cmd($item) }) ;
1593             push @{$self->{'bookmarks'}}, $item ;
1594             }
1595             } # end of add_bookmark_item
1596              
1597             #
1598             # Invoked from the "Add Bookmark" command
1599             #
1600             sub add_bookmark {
1601             my($self) = @_ ;
1602            
1603             my $line = $self->get_lineno() ;
1604             my $fname = $self->{'current_file'} ;
1605             $self->add_bookmark_items("$fname:$line") ;
1606              
1607             } # end of add_bookmark
1608              
1609             #
1610             # Command executed when someone selects
1611             # a bookmark
1612             #
1613             sub bookmark_cmd {
1614             my ($self, $item) = @_ ;
1615              
1616             $item =~ /(.*):([0-9]+)$/ ;
1617              
1618             $self->set_file($1,$2) ;
1619              
1620             } # end of bookmark_cmd
1621              
1622             sub save_bookmarks {
1623             my($self, $pathName) = @_ ;
1624              
1625             return unless $Devel::ptkdb::DataDumperAvailable ; # we can't save without the data dumper
1626             local(*F) ;
1627              
1628             eval {
1629             open F, ">$pathName" || die "open failed" ;
1630             my $d = Data::Dumper->new([ $self->{'bookmarks'} ],
1631             [ 'ptkdb_bookmarks' ]) ;
1632              
1633             $d->Indent(2) ; # make it more editable for people
1634              
1635             my $str ;
1636             if( $d->can('Dumpxs') ) {
1637             $str = $d->Dumpxs() ;
1638             }
1639             else {
1640             $str = $d->Dump() ;
1641             }
1642              
1643             print F $str || die "outputing bookmarks failed" ;
1644             close(F) ;
1645             } ;
1646              
1647             if( $@ ) {
1648             $self->DoAlert("Couldn't save bookmarks file $@") ;
1649             return ;
1650             }
1651              
1652             } # end of save_bookmarks
1653              
1654             #
1655             # This is our callback from a double click in our
1656             # HList. A click in an expanded item will delete
1657             # the children beneath it, and the next time it
1658             # updates, it will only update that entry to that
1659             # depth. If an item is 'unexpanded' such as
1660             # a hash or a list, it will expand it one more
1661             # level. How much further an item is expanded is
1662             # controled by package variable $Devel::ptkdb::add_expr_depth
1663             #
1664             sub expr_expand {
1665             my ($path) = @_ ;
1666             my $hl = $DB::window->{'data_list'} ;
1667             my ($parent, $root, $index, @children, $depth) ;
1668              
1669             $parent = $path ;
1670             $root = $path ;
1671             $depth = 0 ;
1672              
1673             for( $root = $path ; defined $parent && $parent ne "" ; $parent = $hl->infoParent($root) ) {
1674             $root = $parent ;
1675             $depth += 1 ;
1676             } #end of root search
1677              
1678             #
1679             # Determine the index of the root of our expression
1680             #
1681             $index = 0 ;
1682             for( @{$DB::window->{'expr_list'}} ) {
1683             last if $_->{'expr'} eq $root ;
1684             $index += 1 ;
1685             }
1686              
1687             #
1688             # if we have children we're going to delete them
1689             #
1690              
1691             @children = $hl->infoChildren($path) ;
1692              
1693             if( scalar @children > 0 ) {
1694              
1695             $hl->deleteOffsprings($path) ;
1696              
1697             $DB::window->{'expr_list'}->[$index]->{'depth'} = $depth - 1 ; # adjust our depth
1698             }
1699             else {
1700             #
1701             # Delete the existing tree and insert a new one
1702             #
1703             $hl->deleteEntry($root) ;
1704             $hl->add($root, -at => $index) ;
1705             $DB::window->{'expr_list'}->[$index]->{'depth'} += $Devel::ptkdb::add_expr_depth ;
1706             #
1707             # Force an update on our expressions
1708             #
1709             $DB::window->{'event'} = 'update' ;
1710             }
1711             } # end of expr_expand
1712              
1713             sub line_number_from_coord {
1714             my($txtWidget, $coord) = @_ ;
1715             my($index) ;
1716            
1717             $index = $txtWidget->index($coord) ;
1718              
1719             # index is in the format of lineno.column
1720              
1721             $index =~ /([0-9]*)\.([0-9]*)/o ;
1722              
1723             #
1724             # return a list of (col, line). Why
1725             # backwards?
1726             #
1727              
1728             return ($2 ,$1) ;
1729            
1730             } # end of line_number_from_coord
1731              
1732             #
1733             # It may seem as if $txtWidget and $self are
1734             # erroneously reversed, but this is a result
1735             # of the calling syntax of the text-bind callback.
1736             #
1737             sub set_breakpoint_tag {
1738             my($txtWidget, $self, $coord, $value) = @_ ;
1739             my($idx) ;
1740              
1741             $idx = line_number_from_coord($txtWidget, $coord) ;
1742              
1743             $self->insertBreakpoint($self->{'current_file'}, $idx, $value) ;
1744              
1745             } # end of set_breakpoint_tag
1746              
1747             sub clear_breakpoint_tag {
1748             my($txtWidget, $self, $coord) = @_ ;
1749             my($idx) ;
1750              
1751             $idx = line_number_from_coord($txtWidget, $coord) ;
1752            
1753             $self->removeBreakpoint($self->{'current_file'}, $idx) ;
1754              
1755             } # end of clear_breakpoint_tag
1756              
1757             sub change_breakpoint_tag {
1758             my($txtWidget, $self, $coord, $value) = @_ ;
1759             my($idx, $brkPt, @tagSet) ;
1760              
1761             $idx = line_number_from_coord($txtWidget, $coord) ;
1762              
1763             #
1764             # Change the value of the breakpoint
1765             #
1766             @tagSet = ( "$idx.0", "$idx.$Devel::ptkdb::linenumber_length" ) ;
1767              
1768             $brkPt = &DB::getdbline($self->{'current_file'}, $idx + $self->{'line_offset'}) ;
1769             return unless $brkPt ;
1770              
1771             #
1772             # Check the breakpoint tag
1773             #
1774              
1775             if ( $txtWidget ) {
1776             $txtWidget->tagRemove('breaksetLine', @tagSet ) ;
1777             $txtWidget->tagRemove('breakdisabledLine', @tagSet ) ;
1778             }
1779              
1780             $brkPt->{'value'} = $value ;
1781            
1782             if ( $txtWidget ) {
1783             if ( $brkPt->{'value'} ) {
1784             $txtWidget->tagAdd('breaksetLine', @tagSet ) ;
1785             }
1786             else {
1787             $txtWidget->tagAdd('breakdisabledLine', @tagSet ) ;
1788             }
1789             }
1790              
1791             } # end of change_breakpoint_tag
1792              
1793             #
1794             # God Forbid anyone comment something complex and tightly optimized.
1795             #
1796             # We can get a list of the subroutines from the interpreter
1797             # by querrying the *DB::sub typeglob: keys %DB::sub
1798             #
1799             # The list appears broken down by module:
1800             #
1801             # main::BEGIN
1802             # main::mySub
1803             # main::otherSub
1804             # Tk::Adjuster::Mapped
1805             # Tk::Adjuster::Packed
1806             # Tk::Button::BEGIN
1807             # Tk::Button::Enter
1808             #
1809             # We would like to break this list down into a heirarchy.
1810             #
1811             # main Tk
1812             # | | | |
1813             # BEGIN mySub OtherSub | |
1814             # Adjuster Button
1815             # | | | |
1816             # Mapped Packed BEGIN Enter
1817             #
1818             #
1819             # We translate this list into a heirarchy of hashes(say three times fast).
1820             # We take each entry and split it into elements. Each element is a leaf in the tree.
1821             # We traverse the tree with the inner for loop.
1822             # With each branch we check to see if it already exists or
1823             # we create it. When we reach the last element, this becomes our entry.
1824             #
1825              
1826             #
1827             # An incoming list is potentially 'large' so we
1828             # pass in the ref to it instead.
1829             #
1830             # New entries can be inserted by providing a $topH
1831             # hash ref to an existing tree.
1832             #
1833             sub tree_split {
1834             my ($listRef, $separator, $topH) = @_ ;
1835             my ($h, $list_elem) ;
1836              
1837             $topH = {} unless $topH ;
1838              
1839             foreach $list_elem ( @$listRef ) {
1840             $h = $topH ;
1841             for( split /$separator/o, $list_elem ) { # Tk::Adjuster::Mapped -> ( Tk Adjuster Mapped )
1842             $h->{$_} or $h->{$_} = {} ; # either we have an entry for this OR we create one
1843             $h = $h->{$_} ;
1844             }
1845             @$h{'name', 'path'} = ($_, $list_elem) ; # the last leaf is our entry
1846             } # end of tree_split loop
1847              
1848             return $topH ;
1849              
1850             } # end of tree_split
1851              
1852             #
1853             # callback executed when someone double clicks
1854             # an entry in the 'Subs' Tk::Notebook page.
1855             #
1856             sub sub_list_cmd {
1857             my ($self, $path) = @_ ;
1858             my ($h) ;
1859             my $sub_list = $self->{'sub_list'} ;
1860              
1861             if ( $sub_list->info('children', $path) ) {
1862             #
1863             # Delete the children
1864             #
1865             $sub_list->deleteOffsprings($path) ;
1866             return ;
1867             }
1868              
1869             #
1870             # split the path up into elements
1871             # end descend through the tree.
1872             #
1873             $h = $Devel::ptkdb::subs_tree ;
1874             for ( split /\./o, $path ) {
1875             $h = $h->{$_} ; # next level down
1876             }
1877              
1878             #
1879             # if we don't have a 'name' entry we
1880             # still have levels to decend through.
1881             #
1882             if ( !exists $h->{'name'} ) {
1883             #
1884             # Add the next level paths
1885             #
1886             for ( sort keys %$h ) {
1887              
1888             if ( exists $h->{$_}->{'path'} ) {
1889             $sub_list->add($path . '.' . $_, -text => $h->{$_}->{'path'}) ;
1890             }
1891             else {
1892             $sub_list->add($path . '.' . $_, -text => $_) ;
1893             }
1894             }
1895             return ;
1896             }
1897              
1898             $DB::sub{$h->{'path'}} =~ /(.*):([0-9]+)-[0-9]+$/o ; # file name will be in $1, line number will be in $2 */
1899              
1900             $self->set_file($1, $2) ;
1901              
1902             } # end of sub_list_cmd
1903              
1904             sub fill_subs_page {
1905             my($self) = @_ ;
1906              
1907             $self->{'sub_list'}->delete('all') ; # clear existing entries
1908              
1909             my @list = keys %DB::sub ;
1910              
1911             $Devel::ptkdb::subs_tree = tree_split(\@list, "::") ;
1912              
1913             # setup to level of list
1914              
1915             for ( sort keys %$Devel::ptkdb::subs_tree ) {
1916             $self->{'sub_list'}->add($_, -text => $_) ;
1917             } # end of top level loop
1918             }
1919              
1920             sub setup_subs_page {
1921             my($self) = @_ ;
1922              
1923             $self->{'subs_page_activated'} = 1 ;
1924              
1925             $self->{'sub_list'} = $self->{'subs_page'}->Scrolled('HList', -command => sub { $self->sub_list_cmd(@_) ; } ) ;
1926              
1927             $self->fill_subs_page() ;
1928              
1929             $self->{'sub_list'}->pack(-side => 'left', -fill => 'both', -expand => 1
1930             ) ;
1931              
1932             $self->{'subs_list_cnt'} = scalar keys %DB::sub ;
1933              
1934              
1935             } # end of setup_subs_page
1936              
1937              
1938             sub check_search_request {
1939             my($entry, $self, $searchButton, $regexBtn) = @_ ;
1940             my($txt) = $entry->get ;
1941              
1942             if( $txt =~ /^\s*[0-9]+\s*$/ ) {
1943             $self->DoGoto($entry) ;
1944             return ;
1945             }
1946              
1947             if( $txt =~ /\.\*/ ) { # common regex search pattern
1948             $self->FindSearch($entry, $regexBtn, 1) ;
1949             return ;
1950             }
1951              
1952             # vanilla search
1953             $self->FindSearch($entry, $searchButton, 0) ;
1954             }
1955              
1956             sub setup_search_panel {
1957             my ($self, $parent, @packArgs) = @_ ;
1958             my ($frm, $srchBtn, $regexBtn, $entry) ;
1959              
1960             $frm = $parent->Frame() ;
1961              
1962             $frm->Button(-text => 'Goto', -command => sub { $self->DoGoto($entry) })->pack(-side => 'left') ;
1963             $srchBtn = $frm->Button(-text => 'Search', -command => sub { $self->FindSearch($entry, $srchBtn, 0) ; }
1964             )->pack(-side => 'left' ) ;
1965              
1966             $regexBtn = $frm->Button(-text => 'Regex',
1967             -command => sub { $self->FindSearch($entry, $regexBtn, 1) ; }
1968             )->pack(-side => 'left',
1969             ) ;
1970              
1971              
1972             $entry = $frm->Entry(-width => 50)->pack(-side => 'left', -fill => 'both', -expand => 1) ;
1973              
1974             $entry->bind('', sub { check_search_request($entry, $self, $srchBtn, $regexBtn) ; } ) ;
1975              
1976             $frm->pack(@packArgs) ;
1977              
1978             } # end of setup search_panel
1979              
1980             sub setup_breakpts_page {
1981             my ($self) = @_ ;
1982             require Tk::Table ;
1983              
1984             $self->{'breakpts_page'} = $self->{'notebook'}->add("brkptspage", -label => "BrkPts") ;
1985              
1986             $self->{'breakpts_table'} = $self->{'breakpts_page'}->Table(-columns => 1, -scrollbars => 'se')->
1987             pack(-side => 'top', -fill => 'both', -expand => 1
1988             ) ;
1989              
1990             $self->{'breakpts_table_data'} = { } ; # controls addressed by "fname:lineno"
1991              
1992             } # end of setup_breakpts_page
1993              
1994             sub setup_frames {
1995             my ($self) = @_ ;
1996             my $mw = $self->{'main_window'} ;
1997             my ($txt, $place_holder, $frm) ;
1998             require Tk::ROText ;
1999             require Tk::NoteBook ;
2000             require Tk::HList ;
2001             require Tk::Balloon ;
2002             require Tk::Adjuster ;
2003              
2004             # get the side that we want to put the code pane on
2005            
2006             my($codeSide) = $ENV{'PTKDB_CODE_SIDE'} || $mw->optionGet("codeside", "") || 'left' ;
2007              
2008            
2009              
2010             $mw->update ; # force geometry manager to map main_window
2011             $frm = $mw->Frame(-width => $mw->reqwidth()) ; # frame for our code pane and search controls
2012              
2013             $self->setup_search_panel($frm, -side => 'top', -fill => 'x') ;
2014              
2015             #
2016             # Text window for the code of our currently viewed file
2017             #
2018             $self->{'text'} = $frm->Scrolled('ROText',
2019             -wrap => "none",
2020             @Devel::ptkdb::scrollbar_cfg,
2021             @Devel::ptkdb::code_text_font
2022             ) ;
2023              
2024              
2025             $txt = $self->{'text'} ;
2026             for( $txt->children ) {
2027             next unless (ref $_) =~ /ROText$/ ;
2028             $self->{'text'} = $_ ;
2029             last ;
2030             }
2031              
2032             $frm->packPropagate(0) ;
2033             $txt->packPropagate(0) ;
2034              
2035             $frm->packAdjust(-side => $codeSide, -fill => 'both', -expand => 1) ;
2036             $txt->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2037              
2038             # $txt->form(-top => [ $self->{'menu_bar'} ], -left => '%0', -right => '%50') ;
2039             # $frm->form(-top => [ $self->{'menu_bar'} ], -left => '%50', -right => '%100') ;
2040              
2041             $self->configure_text() ;
2042              
2043             #
2044             # Notebook
2045             #
2046              
2047             $self->{'notebook'} = $mw->NoteBook() ;
2048             $self->{'notebook'}->packPropagate(0) ;
2049             $self->{'notebook'}->pack(-side => $codeSide, -fill => 'both', -expand => 1) ;
2050              
2051             #
2052             # an hlist for the data entries
2053             #
2054             $self->{'data_page'} = $self->{'notebook'}->add("datapage", -label => "Exprs") ;
2055              
2056             #
2057             # frame, entry and label for quick expressions
2058             #
2059             my $frame = $self->{'data_page'}->Frame()->pack(-side => 'top', -fill => 'x') ;
2060            
2061             my $label = $frame->Label(-text => "Quick Expr:")->pack(-side => 'left') ;
2062            
2063             $self->{'quick_entry'} = $frame->Entry()->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2064              
2065             $self->{'quick_entry'}->bind('', sub { $self->QuickExpr() ; } ) ;
2066            
2067              
2068             #
2069             # Entry widget for expressions and breakpoints
2070             #
2071             $frame = $self->{'data_page'}->Frame()->pack(-side => 'top', -fill => 'x') ;
2072              
2073             $label = $frame->Label(-text => "Enter Expr:")->pack(-side => 'left') ;
2074              
2075             $self->{'entry'} = $frame->Entry()->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2076              
2077             $self->{'entry'}->bind('', sub { $self->EnterExpr() }) ;
2078              
2079             #
2080             # Hlist for data expressions
2081             #
2082              
2083              
2084             $self->{data_list} = $self->{'data_page'}->Scrolled('HList',
2085             @Devel::ptkdb::scrollbar_cfg,
2086             separator => $Devel::ptkdb::pathSep,
2087             @Devel::ptkdb::expression_text_font,
2088             -command => \&Devel::ptkdb::expr_expand,
2089             -selectmode => 'multiple'
2090             ) ;
2091              
2092             $self->{data_list}->pack(-side => 'top', -fill => 'both', -expand => 1
2093             ) ;
2094              
2095              
2096             $self->{'subs_page_activated'} = 0 ;
2097             $self->{'subs_page'} = $self->{'notebook'}->add("subspage", -label => "Subs", -createcmd => sub { $self->setup_subs_page }) ;
2098              
2099             $self->setup_breakpts_page() ;
2100              
2101             } # end of setup_frames
2102              
2103              
2104              
2105             sub configure_text {
2106             my($self) = @_ ;
2107             my($txt, $mw) = ($self->{'text'}, $self->{'main_window'}) ;
2108             my($place_holder) ;
2109              
2110             $self->{'expr_balloon'} = $txt->Balloon();
2111             $self->{'balloon_expr'} = ' ' ; # initial expression
2112              
2113             # If Data::Dumper is available setup a dumper for the balloon
2114            
2115             if ( $Devel::ptkdb::DataDumperAvailable ) {
2116             $self->{'balloon_dumper'} = new Data::Dumper([$place_holder]) ;
2117             $self->{'balloon_dumper'}->Terse(1) ;
2118             $self->{'balloon_dumper'}->Indent($Devel::ptkdb::eval_dump_indent) ;
2119              
2120             $self->{'quick_dumper'} = new Data::Dumper([$place_holder]) ;
2121             $self->{'quick_dumper'}->Terse(1) ;
2122             $self->{'quick_dumper'}->Indent(0) ;
2123             }
2124            
2125             $self->{'expr_ballon_msg'} = ' ' ;
2126            
2127             $self->{'expr_balloon'}->attach($txt, -initwait => 300,
2128             -msg => \$self->{'expr_ballon_msg'},
2129             -balloonposition => 'mouse',
2130             -postcommand => \&Devel::ptkdb::balloon_post,
2131             -motioncommand => \&Devel::ptkdb::balloon_motion ) ;
2132            
2133             # tags for the text
2134            
2135             my @stopTagConfig = ( -foreground => 'white', -background => $mw->optionGet("stopcolor", "background") || $ENV{'PTKDB_STOP_TAG_COLOR'} || 'blue' ) ;
2136            
2137             my $stopFnt = $mw->optionGet("stopfont", "background") || $ENV{'PTKDB_STOP_TAG_FONT'} ;
2138             push @stopTagConfig, ( -font => $stopFnt ) if $stopFnt ; # user may not have specified a font, if not, stay with the default
2139            
2140             $txt->tagConfigure('stoppt', @stopTagConfig) ;
2141             $txt->tagConfigure('search_tag', "-background" => $mw->optionGet("searchtagcolor", "background") || "green") ;
2142            
2143             $txt->tagConfigure("breakableLine", -overstrike => 0) ;
2144             $txt->tagConfigure("nonbreakableLine", -overstrike => 1) ;
2145             $txt->tagConfigure("breaksetLine", -background => $mw->optionGet("breaktagcolor", "background") || $ENV{'PTKDB_BRKPT_COLOR'} || 'red') ;
2146             $txt->tagConfigure("breakdisabledLine", -background => $mw->optionGet("disabledbreaktagcolor", "background") || $ENV{'PTKDB_DISABLEDBRKPT_COLOR'} || 'green') ;
2147            
2148             $txt->tagBind("breakableLine", '', [ \&Devel::ptkdb::set_breakpoint_tag, $self, Ev('@'), 1 ] ) ;
2149             $txt->tagBind("breakableLine", '', [ \&Devel::ptkdb::set_breakpoint_tag, $self, Ev('@'), 0 ] ) ;
2150            
2151             $txt->tagBind("breaksetLine", '', [ \&Devel::ptkdb::clear_breakpoint_tag, $self, Ev('@') ] ) ;
2152             $txt->tagBind("breaksetLine", '', [ \&Devel::ptkdb::change_breakpoint_tag, $self, Ev('@'), 0 ] ) ;
2153            
2154             $txt->tagBind("breakdisabledLine", '', [ \&Devel::ptkdb::clear_breakpoint_tag, $self, Ev('@') ] ) ;
2155             $txt->tagBind("breakdisabledLine", '', [ \&Devel::ptkdb::change_breakpoint_tag, $self, Ev('@'), 1 ] ) ;
2156            
2157             } # end of configure_text
2158              
2159              
2160             sub setup_options {
2161             my ($self) = @_ ;
2162             my $mw = $self->{main_window} ;
2163              
2164             return unless $mw->can('appname') ;
2165            
2166             $mw->appname("ptkdb") ;
2167             $mw->optionAdd("stopcolor" => 'cyan', 60 ) ;
2168             $mw->optionAdd("stopfont" => 'fixed', 60 ) ;
2169             $mw->optionAdd("breaktag" => 'red', 60 ) ;
2170             $mw->optionAdd("searchtagcolor" => 'green') ;
2171            
2172             $mw->optionClear ; # necessary to reload xresources
2173              
2174             } # end of setup_options
2175              
2176             sub DoAlert {
2177             my($self, $msg, $title) = @_ ;
2178             my($dlg) ;
2179             my $okaySub = sub {
2180             destroy $dlg ;
2181             } ;
2182              
2183             $dlg = $self->{main_window}->Toplevel(-title => $title || "Alert", -overanchor => 'cursor') ;
2184              
2185             $dlg->Label( -text => $msg )->pack( -side => 'top' ) ;
2186              
2187             $dlg->Button( -text => "Okay", -command => $okaySub )->pack( -side => 'top' )->focus ;
2188             $dlg->bind('', $okaySub) ;
2189              
2190             } # end of DoAlert
2191              
2192             sub simplePromptBox {
2193             my ($self, $title, $defaultText, $okaySub, $cancelSub) = @_ ;
2194             my ($top, $entry, $okayBtn) ;
2195              
2196             $top = $self->{main_window}->Toplevel(-title => $title, -overanchor => 'cursor' ) ;
2197              
2198             $Devel::ptkdb::promptString = $defaultText ;
2199              
2200             $entry = $top->Entry('-textvariable' => \$Devel::ptkdb::promptString)->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2201            
2202            
2203             $okayBtn = $top->Button( -text => "Okay", @Devel::ptkdb::button_font, -command => sub { &$okaySub() ; $top->destroy ;}
2204             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2205            
2206             $top->Button( -text => "Cancel", -command => sub { &$cancelSub() if $cancelSub ; $top->destroy() }, @Devel::ptkdb::button_font,
2207             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2208            
2209             $entry->icursor('end') ;
2210            
2211             $entry->selectionRange(0, 'end') if $entry->can('selectionRange') ; # some win32 Tk installations can't do this
2212              
2213             $entry->focus() ;
2214              
2215             return $top ;
2216            
2217             } # end of simplePromptBox
2218              
2219             sub get_entry_text {
2220             my($self) = @_ ;
2221            
2222             return $self->{entry}->get() ; # get the text in the entry
2223             } # end of get_entry_text
2224              
2225              
2226             #
2227             # Clear any text that is in the entry field. If there
2228             # was any text in that field return it. If there
2229             # was no text then return any selection that may be active.
2230             #
2231             sub clear_entry_text {
2232             my($self) = @_ ;
2233             my $str = $self->{'entry'}->get() ;
2234             $self->{'entry'}->delete(0, 'end') ;
2235              
2236             #
2237             # No String
2238             # Empty String
2239             # Or a string that is only whitespace
2240             #
2241             if( !$str || $str eq "" || $str =~ /^\s+$/ ) {
2242             #
2243             # If there is no string or the string is just white text
2244             # Get the text in the selction( if any)
2245             #
2246             if( $self->{'text'}->tagRanges('sel') ) { # check to see if 'sel' tag exists (return undef value)
2247             $str = $self->{'text'}->get("sel.first", "sel.last") ; # get the text between the 'first' and 'last' point of the sel (selection) tag
2248             }
2249             # If still no text, bring the focus to the entry
2250             elsif( !$str || $str eq "" || $str =~ /^\s+$/ ) {
2251             $self->{'entry'}->focus() ;
2252             $str = "" ;
2253             }
2254             }
2255             #
2256             # Erase existing text
2257             #
2258             return $str ;
2259             } # end of clear_entry_text
2260              
2261             sub brkPtCheckbutton {
2262             my ($self, $fname, $idx, $brkPt) = @_ ;
2263             my ($widg) ;
2264              
2265             change_breakpoint_tag($self->{'text'}, $self, "$idx.0", $brkPt->{'value'}) if $fname eq $self->{'current_file'} ;
2266              
2267             } # end of brkPtCheckbutton
2268              
2269             #
2270             # insert a breakpoint control into our breakpoint list.
2271             # returns a handle to the control
2272             #
2273             # Expression, if defined, is to be evaluated at the breakpoint
2274             # and execution stopped if it is non-zero/defined.
2275             #
2276             # If action is defined && True then it will be evalled
2277             # before continuing.
2278             #
2279             sub insertBreakpoint {
2280             my ($self, $fname, @brks) = @_ ;
2281             my ($btn, $cnt, $item) ;
2282              
2283             my($offset) ;
2284              
2285             local(*dbline) = $main::{'_<' . $fname} ;
2286              
2287             $offset = $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ? 1 : 0 ;
2288              
2289             while( @brks ) {
2290             my($index, $value, $expression) = splice @brks, 0, 3 ; # take args 3 at a time
2291              
2292             my $brkPt = {} ;
2293             my $txt = &DB::getdbtextline($fname, $index) ;
2294             @$brkPt{'type', 'line', 'expr', 'value', 'fname', 'text'} =
2295             ('user', $index, $expression, $value, $fname, "$txt") ;
2296              
2297             &DB::setdbline($fname, $index + $offset, $brkPt) ;
2298             $self->add_brkpt_to_brkpt_page($brkPt) ;
2299              
2300             next unless $fname eq $self->{'current_file'} ;
2301              
2302             $self->{'text'}->tagRemove("breakableLine", "$index.0", "$index.$Devel::ptkdb::linenumber_length") ;
2303             $self->{'text'}->tagAdd($value ? "breaksetLine" : "breakdisabledLine", "$index.0", "$index.$Devel::ptkdb::linenumber_length") ;
2304             } # end of loop
2305             } # end of insertBreakpoint
2306              
2307             sub add_brkpt_to_brkpt_page {
2308             my($self, $brkPt) = @_ ;
2309             my($btn, $fname, $index, $frm, $upperFrame, $lowerFrame) ;
2310             my ($row, $btnName, $width) ;
2311             #
2312             # Add the breakpoint to the breakpoints page
2313             #
2314             ($fname, $index) = @$brkPt{'fname', 'line'} ;
2315             return if exists $self->{'breakpts_table_data'}->{"$fname:$index"} ;
2316             $self->{'brkPtCnt'} += 1 ;
2317              
2318             $btnName = $fname ;
2319             $btnName =~ s/.*\/([^\/]*)$/$1/o ;
2320              
2321             # take the last leaf of the pathname
2322            
2323             $frm = $self->{'breakpts_table'}->Frame(-relief => 'raised') ;
2324             $upperFrame = $frm->Frame()->pack(-side => 'top', '-fill' => 'x', -expand => 1) ;
2325              
2326            
2327             $btn = $upperFrame->Checkbutton(-text => "$btnName:$index",
2328             -variable => \$brkPt->{'value'}, # CAUTION value tracking
2329             -command => sub { $self->brkPtCheckbutton($fname, $index, $brkPt) }) ;
2330              
2331             $btn->pack(-side => 'left') ;
2332              
2333             $btn = $upperFrame->Button(-text => "Delete", -command => sub { $self->removeBreakpoint($fname, $index) ; } ) ;
2334             $btn->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2335            
2336             $btn = $upperFrame->Button(-text => "Goto", -command => sub { $self->set_file($fname, $index) ; } ) ;
2337             $btn->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2338              
2339             $lowerFrame = $frm->Frame()->pack(-side => 'top', '-fill' => 'x', -expand => 1) ;
2340              
2341             $lowerFrame->Label(-text => "Cond:")->pack(-side => 'left') ;
2342            
2343             $btn = $lowerFrame->Entry(-textvariable => \$brkPt->{'expr'}) ;
2344             $btn->pack(-side => 'left', -fill => 'x', -expand => 1) ;
2345            
2346             $frm->pack(-side => 'top', -fill => 'x', -expand => 1) ;
2347              
2348             $row = pop @{$self->{'brkPtSlots'}} or $row = $self->{'brkPtCnt'} ;
2349              
2350             $self->{'breakpts_table'}->put($row, 1, $frm) ;
2351            
2352             $self->{'breakpts_table_data'}->{"$fname:$index"}->{'frm'} = $frm ;
2353             $self->{'breakpts_table_data'}->{"$fname:$index"}->{'row'} = $row ;
2354              
2355             $self->{'main_window'}->update ;
2356              
2357             $width = $frm->width ;
2358              
2359             if ( $width > $self->{'breakpts_table'}->width ) {
2360             $self->{'notebook'}->configure(-width => $width) ;
2361             }
2362            
2363             } # end of add_brkpt_to_brkpt_page
2364              
2365             sub remove_brkpt_from_brkpt_page {
2366             my($self, $fname, $idx) = @_ ;
2367             my($table) ;
2368              
2369             $table = $self->{'breakpts_table'} ;
2370              
2371             # Delete the breakpoint control in the breakpoints window
2372              
2373             $table->put($self->{'breakpts_table_data'}->{"$fname:$idx"}->{'row'}, 1) ; # delete?
2374              
2375             #
2376             # Add this now empty slot to the list of ones we have open
2377             #
2378              
2379             push @{$self->{'brkPtSlots'}}, $self->{'breakpts_table_data'}->{"$fname:$idx"}->{'row'} ;
2380              
2381             $self->{'brkPtSlots'} = [ sort { $b <=> $a } @{$self->{'brkPtSlots'}} ] ;
2382            
2383             delete $self->{'breakpts_table_data'}->{"$fname:$idx"} ;
2384            
2385             $self->{'brkPtCnt'} -= 1 ;
2386              
2387             } # end of remove_brkpt_from_brkpt_page
2388              
2389              
2390             #
2391             # Supporting the "Run To Here..." command
2392             #
2393             sub insertTempBreakpoint {
2394             my ($self, $fname, $index) = @_ ;
2395             my($offset) ;
2396             local(*dbline) = $main::{'_<' . $fname} ;
2397              
2398             $offset = $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ? 1 : 0 ;
2399              
2400             return if( &DB::getdbline($fname, $index + $offset) ) ; # we already have a breakpoint here
2401              
2402             &DB::setdbline($fname, $index + $offset, {'type' => 'temp', 'line' => $index, 'value' => 1 } ) ;
2403              
2404             } # end of insertTempBreakpoint
2405              
2406             sub reinsertBreakpoints {
2407             my ($self, $fname) = @_ ;
2408             my ($brkPt) ;
2409              
2410             foreach $brkPt ( &DB::getbreakpoints($fname) ) {
2411             #
2412             # Our breakpoints are indexed by line
2413             # therefore we can have 'gaps' where there
2414             # lines, but not breaks set for them.
2415             #
2416             next unless defined $brkPt ;
2417            
2418             $self->insertBreakpoint($fname, @$brkPt{'line', 'value', 'expr'}) if( $brkPt->{'type'} eq 'user' ) ;
2419             $self->insertTempBreakpoint($fname, $brkPt->{line}) if( $brkPt->{'type'} eq 'temp' ) ;
2420             } # end of reinsert loop
2421              
2422             } # end of reinsertBreakpoints
2423              
2424             sub removeBreakpointTags {
2425             my ($self, @brkPts) = @_ ;
2426             my($idx, $brkPt) ;
2427            
2428             foreach $brkPt (@brkPts) {
2429              
2430             $idx = $brkPt->{'line'} ;
2431              
2432             if ( $brkPt->{'value'} ) {
2433             $self->{'text'}->tagRemove("breaksetLine", "$idx.0", "$idx.$Devel::ptkdb::linenumber_length") ;
2434             }
2435             else {
2436             $self->{'text'}->tagRemove("breakdisabledLine", "$idx.0", "$idx.$Devel::ptkdb::linenumber_length") ;
2437             }
2438            
2439             $self->{'text'}->tagAdd("breakableLine", "$idx.0", "$idx.$Devel::ptkdb::linenumber_length") ;
2440             }
2441             } # end of removeBreakpointTags
2442              
2443             #
2444             # Remove a breakpoint from the current window
2445             #
2446             sub removeBreakpoint {
2447             my ($self, $fname, @idx) = @_ ;
2448             my ($idx, $chkIdx, $i, $j, $info) ;
2449             my($offset) ;
2450             local(*dbline) = $main::{'_<' . $fname} ;
2451              
2452             $offset = $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ? 1 : 0 ;
2453              
2454             foreach $idx (@idx) { # end of removal loop
2455             next unless defined $idx ;
2456             my $brkPt = &DB::getdbline($fname, $idx + $offset) ;
2457             next unless $brkPt ; # if we do not have an entry
2458             &DB::cleardbline($fname, $idx + $offset) ;
2459              
2460             $self->remove_brkpt_from_brkpt_page($fname, $idx) ;
2461              
2462             next unless $brkPt->{fname} eq $self->{'current_file'} ; # if this isn't our current file there will be no controls
2463              
2464             # Delete the ext associated with the breakpoint expression (if any)
2465              
2466             $self->removeBreakpointTags($brkPt) ;
2467             } # end of remove loop
2468            
2469             return ;
2470             } # end of removeBreakpoint
2471              
2472             sub removeAllBreakpoints {
2473             my ($self, $fname) = @_ ;
2474            
2475             $self->removeBreakpoint($fname, &DB::getdblineindexes($fname)) ;
2476              
2477             } # end of removeAllBreakpoints
2478              
2479             #
2480             # Delete expressions prior to an update
2481             #
2482             sub deleteAllExprs {
2483             my ($self) = @_ ;
2484             $self->{'data_list'}->delete('all') ;
2485             } # end of deleteAllExprs
2486              
2487             sub EnterExpr {
2488             my ($self) = @_ ;
2489             my $str = $self->clear_entry_text() ;
2490             if( $str && $str ne "" && $str !~ /^\s+$/ ) { # if there is an expression and it's more than white space
2491             $self->{'expr'} = $str ;
2492             $self->{'event'} = 'expr' ;
2493             }
2494             } # end of EnterExpr
2495              
2496             #
2497             #
2498             #
2499             sub QuickExpr {
2500             my ($self) = @_ ;
2501              
2502             my $str = $self->{'quick_entry'}->get() ;
2503              
2504             if( $str && $str ne "" && $str !~ /^\s+$/ ) { # if there is an expression and it's more than white space
2505             $self->{'qexpr'} = $str ;
2506             $self->{'event'} = 'qexpr' ;
2507             }
2508             } # end of QuickExpr
2509              
2510             sub deleteExpr {
2511             my ($self) = @_ ;
2512             my ($entry, $i, @indexes) ;
2513             my @sList = $self->{'data_list'}->info('select') ;
2514              
2515             #
2516             # if we're deleteing a top level expression
2517             # we have to take it out of the list of expressions
2518             #
2519              
2520             foreach $entry ( @sList ) {
2521             next if ($entry =~ /\//) ; # goto next expression if we're not a top level ( expr/entry)
2522             $i = 0 ;
2523             grep { push @indexes, $i if ($_->{'expr'} eq $entry) ; $i++ ; } @{$self->{'expr_list'}} ;
2524             } # end of check loop
2525            
2526             # now take out our list of indexes ;
2527              
2528             for( 0..$#indexes ) {
2529             splice @{$self->{'expr_list'}}, $indexes[$_] - $_, 1 ;
2530             }
2531              
2532             for( @sList ) {
2533             $self->{'data_list'}->delete('entry', $_) ;
2534             }
2535             } # end of deleteExpr
2536              
2537             sub fixExprPath {
2538             my(@pathList) = @_ ;
2539            
2540             for (@pathList) {
2541             s/$Devel::ptkdb::pathSep/$Devel::ptkdb::pathSepReplacement/go ;
2542             } # end of path list
2543              
2544             return $pathList[0] unless wantarray ;
2545             return @pathList ;
2546              
2547             } # end of fixExprPath
2548              
2549             ##
2550             ## Inserts an expression($theRef) into an HList Widget($dl). If the expression
2551             ## is an array, blessed array, hash, or blessed hash(typical object), then this
2552             ## routine is called recursively, adding the members to the next level of heirarchy,
2553             ## prefixing array members with a [idx] and the hash members with the key name.
2554             ## This continues until the entire expression is decomposed to it's atomic constituents.
2555             ## Protection is given(with $reusedRefs) to ensure that 'circular' references within
2556             ## arrays or hashes(i.e. where a member of a array or hash contains a reference to a
2557             ## parent element within the heirarchy.
2558             ##
2559             #
2560             # Returns 1 if sucessfully added 0 if not
2561             #
2562             sub insertExpr {
2563             my($self, $reusedRefs, $dl, $theRef, $name, $depth, $dirPath) = @_ ;
2564             my($label, $type, $result, $selfCnt, @circRefs) ;
2565             local($^W) = 0 ; # spare us uncessary warnings about comparing strings with ==
2566              
2567             #
2568             # Add data new data entries to the bottom
2569             #
2570             $dirPath = "" unless defined $dirPath ;
2571              
2572             $label = "" ;
2573             $selfCnt = 0 ;
2574              
2575             while( ref $theRef eq 'SCALAR' ) {
2576             $theRef = $$theRef ;
2577             }
2578             REF_CHECK: for( ; ; ) {
2579             push @circRefs, $theRef ;
2580             $type = ref $theRef ;
2581             last unless ($type eq "REF") ;
2582             $theRef = $$theRef ; # dref again
2583            
2584             $label .= "\\" ; # append a
2585             if( grep $_ == $theRef, @circRefs ) {
2586             $label .= "(circular)" ;
2587             last ;
2588             }
2589             }
2590              
2591             if( !$type || $type eq "" || $type eq "GLOB" || $type eq "CODE") {
2592             eval {
2593             if( !defined $theRef ) {
2594             $dl->add($dirPath . $name, -text => "$name = $label" . "undef") ;
2595             }
2596             else {
2597             $dl->add($dirPath . $name, -text => "$name = $label$theRef") ;
2598             }
2599             } ;
2600             $self->DoAlert($@), return 0 if $@ ;
2601             return 1 ;
2602             }
2603              
2604             if( $type eq 'ARRAY' or "$theRef" =~ /ARRAY/ ) {
2605             my ($r, $idx) ;
2606             $idx = 0 ;
2607             eval {
2608             $dl->add($dirPath . $name, -text => "$name = $theRef") ;
2609             } ;
2610             if( $@ ) {
2611             $self->DoAlert($@) ;
2612             return 0 ;
2613             }
2614             $result = 1 ;
2615             foreach $r ( @{$theRef} ) {
2616              
2617             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
2618             eval {
2619             $dl->add($dirPath . fixExprPath($name) . $Devel::ptkdb::pathSep . "__ptkdb_self_path" . $selfCnt++, -text => "[$idx] = $r REUSED ADDR") ;
2620             } ;
2621             $self->DoAlert($@) if( $@ ) ;
2622             next ;
2623             }
2624            
2625             push @$reusedRefs, $r ;
2626             $result = $self->insertExpr($reusedRefs, $dl, $r, "[$idx]", $depth-1, $dirPath . fixExprPath($name) . $Devel::ptkdb::pathSep) unless $depth == 0 ;
2627             pop @$reusedRefs ;
2628              
2629             return 0 unless $result ;
2630             $idx += 1 ;
2631             }
2632             return 1 ;
2633             } # end of array case
2634              
2635             if( "$theRef" !~ /HASH\050\060x[0-9a-f]*\051/o ) {
2636             eval {
2637             $dl->add($dirPath . fixExprPath($name), -text => "$name = $theRef") ;
2638             } ;
2639             if( $@ ) {
2640             $self->DoAlert($@) ;
2641             return 0 ;
2642             }
2643             return 1 ;
2644             }
2645             #
2646             # Anything else at this point is
2647             # either a 'HASH' or an object
2648             # of some kind.
2649             #
2650             my($r, @theKeys, $idx) ;
2651             $idx = 0 ;
2652             @theKeys = sort keys %{$theRef} ;
2653             $dl->add($dirPath . $name, -text => "$name = " . "$theRef") ;
2654             $result = 1 ;
2655              
2656             foreach $r ( @$theRef{@theKeys} ) { # slice out the values with the sorted list
2657              
2658             if( grep $_ == $r, @$reusedRefs ) { # check to make sure that we're not doing a single level self reference
2659             eval {
2660             $dl->add($dirPath . fixExprPath($name) . $Devel::ptkdb::pathSep . "__ptkdb_self_path" . $selfCnt++, -text => "$theKeys[$idx++] = $r REUSED ADDR") ;
2661             } ;
2662             print "bad path $@\n" if( $@ ) ;
2663             next ;
2664             }
2665              
2666             push @$reusedRefs, $r ;
2667              
2668             $result = $self->insertExpr($reusedRefs, # recursion protection
2669             $dl, # data list widget
2670             $r, # reference whose value is displayed
2671             $theKeys[$idx], # name
2672             $depth-1, # remaining expansion depth
2673             $dirPath . $name . $Devel::ptkdb::pathSep # path to add to
2674             ) unless $depth == 0 ;
2675              
2676             pop @$reusedRefs ;
2677              
2678             return 0 unless $result ;
2679             $idx += 1 ;
2680             } # end of ref add loop
2681              
2682             return 1 ;
2683             } # end of insertExpr
2684              
2685             #
2686             # We're setting the line where we are stopped.
2687             # Create a tag for this and set it as bold.
2688             #
2689             sub set_line {
2690             my ($self, $lineno) = @_ ;
2691             my $text = $self->{'text'} ;
2692              
2693             return if( $lineno <= 0 ) ;
2694              
2695             if( $self->{current_line} > 0 ) {
2696             $text->tagRemove('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
2697             }
2698             $self->{current_line} = $lineno - $self->{'line_offset'} ;
2699             $text->tagAdd('stoppt', "$self->{current_line}.0 linestart", "$self->{current_line}.0 lineend") ;
2700              
2701             $self->{'text'}->see("$self->{current_line}.0 linestart") ;
2702             } # end of set_line
2703              
2704             #
2705             # Set the file that is in the code window.
2706             #
2707             # $fname the 'new' file to view
2708             # $line the line number we're at
2709             # $brkPts any breakpoints that may have been set in this file
2710             #
2711              
2712             use Carp ;
2713              
2714             sub set_file {
2715             my ($self, $fname, $line) = @_ ;
2716             my ($lineStr, $offset, $text, $i, @text, $noCode, $title) ;
2717             my (@breakableTagList, @nonBreakableTagList) ;
2718              
2719             return unless $fname ; # we're getting an undef here on 'Restart...'
2720              
2721             local(*dbline) = $main::{'_<' . $fname};
2722              
2723             #
2724             # with the #! /usr/bin/perl -d:ptkdb at the header of the file
2725             # we've found that with various combinations of other options the
2726             # files haven't come in at the right offsets
2727             #
2728             $offset = 0 ;
2729             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ;
2730             $self->{'line_offset'} = $offset ;
2731              
2732             $text = $self->{'text'} ;
2733            
2734             if( $fname eq $self->{current_file} ) {
2735             $self->set_line($line) ;
2736             return ;
2737             } ;
2738              
2739             $title = $fname ; # removing the - messes up stashes on -e invocations
2740             $title =~ s/^\-// ; # Tk does not like leadiing '-'s
2741             $self->{main_window}->configure('-title' => $title) ;
2742              
2743             # Erase any existing text
2744              
2745             $text->delete('0.0','end') ;
2746            
2747             my $len = $Devel::ptkdb::linenumber_length ;
2748              
2749             #
2750             # This is the tightest loop we have in the ptkdb code.
2751             # It is here where performance is the most critical.
2752             # The map block formats perl code for display. Since
2753             # the file could be potentially large, we will try
2754             # to make this loop as thin as possible.
2755             #
2756             # NOTE: For a new perl individual this may appear as
2757             # if it was intentionally obfuscated. This is not
2758             # not the case. The following code is the result
2759             # of an intensive effort to optimize this code.
2760             # Prior versions of this code were quite easier
2761             # to read, but took 3 times longer.
2762             #
2763              
2764             $lineStr = " " x 200 ; # pre-allocate space for $lineStr
2765             $i = 1 ;
2766              
2767             local($^W) = 0 ; # spares us useless warnings under -w when checking $dbline[$_] != 0
2768             #
2769             # The 'map' call will build list of 'string', 'tag' pairs
2770             # that will become arguments to the 'insert' call. Passing
2771             # the text to insert "all at once" rather than one insert->('end', 'string', 'tag')
2772             # call at time provides a MASSIVE savings in execution time.
2773             #
2774             $noCode = ($#dbline - ($offset + 1)) < 0 ;
2775              
2776             $text->insert('end', map {
2777             #
2778             # build collections of tags representing
2779             # the line numbers for breakable and
2780             # non-breakable lines. We apply these
2781             # tags after we've built the text
2782             #
2783            
2784             ($_ != 0 && push @breakableTagList, "$i.0", "$i.$len") || push @nonBreakableTagList, "$i.0", "$i.$len" ;
2785              
2786             $lineStr = sprintf($Devel::ptkdb::linenumber_format, $i++) . $_ ; # line number + text of the line
2787              
2788             substr $lineStr, -2, 1, '' if $isWin32 ; # removes the CR from win32 instances
2789              
2790             $lineStr .= "\n" unless /\n$/o ; # append a \n if there isn't one already
2791              
2792             ($lineStr, 'code') ; # return value for block, a string,tag pair for text insert
2793            
2794             } @dbline[$offset+1 .. $#dbline] ) unless $noCode ;
2795            
2796             #
2797             # Apply the tags that we've collected
2798             # NOTE: it was attempted to incorporate these
2799             # operations into the 'map' block above, but that
2800             # actually degraded performance.
2801             #
2802             $text->tagAdd("breakableLine", @breakableTagList) if @breakableTagList ; # apply tag to line numbers where the lines are breakable
2803             $text->tagAdd("nonbreakableLine", @nonBreakableTagList) if @nonBreakableTagList ; # apply tag to line numbers where the lines are not breakable.
2804            
2805             #
2806             # Reinsert breakpoints (if info provided)
2807             #
2808              
2809             $self->set_line($line) ;
2810             $self->{current_file} = $fname ;
2811             return $self->reinsertBreakpoints($fname) ;
2812             } # end of set_file
2813              
2814             #
2815             # Get the current line that the insert cursor is in
2816             #
2817             sub get_lineno {
2818             my ($self) = @_ ;
2819             my ($info) ;
2820            
2821             $info = $self->{'text'}->index('insert') ; # get the location for the insertion point
2822             $info =~ s/\..*$/\.0/ ;
2823              
2824             return int $info ;
2825             } # end of get_lineno
2826              
2827             sub DoGoto {
2828             my ($self, $entry) = @_ ;
2829              
2830             my $txt = $entry->get() ;
2831            
2832             $txt =~ s/(\d*).*/$1/ ; # take the first blob of digits
2833             if( $txt eq "" ) {
2834             print "invalid text range\n" ;
2835             return if $txt eq "" ;
2836             }
2837            
2838             $self->{'text'}->see("$txt.0") ;
2839            
2840             $entry->selectionRange(0, 'end') if $entry->can('selectionRange')
2841              
2842             } # end of DoGoto
2843              
2844             sub GotoLine {
2845             my ($self) = @_ ;
2846             my ($topLevel) ;
2847              
2848             if( $self->{goto_window} ) {
2849             $self->{goto_window}->raise() ;
2850             $self->{goto_text}->focus() ;
2851             return ;
2852             }
2853              
2854             #
2855             # Construct a dialog that has an
2856             # entry field, okay and cancel buttons
2857             #
2858             my $okaySub = sub { $self->DoGoto($self->{'goto_text'}) } ;
2859            
2860             $topLevel = $self->{main_window}->Toplevel(-title => "Goto Line?", -overanchor => 'cursor') ;
2861              
2862             $self->{goto_text} = $topLevel->Entry()->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2863              
2864             $self->{goto_text}->bind('', $okaySub) ; # make a CR do the same thing as pressing an okay
2865              
2866             $self->{goto_text}->focus() ;
2867              
2868             # Bind a double click on the mouse button to the same action
2869             # as pressing the Okay button
2870              
2871             $topLevel->Button( -text => "Okay", -command => $okaySub, @Devel::ptkdb::button_font,
2872             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2873              
2874             #
2875             # Subroutone called when the 'Dismiss'
2876             # button is pushed.
2877             #
2878             my $dismissSub = sub {
2879             delete $self->{goto_text} ;
2880             destroy {$self->{goto_window}} ;
2881             delete $self->{goto_window} ; # remove the entry from our hash so we won't
2882             } ;
2883              
2884             $topLevel->Button( -text => "Dismiss", @Devel::ptkdb::button_font,
2885             -command => $dismissSub )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2886              
2887             $topLevel->protocol('WM_DELETE_WINDOW', sub { destroy $topLevel ; } ) ;
2888              
2889             $self->{goto_window} = $topLevel ;
2890              
2891             } # end of GotoLine
2892              
2893              
2894             #
2895             # Subroutine called when the 'okay' button is pressed
2896             #
2897             sub FindSearch {
2898             my ($self, $entry, $btn, $regExp) = @_ ;
2899             my (@switches, $result) ;
2900             my $txt = $entry->get() ;
2901              
2902             return if $txt eq "" ;
2903              
2904             push @switches, "-forward" if $self->{fwdOrBack} eq "forward" ;
2905             push @switches, "-backward" if $self->{fwdOrBack} eq "backward" ;
2906            
2907             if( $regExp ) {
2908             push @switches, "-regexp" ;
2909             }
2910             else {
2911             push @switches, "-nocase" ; # if we're not doing regex we may as well do caseless search
2912             }
2913              
2914             $result = $self->{'text'}->search(@switches, $txt, $self->{search_start}) ;
2915              
2916             # untag the previously found text
2917              
2918             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2919              
2920             if( !$result || $result eq "" ) {
2921             # No Text was found
2922             $btn->flash() ;
2923             $btn->bell() ;
2924              
2925             delete $self->{search_tag} ;
2926             $self->{'search_start'} = "0.0" ;
2927             }
2928             else { # text found
2929             $self->{'text'}->see($result) ;
2930             # set the insertion of the text as well
2931             $self->{'text'}->markSet('insert' => $result) ;
2932             my $len = length $txt ;
2933              
2934             if( $self->{fwdOrBack} ) {
2935             $self->{search_start} = "$result +$len chars" ;
2936             $self->{search_tag} = [ $result, $self->{search_start} ] ;
2937             }
2938             else {
2939             # backwards search
2940             $self->{search_start} = "$result -$len chars" ;
2941             $self->{search_tag} = [ $result, "$result +$len chars" ] ;
2942             }
2943              
2944             # tag the newly found text
2945              
2946             $self->{'text'}->tagAdd('search_tag', @{$self->{search_tag}}) ;
2947             } # end of text found
2948              
2949             $entry->selectionRange(0, 'end') if $entry->can('selectionRange') ;
2950              
2951             } # end of FindSearch
2952              
2953              
2954             #
2955             # Support for the Find Text... Menu command
2956             #
2957             sub FindText {
2958             my ($self) = @_ ;
2959             my ($top, $entry, $rad1, $rad2, $chk, $regExp, $frm, $okayBtn) ;
2960              
2961             #
2962             # if we already have the Find Text Window
2963             # open don't bother openning another, bring
2964             # the existing one to the front.
2965             #
2966             if( $self->{find_window} ) {
2967             $self->{find_window}->raise() ;
2968             $self->{find_text}->focus() ;
2969             return ;
2970             }
2971              
2972             $self->{search_start} = $self->{'text'}->index('insert') if( $self->{search_start} eq "" ) ;
2973              
2974             #
2975             # Subroutine called when the 'Dismiss' button
2976             # is pushed.
2977             #
2978             my $dismissSub = sub {
2979             $self->{'text'}->tagRemove('search_tag', @{$self->{search_tag}}) if defined $self->{search_tag} ;
2980             $self->{search_start} = "" ;
2981             destroy {$self->{find_window}} ;
2982             delete $self->{search_tag} ;
2983             delete $self->{find_window} ;
2984             } ;
2985              
2986             #
2987             # Construct a dialog that has an entry field, forward, backward, regex option, okay and cancel buttons
2988             #
2989             $top = $self->{main_window}->Toplevel(-title => "Find Text?") ;
2990              
2991             $self->{find_text} = $top->Entry()->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2992              
2993            
2994             $frm = $top->Frame()->pack(-side => 'top', -fill => 'both', -expand => 1) ;
2995              
2996             $self->{fwdOrBack} = 'forward' ;
2997             $rad1 = $frm->Radiobutton(-text => "Forward", -value => 1, -variable => \$self->{fwdOrBack}) ;
2998             $rad1->pack(-side => 'left', -fill => 'both', -expand => 1) ;
2999             $rad2 = $frm->Radiobutton(-text => "Backward", -value => 0, -variable => \$self->{fwdOrBack}) ;
3000             $rad2->pack(-side => 'left', -fill => 'both', -expand => 1) ;
3001              
3002             $regExp = 0 ;
3003             $chk = $frm->Checkbutton(-text => "RegExp", -variable => \$regExp) ;
3004             $chk->pack(-side => 'left', -fill => 'both', -expand => 1) ;
3005              
3006             # Okay and cancel buttons
3007              
3008             # Bind a double click on the mouse button to the same action
3009             # as pressing the Okay button
3010              
3011             $okayBtn = $top->Button( -text => "Okay", -command => sub { $self->FindSearch($self->{find_text}, $okayBtn, $regExp) ; },
3012             @Devel::ptkdb::button_font,
3013             )->pack(-side => 'left', -fill => 'both', -expand => 1) ;
3014              
3015             $self->{find_text}->bind('', sub { $self->FindSearch($self->{find_text}, $okayBtn, $regExp) ; }) ;
3016              
3017             $top->Button( -text => "Dismiss", @Devel::ptkdb::button_font,
3018             -command => $dismissSub)->pack(-side => 'left', -fill => 'both', -expand => 1) ;
3019              
3020             $top->protocol('WM_DELETE_WINDOW', $dismissSub) ;
3021              
3022             $self->{find_text}->focus() ;
3023              
3024             $self->{find_window} = $top ;
3025              
3026             } # end of FindText
3027              
3028             sub main_loop {
3029             my ($self) = @_ ;
3030             my ($evt, $str, $result) ;
3031             my $i = 0;
3032             SWITCH: for ($self->{'event'} = 'null' ; ; $self->{'event'} = undef ) {
3033              
3034             Tk::DoOneEvent(0);
3035             next unless $self->{'event'} ;
3036              
3037             $evt = $self->{'event'} ;
3038             $evt =~ /step/o && do { last SWITCH ; } ;
3039             $evt =~ /null/o && do { next SWITCH ; } ;
3040             $evt =~ /run/o && do { last SWITCH ; } ;
3041             $evt =~ /quit/o && do { $self->DoQuit ; } ;
3042             $evt =~ /expr/o && do { return $evt ; } ; # adds an expression to our expression window
3043             $evt =~ /qexpr/o && do { return $evt ; } ; # does a 'quick' expression
3044             $evt =~ /update/o && do { return $evt ; } ; # forces an update on our expression window
3045             $evt =~ /reeval/o && do { return $evt ; } ; # updated the open expression eval window
3046             $evt =~ /balloon_eval/ && do { return $evt } ;
3047             } # end of switch block
3048             return $evt ;
3049             } # end of main_loop
3050              
3051             #
3052             # $subStackRef A reference to the current subroutine stack
3053             #
3054              
3055             sub goto_sub_from_stack {
3056             my ($self, $f, $lineno) = @_ ;
3057             $self->set_file($f, $lineno) ;
3058             } # end of goto_sub_from_stack ;
3059              
3060             sub refresh_stack_menu {
3061             my ($self) = @_ ;
3062             my ($str, $name, $i, $sub_offset, $subStack) ;
3063              
3064             #
3065             # CAUTION: In the effort to 'rationalize' the code
3066             # are moving some of this function down from DB::DB
3067             # to here. $sub_offset represents how far 'down'
3068             # we are from DB::DB. The $DB::subroutine_depth is
3069             # tracked in such a way that while we are 'in' the debugger
3070             # it will not be incremented, and thus represents the stack depth
3071             # of the target program.
3072             #
3073             $sub_offset = 1 ;
3074             $subStack = [] ;
3075              
3076             # clear existing entries
3077              
3078             for( $i = 0 ; $i <= $DB::subroutine_depth ; $i++ ) {
3079             my ($package, $filename, $line, $subName) = caller $i+$sub_offset ;
3080             last if !$subName ;
3081             push @$subStack, { 'name' => $subName, 'pck' => $package, 'filename' => $filename, 'line' => $line } ;
3082             }
3083              
3084             $self->{stack_menu}->menu->delete(0, 'last') ; # delete existing menu items
3085              
3086             for( $i = 0 ; $subStack->[$i] ; $i++ ) {
3087              
3088             $str = defined $subStack->[$i+1] ? "$subStack->[$i+1]->{name}" : "MAIN" ;
3089              
3090             my ($f, $line) = ($subStack->[$i]->{filename}, $subStack->[$i]->{line}) ; # make copies of the values for use in 'sub'
3091             $self->{stack_menu}->command(-label => $str, -command => sub { $self->goto_sub_from_stack($f, $line) ; } ) ;
3092             }
3093             } # end of refresh_stack_menu
3094              
3095             no strict ;
3096              
3097             sub get_state {
3098             my ($self, $fname) = @_ ;
3099             my ($val) ;
3100             local($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
3101            
3102             do "$fname" ;
3103              
3104             if( $@ ) {
3105             $self->DoAlert($@) ;
3106             return ( undef ) x 4 ; # return a list of 4 undefined values
3107             }
3108              
3109             return ($files, $expr_list, $eval_saved_text, $main_win_geometry) ;
3110             } # end of get_state
3111              
3112             use strict ;
3113              
3114             sub restoreStateFile {
3115             my ($self, $fname) = @_ ;
3116             local(*F) ;
3117             my ($saveCurFile, $s, @n, $n) ;
3118              
3119             if (!(-e $fname && -r $fname)) {
3120             $self->DoAlert("$fname does not exist") ;
3121             return ;
3122             }
3123              
3124             my ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $self->get_state($fname) ;
3125             my ($f, $brks) ;
3126              
3127             return unless defined $files || defined $expr_list ;
3128              
3129             &DB::restore_breakpoints_from_save($files) ;
3130              
3131             #
3132             # This should force the breakpoints to be restored
3133             #
3134             $saveCurFile = $self->{current_file} ;
3135              
3136             @$self{ 'current_file', 'expr_list', 'eval_saved_text' } =
3137             ( "" , $expr_list, $eval_saved_text) ;
3138              
3139             $self->set_file($saveCurFile, $self->{current_line}) ;
3140              
3141             $self->{'event'} = 'update' ;
3142              
3143             if ( $main_win_geometry && $self->{'main_window'} ) {
3144             # restore the height and width of the window
3145             $self->{main_window}->geometry( $main_win_geometry ) ;
3146             }
3147             } # end of retstoreState
3148              
3149             sub updateEvalWindow {
3150             my ($self, @result) = @_ ;
3151             my ($leng, $str, $d) ;
3152              
3153             $leng = 0 ;
3154             for( @result ) {
3155             if( $self->{hexdump_evals} ) {
3156             # eventually put hex dumper code in here
3157            
3158             $self->{eval_results}->insert('end', hexDump($_)) ;
3159              
3160             }
3161             elsif( !$Devel::ptkdb::DataDumperAvailable || !$Devel::ptkdb::useDataDumperForEval ) {
3162             $str = "$_\n" ;
3163             }
3164             else {
3165             $d = Data::Dumper->new([ $_ ]) ;
3166             $d->Indent($Devel::ptkdb::eval_dump_indent) ;
3167             $d->Terse(1) ;
3168             if( Data::Dumper->can('Dumpxs') ) {
3169             $str = $d->Dumpxs( $_ ) ;
3170             }
3171             else {
3172             $str = $d->Dump( $_ ) ;
3173             }
3174             }
3175             $leng += length $str ;
3176             $self->{eval_results}->insert('end', $str) ;
3177             }
3178             } # end of updateEvalWindow
3179              
3180              
3181             ##
3182             ## converts non printable chars to '.' for a string
3183             ##
3184             sub printablestr {
3185             return join "", map { (ord($_) >= 32 && ord($_) < 127) ? $_ : '.' } split //, $_[0] ;
3186             }
3187              
3188             ##
3189             ## hex dump utility function
3190             ##
3191             sub hexDump {
3192             my(@retList) ;
3193             my($width) = 8 ;
3194             my($offset) ;
3195             my($len, $fmt, $n, @elems) ;
3196              
3197             for( @_ ) {
3198             my($str) ;
3199             $len = length $_ ;
3200            
3201             while($len) {
3202             $n = $len >= $width ? $width : $len ;
3203              
3204             $fmt = "\n%04X " . ("%02X " x $n ) . ( ' ' x ($width - $n) ) . " %s" ;
3205             @elems = map ord, split //, (substr $_, $offset, $n) ;
3206             $str .= sprintf($fmt, $offset, @elems, printablestr(substr $_, $offset, $n)) ;
3207             $offset += $width ;
3208              
3209             $len -= $n ;
3210             } # while
3211              
3212             push @retList, $str ;
3213             } # for
3214              
3215             return $retList[0] unless wantarray ;
3216             return @retList ;
3217             } # end of hd
3218              
3219              
3220             sub setupEvalWindow {
3221             my($self) = @_ ;
3222             my($top, $dismissSub) ;
3223             my $f ;
3224             $self->{eval_window}->focus(), return if exists $self->{eval_window} ; # already running this window?
3225              
3226             $top = $self->{main_window}->Toplevel(-title => "Evaluate Expressions...") ;
3227             $self->{eval_window} = $top ;
3228             $self->{eval_text} = $top->Scrolled('TextUndo',
3229             @Devel::ptkdb::scrollbar_cfg,
3230             @Devel::ptkdb::eval_text_font,
3231             width => 50,
3232             height => 10,
3233             -wrap => "none",
3234             )->packAdjust(-side => 'top', -fill => 'both', -expand => 1) ;
3235              
3236             $self->{eval_text}->insert('end', $self->{eval_saved_text}) if exists $self->{eval_saved_text} && defined $self->{eval_saved_text} ;
3237              
3238             $top->Label(-text, "Results:")->pack(-side => 'top', -fill => 'both', -expand => 'n') ;
3239              
3240             $self->{eval_results} = $top->Scrolled('Text',
3241             @Devel::ptkdb::scrollbar_cfg,
3242             width => 50,
3243             height => 10,
3244             -wrap => "none",
3245             @Devel::ptkdb::eval_text_font
3246             )->pack(-side => 'top', -fill => 'both', -expand => 1) ;
3247              
3248             my $btn = $top->Button(-text => 'Eval...', -command => sub { $DB::window->{event} = 'reeval' ; }
3249             )->pack(-side => 'left', -fill => 'x', -expand => 1) ;
3250              
3251             $dismissSub = sub {
3252             $self->{eval_saved_text} = $self->{eval_text}->get('0.0', 'end') ;
3253             $self->{eval_window}->destroy ;
3254             delete $self->{eval_window} ;
3255             } ;
3256              
3257             $top->protocol('WM_DELETE_WINDOW', $dismissSub ) ;
3258              
3259             $top->Button(-text => 'Clear Eval', -command => sub { $self->{eval_text}->delete('0.0', 'end') }
3260             )->pack(-side => 'left', -fill => 'x', -expand => 1) ;
3261              
3262             $top->Button(-text => 'Clear Results', -command => sub { $self->{eval_results}->delete('0.0', 'end') }
3263             )->pack(-side => 'left', -fill => 'x', -expand => 1) ;
3264              
3265             $top->Button(-text => 'Dismiss', -command => $dismissSub)->pack(-side => 'left', -fill => 'x', -expand => 1) ;
3266             $top->Checkbutton(-text => 'Hex', -variable => \$self->{hexdump_evals})->pack(-side => 'left') ;
3267              
3268             } # end of setupEvalWindow ;
3269              
3270             sub filterBreakPts {
3271             my ($breakPtsListRef, $fname) = @_ ;
3272             my $dbline = $main::{'_<' . $fname}; # breakable lines
3273             local($^W) = 0 ;
3274             #
3275             # Go through the list of breaks and take out any that
3276             # are no longer breakable
3277             #
3278              
3279             for( @$breakPtsListRef ) {
3280             next unless defined $_ ;
3281              
3282             next if $dbline->[$_->{'line'}] != 0 ; # still breakable
3283              
3284             $_ = undef ;
3285             }
3286             } # end of filterBreakPts
3287              
3288             sub DoAbout {
3289             my $self = shift ;
3290             my $str = "ptkdb $DB::VERSION\nCopyright 1998,2003 by Andrew E. Page\nFeedback to aepage\@users.sourceforge.net\n\n" ;
3291             my $threadString = "" ;
3292            
3293             $threadString = "Threads Available" if $Config::Config{usethreads} ;
3294             $threadString = " Thread Debugging Enabled" if $DB::usethreads ;
3295            
3296             $str .= <<"__STR__" ;
3297             This program is free software; you can redistribute it and/or modify
3298             it under the terms of either:
3299              
3300             a) the GNU General Public License as published by the Free
3301             Software Foundation; either version 1, or (at your option) any
3302             later version, or
3303              
3304             b) the "Artistic License" which comes with this Kit.
3305              
3306             This program is distributed in the hope that it will be useful,
3307             but WITHOUT ANY WARRANTY; without even the implied warranty of
3308             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either
3309             the GNU General Public License or the Artistic License for more details.
3310              
3311             OS $^O
3312             Tk Version $Tk::VERSION
3313             Perl Version $]
3314             Data::Dumper Version $Data::Dumper::VERSION
3315             $threadString
3316             __STR__
3317              
3318             $self->DoAlert($str, "About ptkdb") ;
3319             } # end of DoAbout
3320              
3321             #
3322             # return 1 if succesfully set,
3323             # return 0 if otherwise
3324             #
3325             sub SetBreakPoint {
3326             my ($self, $isTemp) = @_ ;
3327             my $dbw = $DB::window ;
3328             my $lineno = $dbw->get_lineno() ;
3329             my $expr = $dbw->clear_entry_text() ;
3330             local($^W) = 0 ;
3331              
3332             if( !&DB::checkdbline($DB::window->{current_file}, $lineno + $self->{'line_offset'}) ) {
3333             $dbw->DoAlert("line $lineno in $DB::window->{current_file} is not breakable") ;
3334             return 0 ;
3335             }
3336              
3337             if( !$isTemp ) {
3338             $dbw->insertBreakpoint($DB::window->{current_file}, $lineno, 1, $expr) ;
3339             return 1 ;
3340             }
3341             else {
3342             $dbw->insertTempBreakpoint($DB::window->{current_file}, $lineno) ;
3343             return 1 ;
3344             }
3345              
3346             return 0 ;
3347             } # end of SetBreakPoint
3348              
3349             sub UnsetBreakPoint {
3350             my ($self) = @_ ;
3351             my $lineno = $self->get_lineno() ;
3352            
3353             $self->removeBreakpoint($DB::window->{current_file}, $lineno) ;
3354             } # end of UnsetBreakPoint
3355              
3356             sub balloon_post {
3357             my $self = $DB::window ;
3358             my $txt = $DB::window->{'text'} ;
3359              
3360             return 0 if ($self->{'expr_ballon_msg'} eq "") || ($self->{'balloon_expr'} eq "") ; # don't post for an empty string
3361              
3362             return $self->{'balloon_coord'} ;
3363             }
3364              
3365             sub balloon_motion {
3366             my ($txt, $x, $y) = @_ ;
3367             my ($offset_x, $offset_y) = ($x + 4, $y + 4) ;
3368             my $self = $DB::window ;
3369             my $txt2 = $self->{'text'} ;
3370             my $data ;
3371              
3372             $self->{'balloon_coord'} = "$offset_x,$offset_y" ;
3373              
3374             $x -= $txt->rootx ;
3375             $y -= $txt->rooty ;
3376             #
3377             # Post an event that will cause us to put up a popup
3378             #
3379            
3380             if( $txt2->tagRanges('sel') ) { # check to see if 'sel' tag exists (return undef value)
3381             $data = $txt2->get("sel.first", "sel.last") ; # get the text between the 'first' and 'last' point of the sel (selection) tag
3382             }
3383             else {
3384             $data = $DB::window->retrieve_text_expr($x, $y) ;
3385             }
3386              
3387             if( !$data ) {
3388             $self->{'balloon_expr'} = "" ;
3389             return 0 ;
3390             }
3391            
3392             return 0 if ($data eq $self->{'balloon_expr'}) ; # nevermind if it's the same expression
3393              
3394             $self->{'event'} = 'balloon_eval' ;
3395             $self->{'balloon_expr'} = $data ;
3396              
3397             return 1 ; # ballon will be canceled and a new one put up(maybe)
3398             } # end of balloon_motion
3399              
3400             sub retrieve_text_expr {
3401             my($self, $x, $y) = @_ ;
3402             my $txt = $self->{'text'} ;
3403              
3404             my $coord = "\@$x,$y" ;
3405              
3406             my($idx, $col, $data, $offset) ;
3407              
3408             ($col, $idx) = line_number_from_coord($txt, $coord) ;
3409              
3410             $offset = $Devel::ptkdb::linenumber_length + 1 ; # line number text + 1 space
3411              
3412             return undef if $col < $offset ; # no posting
3413              
3414             $col -= $offset ;
3415              
3416             local(*dbline) = $main::{'_<' . $self->{current_file}} ;
3417              
3418             return undef if( !defined $dbline[$idx] || $dbline[$idx] == 0 ) ; # no executable text, no real variable(?)
3419            
3420             $data = $dbline[$idx] ;
3421            
3422             # if we're sitting over white space, leave
3423             my $len = length $data ;
3424             return unless $data && $col && $len > 0 ;
3425              
3426             return if substr($data, $col, 1) =~ /\s/ ;
3427              
3428             # walk backwards till we find some whitespace
3429              
3430             $col = $len if $len < $col ;
3431             while( --$col >= 0 ) {
3432             last if substr($data, $col, 1) =~ /[\s\$\@\%]/ ;
3433             }
3434              
3435             substr($data, $col) =~ /^([\$\@\%][a-zA-Z0-9_]+)/ ;
3436              
3437             return $1 ;
3438             }
3439              
3440             #
3441             # after DB::eval get's us a result
3442             #
3443             sub code_motion_eval {
3444             my ($self, @result) = @_ ;
3445             my $str ;
3446              
3447             if( exists $self->{'balloon_dumper'} ) {
3448              
3449             my $d = $self->{'balloon_dumper'} ;
3450              
3451             $d->Reset() ;
3452             $d->Values( [ $#result == 0 ? @result : \@result ] ) ;
3453              
3454             if( $d->can('Dumpxs') ) {
3455             $str = $d->Dumpxs() ;
3456             }
3457             else {
3458             $str = $d->Dump() ;
3459             }
3460              
3461             chomp($str) ;
3462             }
3463             else {
3464             $str = "@result" ;
3465             }
3466              
3467             #
3468             # Cut the string down to 1024 characters to keep from
3469             # overloading the balloon window
3470             #
3471            
3472             $self->{'expr_ballon_msg'} = "$self->{'balloon_expr'} = " . substr $str, 0, 1024 ;
3473             } # end of code motion eval
3474              
3475             #
3476             # Subroutine called when we enter DB::DB()
3477             # In other words when the target script 'stops'
3478             # in the Debugger
3479             #
3480             sub EnterActions {
3481             my($self) = @_ ;
3482              
3483             # $self->{'main_window'}->Unbusy() ;
3484              
3485             } # end of EnterActions
3486              
3487             #
3488             # Subroutine called when we return from DB::DB()
3489             # When the target script resumes.
3490             #
3491             sub LeaveActions {
3492             my($self) = @_ ;
3493            
3494             # $self->{'main_window'}->Busy() ;
3495             } # end of LeaveActions
3496              
3497              
3498             sub BEGIN {
3499             $Devel::ptkdb::scriptName = $0 ;
3500             @Devel::ptkdb::script_args = @ARGV ; # copy args
3501              
3502             }
3503              
3504             ##
3505             ## Save the ptkdb state file and restart the debugger
3506             ##
3507             sub DoRestart {
3508             my($fname) ;
3509              
3510             $fname = $ENV{'TMP'} || $ENV{'TMPDIR'} || $ENV{'TMP_DIR'} || $ENV{'TEMP'} || $ENV{'HOME'} ;
3511             $fname .= '/' if $fname ;
3512             $fname = "" unless $fname ;
3513              
3514             $fname .= "ptkdb_restart_state$$" ;
3515              
3516             # print "saving temp state file $fname\n" ;
3517            
3518             &DB::save_state_file($fname) ;
3519              
3520             $ENV{'PTKDB_RESTART_STATE_FILE'} = $fname ;
3521              
3522             ##
3523             ## build up the command to do the restart
3524             ##
3525              
3526             $fname = "perl -w -d:ptkdb $Devel::ptkdb::scriptName @Devel::ptkdb::script_args" ;
3527              
3528             # print "$$ doing a restart with $fname\n" ;
3529              
3530             exec $fname ;
3531              
3532             } # end of DoRestart
3533              
3534             ##
3535             ## Enables/Disables the feature where we stop
3536             ## if we've encountered a perl warning such as:
3537             ## "Use of uninitialized value at undef_warn.pl line N"
3538             ##
3539              
3540             sub stop_on_warning_cb {
3541             &$DB::ptkdb::warn_sig_save() if $DB::ptkdb::warn_sig_save ; # call any previously registered warning
3542             $DB::window->DoAlert(@_) ;
3543             $DB::single = 1 ; # forces debugger to stop next time
3544             }
3545              
3546             sub set_stop_on_warning {
3547              
3548             if( $DB::ptkdb::stop_on_warning ) {
3549            
3550             return if $DB::ptkdb::warn_sig_save == \&stop_on_warning_cb ; # prevents recursion
3551              
3552             $DB::ptkdb::warn_sig_save = $SIG{'__WARN__'} if $SIG{'__WARN__'} ;
3553             $SIG{'__WARN__'} = \&stop_on_warning_cb ;
3554             }
3555             else {
3556             ##
3557             ## Restore any previous warning signal
3558             ##
3559             local($^W) = 0 ;
3560             $SIG{'__WARN__'} = $DB::ptkdb::warn_sig_save ;
3561             }
3562             } # end of set_stop_on_warning
3563              
3564             1 ; # end of Devel::ptkdb
3565              
3566             package DB ;
3567              
3568             use vars '$VERSION', '$header' ;
3569              
3570             $VERSION = '1.1091' ;
3571             $header = "ptkdb.pm version $DB::VERSION";
3572             $DB::window->{current_file} = "" ;
3573              
3574             #
3575             # Here's the clue...
3576             # eval only seems to eval the context of
3577             # the executing script while in the DB
3578             # package. When we had updateExprs in the Devel::ptkdb
3579             # package eval would turn up an undef result.
3580             #
3581              
3582             sub updateExprs {
3583             my ($package) = @_ ;
3584             #
3585             # Update expressions
3586             #
3587             $DB::window->deleteAllExprs() ;
3588             my ($expr, @result);
3589              
3590             foreach $expr ( @{$DB::window->{'expr_list'}} ) {
3591             next if length $expr == 0 ;
3592              
3593             @result = &DB::dbeval($package, $expr->{'expr'}) ;
3594              
3595             if( @result == 1 ) {
3596             $DB::window->insertExpr([ $result[0] ], $DB::window->{'data_list'}, $result[0], $expr->{'expr'}, $expr->{'depth'}) ;
3597             }
3598             else {
3599             $DB::window->insertExpr([ \@result ], $DB::window->{'data_list'}, \@result, $expr->{'expr'}, $expr->{'depth'}) ;
3600             }
3601             }
3602              
3603             } # end of updateExprs
3604              
3605             no strict ; # turning strict off (shame shame) because we keep getting errrs for the local(*dbline)
3606              
3607             #
3608             # returns true if line is breakable
3609             #
3610             use Carp ;
3611             sub checkdbline($$) {
3612             my ($fname, $lineno) = @_ ;
3613              
3614             return 0 unless $fname; # we're getting an undef here on 'Restart...'
3615              
3616             local($^W) = 0 ; # spares us warnings under -w
3617             local(*dbline) = $main::{'_<' . $fname} ;
3618              
3619             my $flag = $dbline[$lineno] != 0 ;
3620              
3621             return $flag;
3622            
3623             } # end of checkdbline
3624              
3625             #
3626             # sets a breakpoint 'through' a magic
3627             # variable that perl is able to interpert
3628             #
3629             sub setdbline($$$) {
3630             my ($fname, $lineno, $value) = @_ ;
3631             local(*dbline) = $main::{'_<' . $fname};
3632              
3633             $dbline{$lineno} = $value ;
3634             } # end of setdbline
3635              
3636             sub getdbline($$) {
3637             my ($fname, $lineno) = @_ ;
3638             local(*dbline) = $main::{'_<' . $fname};
3639             return $dbline{$lineno} ;
3640             } # end of getdbline
3641              
3642             sub getdbtextline {
3643             my ($fname, $lineno) = @_ ;
3644             local(*dbline) = $main::{'_<' . $fname};
3645             return $dbline[$lineno] ;
3646             } # end of getdbline
3647              
3648              
3649             sub cleardbline($$;&) {
3650             my ($fname, $lineno, $clearsub) = @_ ;
3651             local(*dbline) = $main::{'_<' . $fname};
3652             my $value ; # just in case we want it for something
3653              
3654             $value = $dbline{$lineno} ;
3655             delete $dbline{$lineno} ;
3656             &$clearsub($value) if $value && $clearsub ;
3657              
3658             return $value ;
3659             } # end of cleardbline
3660              
3661             sub clearalldblines(;&) {
3662             my ($clearsub) = @_ ;
3663             my ($key, $value, $brkPt, $dbkey) ;
3664             local(*dbline) ;
3665              
3666             while ( ($key, $value) = each %main:: ) { # key loop
3667             next unless $key =~ /^_
3668             *dbline = $value ;
3669              
3670             foreach $dbkey (keys %dbline) {
3671             $brkPt = $dbline{$dbkey} ;
3672             delete $dbline{$dbkey} ;
3673             next unless $brkPt && $clearSub ;
3674             &$clearsub($brkPt) ; # if specificed, call the sub routine to clear the breakpoint
3675             }
3676              
3677             } # end of key loop
3678              
3679             } # end of clearalldblines
3680              
3681             sub getdblineindexes {
3682             my ($fname) = @_ ;
3683             local(*dbline) = $main::{'_<' . $fname} ;
3684             return keys %dbline ;
3685             } # end of getdblineindexes
3686              
3687             sub getbreakpoints {
3688             my (@fnames) = @_ ;
3689             my ($fname, @retList) ;
3690              
3691             foreach $fname (@fnames) {
3692             next unless $main::{'_<' . $fname} ;
3693             local(*dbline) = $main::{'_<' . $fname} ;
3694             push @retList, values %dbline ;
3695             }
3696             return @retList ;
3697             } # end of getbreakpoints
3698              
3699             #
3700             # Construct a hash of the files
3701             # that have breakpoints to save
3702             #
3703             sub breakpoints_to_save {
3704             my ($file, @breaks, $brkPt, $svBrkPt, $list) ;
3705             my ($brkList) ;
3706              
3707             $brkList = {} ;
3708              
3709             foreach $file ( keys %main:: ) { # file loop
3710             next unless $file =~ /^_
3711             local(*dbline) = $main::{$file} ;
3712              
3713             next unless @breaks = values %dbline ;
3714             $list = [] ;
3715             foreach $brkPt ( @breaks ) {
3716            
3717             $svBrkPt = { %$brkPt } ; # make a copy of it's data
3718            
3719             push @$list, $svBrkPt ;
3720              
3721             } # end of breakpoint loop
3722              
3723             $brkList->{$file} = $list ;
3724              
3725             } # end of file loop
3726              
3727             return $brkList ;
3728              
3729             } # end of breakpoints_to_save
3730              
3731             #
3732             # When we restore breakpoints from a state file
3733             # they've often 'moved' because the file
3734             # has been editted.
3735             #
3736             # We search for the line starting with the original line number,
3737             # then we walk it back 20 lines, then with line right after the
3738             # orginal line number and walk forward 20 lines.
3739             #
3740             # NOTE: dbline is expected to be 'local'
3741             # when called
3742             #
3743             sub fix_breakpoints {
3744             my(@brkPts) = @_ ;
3745             my($startLine, $endLine, $nLines, $brkPt) ;
3746             my (@retList) ;
3747             local($^W) = 0 ;
3748              
3749             $nLines = scalar @dbline ;
3750              
3751             foreach $brkPt (@brkPts) {
3752              
3753             $startLine = $brkPt->{'line'} > 20 ? $brkPt->{'line'} - 20 : 0 ;
3754             $endLine = $brkPt->{'line'} < $nLines - 20 ? $brkPt->{'line'} + 20 : $nLines ;
3755              
3756             for( (reverse $startLine..$brkPt->{'line'}), $brkPt->{'line'} + 1 .. $endLine ) {
3757             next unless $brkPt->{'text'} eq $dbline[$_] ;
3758             $brkPt->{'line'} = $_ ;
3759             push @retList, $brkPt ;
3760             last ;
3761             }
3762             } # end of breakpoint list
3763            
3764             return @retList ;
3765              
3766             } # end of fix_breakpoints
3767              
3768             #
3769             # Restore breakpoints saved above
3770             #
3771             sub restore_breakpoints_from_save {
3772             my ($brkList) = @_ ;
3773             my ($offset, $key, $list, $brkPt, @newList) ;
3774            
3775             while ( ($key, $list) = each %$brkList ) { # reinsert loop
3776             next unless exists $main::{$key} ;
3777             local(*dbline) = $main::{$key} ;
3778              
3779             $offset = 0 ;
3780             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?ptkdb/ ;
3781              
3782             @newList = fix_breakpoints(@$list) ;
3783            
3784             foreach $brkPt ( @newList ) {
3785             if( !&DB::checkdbline($key, $brkPt->{'line'} + $offset) ) {
3786             print "Breakpoint $key:$brkPt->{'line'} in config file is not breakable.\n" ;
3787             next ;
3788             }
3789             $dbline{$brkPt->{'line'}} = { %$brkPt } ; # make a fresh copy
3790             }
3791             } # end of reinsert loop
3792            
3793             } # end of restore_breakpoints_from_save ;
3794              
3795             use strict ;
3796              
3797             sub dbint_handler {
3798             my($sigName) = @_ ;
3799             $DB::single = 1 ;
3800             print "signalled\n" ;
3801             } # end of dbint_handler
3802              
3803             #
3804             # Do first time initialization at the startup
3805             # of DB::DB
3806             #
3807             sub Initialize {
3808             my ($fName) = @_ ;
3809             return if $DB::ptkdb::isInitialized ;
3810             $DB::ptkdb::isInitialized = 1 ;
3811              
3812             $DB::window = new Devel::ptkdb ;
3813              
3814             $DB::window->do_user_init_files() ;
3815              
3816              
3817             $DB::dbint_handler_save = $SIG{'INT'} unless $DB::sigint_disable ; # saves the old handler
3818             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ;
3819            
3820             # Save the file name we started up with
3821             $DB::startupFname = $fName ;
3822              
3823             # Check for a 'restart' file
3824              
3825             if( $ENV{'PTKDB_RESTART_STATE_FILE'} && $Devel::ptkdb::DataDumperAvailable && -e $ENV{'PTKDB_RESTART_STATE_FILE'} ) {
3826             ##
3827             ## Restore expressions and breakpoints in state file
3828             ##
3829             $DB::window->restoreStateFile($ENV{'PTKDB_RESTART_STATE_FILE'}) ;
3830             unlink $ENV{'PTKDB_RESTART_STATE_FILE'} ; # delete state file
3831              
3832             # print "restoring state from $ENV{'PTKDB_RESTART_STATE_FILE'}\n" ;
3833              
3834             $ENV{'PTKDB_RESTART_STATE_FILE'} = "" ; # clear entry
3835             }
3836             else {
3837             &DB::restoreState($fName) if $Devel::ptkdb::DataDumperAvailable ;
3838             }
3839              
3840             } # end of Initialize
3841              
3842             sub restoreState {
3843             my($fName) = @_ ;
3844             my ($stateFile, $files, $expr_list, $eval_saved_text, $main_win_geometry, $restoreName) ;
3845              
3846             $stateFile = makeFileSaveName($fName) ;
3847            
3848             if( -e $stateFile && -r $stateFile ) {
3849             ($files, $expr_list, $eval_saved_text, $main_win_geometry) = $DB::window->get_state($stateFile) ;
3850             &DB::restore_breakpoints_from_save($files) ;
3851             $DB::window->{'expr_list'} = $expr_list if defined $expr_list ;
3852             $DB::window->{eval_saved_text} = $eval_saved_text ;
3853            
3854             if ( $main_win_geometry ) {
3855             # restore the height and width of the window
3856             $DB::window->{main_window}->geometry($main_win_geometry) ;
3857             }
3858             }
3859              
3860             } # end of Restore State
3861              
3862             sub makeFileSaveName {
3863             my ($fName) = @_ ;
3864             my $saveName = $fName ;
3865              
3866             if( $saveName =~ /.p[lm]$/ ) {
3867             $saveName =~ s/.pl$/.ptkdb/ ;
3868             }
3869             else {
3870             $saveName .= ".ptkdb" ;
3871             }
3872              
3873             return $saveName ;
3874             } # end of makeFileSaveName
3875              
3876             sub save_state_file {
3877             my($fname) = @_ ;
3878             my($files, $d, $saveStr) ;
3879            
3880             $files = &DB::breakpoints_to_save() ;
3881            
3882             $d = Data::Dumper->new( [ $files, $DB::window->{'expr_list'}, "" ],
3883             [ "files", "expr_list", "eval_saved_text" ] ) ;
3884            
3885             $d->Purity(1) ;
3886             if( Data::Dumper->can('Dumpxs') ) {
3887             $saveStr = $d->Dumpxs() ;
3888             } else {
3889             $saveStr = $d->Dump() ;
3890             }
3891            
3892             local(*F) ;
3893             open F, ">$fname" || die "Couldn't open file $fname" ;
3894            
3895             print F $saveStr || die "Couldn't write file" ;
3896            
3897             close F ;
3898             } # end of save_state_file
3899              
3900             sub SaveState {
3901             my($name_in) = @_ ;
3902             my ($top, $entry, $okayBtn, $win) ;
3903             my ($fname, $saveSub, $cancelSub, $saveName, $eval_saved_text, $d) ;
3904             my ($files, $main_win_geometry);
3905             #
3906             # Create our default name
3907             #
3908             $win = $DB::window ;
3909            
3910             #
3911             # Extract the height and width of our window
3912             #
3913             $main_win_geometry = $win->{main_window}->geometry ;
3914              
3915             if ( defined $win->{save_box} ) {
3916             $win->{save_box}->raise ;
3917             $win->{save_box}->focus ;
3918             return ;
3919             }
3920              
3921             $saveName = $name_in || makeFileSaveName($DB::startupFname) ;
3922            
3923            
3924              
3925             $saveSub = sub {
3926             $win->{'event'} = 'null' ;
3927              
3928             my $saveStr ;
3929              
3930             delete $win->{save_box} ;
3931              
3932             if( exists $win->{eval_window} ) {
3933             $eval_saved_text = $win->{eval_text}->get('0.0', 'end') ;
3934             }
3935             else {
3936             $eval_saved_text = $win->{eval_saved_text} ;
3937             }
3938            
3939             $files = &DB::breakpoints_to_save() ;
3940              
3941             $d = Data::Dumper->new( [ $files, $win->{'expr_list'}, $eval_saved_text, $main_win_geometry ],
3942             [ "files", "expr_list", "eval_saved_text", "main_win_geometry"] ) ;
3943            
3944             $d->Purity(1) ;
3945             if( Data::Dumper->can('Dumpxs') ) {
3946             $saveStr = $d->Dumpxs() ;
3947             } else {
3948             $saveStr = $d->Dump() ;
3949             }
3950            
3951             local(*F) ;
3952             eval {
3953             open F, ">$saveName" || die "Couldn't open file $saveName" ;
3954            
3955             print F $saveStr || die "Couldn't write file" ;
3956            
3957             close F ;
3958             } ;
3959             $win->DoAlert($@) if $@ ;
3960             } ; # end of save sub
3961              
3962             $cancelSub = sub {
3963             delete $win->{'save_box'}
3964             } ; # end of cancel sub
3965            
3966             #
3967             # Create a dialog
3968             #
3969            
3970             $win->{'save_box'} = $win->simplePromptBox("Save Config?", $saveName, $saveSub, $cancelSub) ;
3971              
3972             } # end of SaveState
3973              
3974             sub RestoreState {
3975             my ($top, $restoreSub) ;
3976              
3977             $restoreSub = sub {
3978             $DB::window->restoreStateFile($Devel::ptkdb::promptString) ;
3979             } ;
3980              
3981             $top = $DB::window->simplePromptBox("Restore Config?", makeFileSaveName($DB::startupFname), $restoreSub) ;
3982              
3983             } # end of RestoreState
3984              
3985             sub SetStepOverBreakPoint {
3986             my ($offset) = @_ ;
3987             $DB::step_over_depth = $DB::subroutine_depth + ($offset ? $offset : 0) ;
3988             } # end of SetStepOverBreakPoint
3989              
3990             #
3991             # NOTE: It may be logical and somewhat more economical
3992             # lines of codewise to set $DB::step_over_depth_saved
3993             # when we enter the subroutine, but this gets called
3994             # for EVERY callable line of code in a program that
3995             # is being debugged, so we try to save every line of
3996             # execution that we can.
3997             #
3998             sub isBreakPoint {
3999             my ($fname, $line, $package) = @_ ;
4000             my ($brkPt) ;
4001              
4002             if ( $DB::single && ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth > 0) && !$DB::on) {
4003             $DB::single = 0 ;
4004             return 0 ;
4005             }
4006             #
4007             # doing a step over/in
4008             #
4009              
4010             if( $DB::single || $DB::signal ) {
4011             $DB::single = 0 ;
4012             $DB::signal = 0 ;
4013             $DB::subroutine_depth = $DB::subroutine_depth ;
4014             return 1 ;
4015             }
4016             #
4017             # 1st Check to see if there is even a breakpoint there.
4018             # 2nd If there is a breakpoint check to see if it's check box control is 'on'
4019             # 3rd If there is any kind of expression, evaluate it and see if it's true.
4020             #
4021             $brkPt = &DB::getdbline($fname, $line) ;
4022              
4023             return 0 if( !$brkPt || !$brkPt->{'value'} || !breakPointEvalExpr($brkPt, $package) ) ;
4024              
4025             &DB::cleardbline($fname, $line) if( $brkPt->{'type'} eq 'temp' ) ;
4026              
4027             $DB::subroutine_depth = $DB::subroutine_depth ;
4028              
4029             return 1 ;
4030             } # end of isBreakPoint
4031              
4032             #
4033             # Check the breakpoint expression to see if it
4034             # is true.
4035             #
4036             sub breakPointEvalExpr {
4037             my ($brkPt, $package) = @_ ;
4038             my (@result) ;
4039              
4040             return 1 unless $brkPt->{expr} ; # return if there is no expression
4041              
4042             no strict ;
4043              
4044             @result = &DB::dbeval($package, $brkPt->{'expr'}) ;
4045              
4046             use strict ;
4047            
4048             $DB::window->DoAlert($@) if $@ ;
4049              
4050             return $result[0] or @result ; # we could have a case where the 1st element is undefined
4051             # but subsequent elements are defined
4052              
4053             } # end of breakPointEvalExpr
4054              
4055             #
4056             # Evaluate the given expression, return the result.
4057             # MUST BE CALLED from within DB::DB in order for it
4058             # to properly interpret the vars
4059             #
4060             sub dbeval {
4061             my($ptkdb__package, $ptkdb__expr) = @_ ;
4062             my(@ptkdb__result, $ptkdb__str) ;
4063             my(@ptkdb_args) ;
4064             local($^W) = 0 ; # temporarily turn off warnings
4065            
4066             no strict ;
4067             #
4068             # This substitution is done so that
4069             # we return HASH, as opposed to an ARRAY.
4070             # An expression of %hash results in a
4071             # list of key/value pairs.
4072             #
4073              
4074             $ptkdb__expr =~ s/^\s*%/\\%/o ;
4075              
4076             @_ = @DB::saved_args ; # replace @_ arg array with what we came in with
4077              
4078             @ptkdb__result = eval <<__EVAL__ ;
4079              
4080              
4081             \$\@ = \$DB::save_err ;
4082              
4083             package $ptkdb__package ;
4084              
4085             $ptkdb__expr ;
4086              
4087             __EVAL__
4088              
4089             @ptkdb__result = ("ERROR ($@)") if $@ ;
4090              
4091             use strict ;
4092              
4093             return @ptkdb__result ;
4094             } # end of dbeval
4095              
4096             #
4097             # Call back we give to our 'quit' button
4098             # and binding to the WM_DELETE_WINDOW protocol
4099             # to quit the debugger.
4100             #
4101             sub dbexit {
4102             exit ;
4103             } # end of dbexit
4104              
4105             #
4106             # This is the primary entry point for the debugger. When a perl program
4107             # is parsed with the -d(in our case -d:ptkdb) option set the parser will
4108             # insert a call to DB::DB in front of every excecutable statement.
4109             #
4110             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
4111             #
4112              
4113              
4114             ##
4115             ## Since perl 5.8.0 we need to predeclare the sub DB{} at the start of the
4116             ## package or else the compilation fails. We need to disable warnings though
4117             ## since in 5.6.x we get warnings on the sub DB begin redeclared. Using
4118             ## local($^W) = 0 will leave warnings disabled for the rest of the compile
4119             ## and we don't want that.
4120             ##
4121             my($saveW) ;
4122             sub BEGIN {
4123             $saveW = $^W ;
4124             $^W = 0 ;
4125             }
4126              
4127             no strict ;
4128             sub DB {
4129             @DB::saved_args = @_ ; # save arg context
4130             $DB::save_err = $@ ; # save value of $@
4131             my ($package, $filename, $line) = caller ;
4132             my ($stop, $cnt) ;
4133              
4134             $^W = $saveW ;
4135             unless( $DB::ptkdb::isInitialized ) {
4136             return if( $filename ne $0 ) ; # not in our target file
4137             &DB::Initialize($filename) ;
4138             }
4139              
4140             if (!isBreakPoint($filename, $line, $package) ) {
4141             $DB::single = 0 ;
4142             $@ = $DB::save_err ;
4143             return ;
4144             }
4145              
4146             if ( !$DB::window ) { # not setup yet
4147             $@ = $DB::save_err ;
4148             return ;
4149             }
4150              
4151             $DB::window->setup_main_window() unless $DB::window->{'main_window'} ;
4152              
4153             $DB::window->EnterActions() ;
4154              
4155             my ($saveP) ;
4156             $saveP = $^P ;
4157             $^P = 0 ;
4158              
4159             $DB::on = 1 ;
4160              
4161             #
4162             # The user can specify this variable in one of the startup files,
4163             # this will make the debugger run right after startup without
4164             # the user having to press the 'run' button.
4165             #
4166             if( $DB::no_stop_at_start ) {
4167             $DB::no_stop_at_start = 0 ;
4168             $DB::on = 0 ;
4169             $@ = $DB::save_err ;
4170             return ;
4171             }
4172              
4173             if( !$DB::sigint_disable ) {
4174             $SIG{'INT'} = $DB::dbint_handler_save if $DB::dbint_handler_save ; # restore original signal handler
4175             $SIG{'INT'} = "DB::dbexit" unless $DB::dbint_handler_save ;
4176             }
4177              
4178             #$DB::window->{main_window}->raise() ; # bring us to the top make sure OUR event loop runs
4179             $DB::window->{main_window}->focus() ;
4180              
4181             $DB::window->set_file($filename, $line) ;
4182             #
4183             # Refresh the exprs to see if anything has changed
4184             #
4185             updateExprs($package) ;
4186              
4187             #
4188             # Update subs Page if necessary
4189             #
4190             $cnt = scalar keys %DB::sub ;
4191             if ( $cnt != $DB::window->{'subs_list_cnt'} && $DB::window->{'subs_page_activated'} ) {
4192             $DB::window->fill_subs_page() ;
4193             $DB::window->{'subs_list_cnt'} = $cnt ;
4194             }
4195             #
4196             # Update the subroutine stack menu
4197             #
4198             $DB::window->refresh_stack_menu() ;
4199            
4200             $DB::window->{run_flag} = 1 ;
4201              
4202             my ($evt, @result, $r) ;
4203              
4204             for( ; ; ) {
4205             #
4206             # we wait here for something to do
4207             #
4208             $evt = $DB::window->main_loop() ;
4209              
4210             last if( $evt eq 'step' ) ;
4211              
4212             $DB::single = 0 if ($evt eq 'run' ) ;
4213              
4214             if ($evt eq 'balloon_eval' ) {
4215             $DB::window->code_motion_eval(&DB::dbeval($package, $DB::window->{'balloon_expr'})) ;
4216             next ;
4217             }
4218              
4219             if ( $evt eq 'qexpr' ) {
4220             my $str ;
4221             @result = &DB::dbeval($package, $DB::window->{'qexpr'}) ;
4222             $DB::window->{'quick_entry'}->delete(0, 'end') ; # clear old text
4223             if (exists $DB::window->{'quick_dumper'}) {
4224             $DB::window->{'quick_dumper'}->Reset() ;
4225             $DB::window->{'quick_dumper'}->Values( [ $#result == 0 ? @result : \@result ] ) ;
4226             if( $DB::window->{'quick_dumper'}->can('Dumpxs') ) {
4227             $str = $DB::window->{'quick_dumper'}->Dumpxs() ;
4228             }
4229             else {
4230             $str = $DB::window->{'quick_dumper'}->Dump() ;
4231             }
4232             }
4233             else {
4234             $str = "@result" ;
4235             }
4236             $DB::window->{'quick_entry'}->insert(0, $str) ; #enter the text
4237             $DB::window->{'quick_entry'}->selectionRange(0, 'end') ; # select it
4238             $evt = 'update' ; # force an update on the expressions
4239             }
4240              
4241             if( $evt eq 'expr' ) {
4242             #
4243             # Append the new expression to the list
4244             # but first check to make sure that we don't
4245             # already have it.
4246             #
4247            
4248             if ( grep $_->{'expr'} eq $DB::window->{'expr'}, @{$DB::window->{'expr_list'}} ) {
4249             $DB::window->DoAlert("$DB::window->{'expr'} is already listed") ;
4250             next ;
4251             }
4252              
4253             @result = &DB::dbeval($package, $DB::window->{expr}) ;
4254              
4255             if( @result == 1 ) {
4256             $r = $DB::window->insertExpr([ $result[0] ], $DB::window->{'data_list'}, $result[0], $DB::window->{'expr'}, $Devel::ptkdb::expr_depth) ;
4257             }
4258             else {
4259             $r = $DB::window->insertExpr([ \@result ], $DB::window->{'data_list'}, \@result, $DB::window->{'expr'}, $Devel::ptkdb::expr_depth) ;
4260             }
4261            
4262             #
4263             # $r will be 1 if the expression was added succesfully, 0 if not,
4264             # and it if wasn't added sucessfully it won't be reevalled the
4265             # next time through.
4266             #
4267             push @{$DB::window->{'expr_list'}}, { 'expr' => $DB::window->{'expr'}, 'depth' => $Devel::ptkdb::expr_depth } if $r ;
4268            
4269             next ;
4270             }
4271             if( $evt eq 'update' ) {
4272             updateExprs($package) ;
4273             next ;
4274             }
4275             if( $evt eq 'reeval' ) {
4276             #
4277             # Reevaluate the contents of the expression eval window
4278             #
4279             my $txt = $DB::window->{'eval_text'}->get('0.0', 'end') ;
4280             my @result = &DB::dbeval($package, $txt) ;
4281              
4282             $DB::window->updateEvalWindow(@result) ;
4283              
4284             next ;
4285             }
4286             last ;
4287             }
4288             $^P = $saveP ;
4289             $SIG{'INT'} = "DB::dbint_handler" unless $DB::sigint_disable ; # set our signal handler
4290              
4291             $DB::window->LeaveActions() ;
4292              
4293             $@ = $DB::save_err ;
4294             $DB::on = 0 ;
4295             } # end of DB
4296              
4297             ##
4298             ## in this case we do not use local($^W) since we would like warnings
4299             ## to be issued past this point, and the localized copy of $^W will not
4300             ## go out of scope until the end of compilation
4301             ##
4302             ##
4303              
4304             #
4305             # This is another place where we'll try and keep the
4306             # code as 'lite' as possible to prevent the debugger
4307             # from slowing down the user's application
4308             #
4309             # When a perl program is parsed with the -d(in our case a -d:ptkdb) option
4310             # the parser will route all subroutine calls through here, setting $DB::sub
4311             # to the name of the subroutine to be called, leaving it to the debugger to
4312             # make the actual subroutine call and do any pre or post processing it may
4313             # need to do. In our case we take the opportunity to track the depth of the call
4314             # stack so that we can update our 'Stack' menu when we stop.
4315             #
4316             # Refs: Progamming Perl 2nd Edition, Larry Wall, O'Reilly & Associates, Chapter 8
4317             #
4318             #
4319             sub sub {
4320             my ($result, @result) ;
4321             #
4322             # See NOTES(1)
4323             #
4324             $DB::subroutine_depth += 1 unless $DB::on ;
4325             $DB::single = 0 if ( ($DB::step_over_depth < $DB::subroutine_depth) && ($DB::step_over_depth >= 0) && !$DB::on) ;
4326              
4327             if( wantarray ) {
4328             #
4329             # array context
4330             #
4331             no strict ; # otherwise perl gripes about calling the sub by the reference
4332             @result = &$DB::sub ; # call the subroutine by name
4333             use strict ;
4334              
4335             $DB::subroutine_depth -= 1 unless $DB::on ;
4336             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on) ;
4337             return @result ;
4338             }
4339             elsif(defined wantarray) {
4340              
4341             #
4342             # scalar context
4343             #
4344             no strict ;
4345             $result = &$DB::sub ;
4346             use strict ;
4347              
4348             $DB::subroutine_depth -= 1 unless $DB::on ;
4349             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on) ;
4350             return $result ;
4351             } else {
4352             #
4353             # void context
4354             #
4355            
4356             no strict ;
4357             &$DB::sub ;
4358             use strict ;
4359              
4360             $DB::subroutine_depth -= 1 unless $DB::on ;
4361             $DB::single = 1 if ($DB::step_over_depth >= $DB::subroutine_depth && !$DB::on) ;
4362             return $result ;
4363              
4364             return ;
4365             }
4366            
4367             } # end of sub
4368              
4369             1 ; # return true value
4370              
4371             # ptkdb.pm,v
4372             # Revision 1.15 2004/03/31 02:08:40 aepage
4373             # fixes for various lacks of backwards compatiblity in Tk804
4374             # Added a 'bug report' item to the File Menu.
4375             #
4376             # Revision 1.14 2003/11/20 01:59:40 aepage
4377             # version fix
4378             #
4379             # Revision 1.12 2003/11/20 01:46:45 aepage
4380             # Hex Dumper and correction of some parameters for Tk804.025_beta6
4381             #
4382             # Revision 1.11 2003/06/26 13:42:49 aepage
4383             # fix for chars at the end of win32 platforms.
4384             #
4385             # Revision 1.10 2003/05/12 14:38:34 aepage
4386             # win32 pushback
4387             #
4388             # Revision 1.9 2003/05/12 13:46:46 aepage
4389             # optmization of win32 line fixing
4390             #
4391             # Revision 1.8 2003/05/11 23:42:20 aepage
4392             # fix to remove stray win32 chars
4393             #
4394             # Revision 1.7 2003/05/11 23:15:26 aepage
4395             # email address changes, fixes for perl 5.8.0
4396             #
4397             # Revision 1.6 2002/11/28 19:17:43 aepage
4398             # Changed many options to widgets and pack from bareword or 'bareword'
4399             # to -bareword to support Tk804.024(Devel).
4400             #
4401             # Revision 1.5 2002/11/25 23:47:03 aepage
4402             # A perl debugger package is required to define a subroutine name 'sub'.
4403             # This routine is a 'proxy' for handling subroutine calls and allows the
4404             # debugger pacakage to track subroutine depth so that it can implement
4405             # 'step over', 'step in' and 'return' functionality. It must also
4406             # handle the same context as the proxied routine; it must return a
4407             # scalar where a scalar was being expected, an array where an array is
4408             # being expected and a void where a void was being expected. Ptkdb was
4409             # not handling the case for void. 99.9% of the time this will have no
4410             # ill effects although it is being handled incorrectly. Ref Programming
4411             # Perl 3rd Edition pg 827
4412             #
4413             # Revision 1.4 2002/10/24 17:07:10 aepage
4414             # fix for warning for undefined value assigend to typeglob during restart
4415             #
4416             # Revision 1.3 2002/10/20 23:49:51 aepage
4417             #
4418             # changed email address to aepage@ptkdb.sourceforge.net
4419             #
4420             # localized $^W in dbeval
4421             #
4422             # fix for instances where there is no code in a package.
4423             #
4424             # Initialized $self->{'subs_list_cnt'} in the new constructor to 0 to
4425             # prevent warnings with -w.
4426             #