| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # -*-Perl-*- | 
| 2 | 2 |  |  | 2 |  | 92415 | use strict; | 
|  | 2 |  |  |  |  | 7 |  | 
|  | 2 |  |  |  |  | 143 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | $Tk::GraphViz::VERSION = '1.01'; | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | package Tk::GraphViz; | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 2 |  |  | 2 |  | 1097 | use Tk 800.020; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | use Tk::Font; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | # Parse::Yapp-generated Parser for parsing record node labels | 
| 12 |  |  |  |  |  |  | use Tk::GraphViz::parseRecordLabel; | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | use base qw(Tk::Derived Tk::Canvas); | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | #use warnings; | 
| 18 |  |  |  |  |  |  | use IO qw(Handle File Pipe); | 
| 19 |  |  |  |  |  |  | use Carp; | 
| 20 |  |  |  |  |  |  | use Reaper qw( reapPid pidStatus ); | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | use IPC::Open3; | 
| 23 |  |  |  |  |  |  | use POSIX qw( :sys_wait_h :errno_h ); | 
| 24 |  |  |  |  |  |  | use Fcntl; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # Initialize as a derived Tk widget | 
| 28 |  |  |  |  |  |  | Construct Tk::Widget 'GraphViz'; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | ###################################################################### | 
| 32 |  |  |  |  |  |  | # Class initializer | 
| 33 |  |  |  |  |  |  | # | 
| 34 |  |  |  |  |  |  | ###################################################################### | 
| 35 |  |  |  |  |  |  | sub ClassInit | 
| 36 |  |  |  |  |  |  | { | 
| 37 |  |  |  |  |  |  | my ($class, $mw) = @_; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | $class->SUPER::ClassInit($mw); | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | ###################################################################### | 
| 45 |  |  |  |  |  |  | # Instance initializer | 
| 46 |  |  |  |  |  |  | # | 
| 47 |  |  |  |  |  |  | ###################################################################### | 
| 48 |  |  |  |  |  |  | sub Populate | 
| 49 |  |  |  |  |  |  | { | 
| 50 |  |  |  |  |  |  | my ($self, $args) = @_; | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | $self->SUPER::Populate($args); | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | # Default resolution, for scaling | 
| 56 |  |  |  |  |  |  | $self->{dpi} = 72; | 
| 57 |  |  |  |  |  |  | $self->{margin} = .15 * $self->{dpi}; | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | # Keep track of fonts used, so they can be scaled | 
| 60 |  |  |  |  |  |  | # when the canvas is scaled | 
| 61 |  |  |  |  |  |  | $self->{fonts} = {}; | 
| 62 |  |  |  |  |  |  | } | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | ###################################################################### | 
| 66 |  |  |  |  |  |  | # Show a GraphViz graph | 
| 67 |  |  |  |  |  |  | # | 
| 68 |  |  |  |  |  |  | # Major steps: | 
| 69 |  |  |  |  |  |  | # - generate layout of the graph, which includes | 
| 70 |  |  |  |  |  |  | #   locations / color info | 
| 71 |  |  |  |  |  |  | # - clear canvas | 
| 72 |  |  |  |  |  |  | # - parse layout to add nodes, edges, subgraphs, etc | 
| 73 |  |  |  |  |  |  | # - resize to fit the graph | 
| 74 |  |  |  |  |  |  | ###################################################################### | 
| 75 |  |  |  |  |  |  | sub show | 
| 76 |  |  |  |  |  |  | { | 
| 77 |  |  |  |  |  |  | my ($self, $graph, %opt) = @_; | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | die __PACKAGE__.": Nothing to show" unless defined $graph; | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | # Layout is actually done in the background, so the graph | 
| 82 |  |  |  |  |  |  | # will get updated when the new layout is ready | 
| 83 |  |  |  |  |  |  | $self->_startGraphLayout ( $graph, fit => 1, %opt ); | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | ###################################################################### | 
| 88 |  |  |  |  |  |  | # Begin the process of creating the graph layout. | 
| 89 |  |  |  |  |  |  | # Layout is done with a separate process, and it can be time | 
| 90 |  |  |  |  |  |  | # consuming.  So allow the background task to run to completion | 
| 91 |  |  |  |  |  |  | # without blocking this process.  When the layout task is complete, | 
| 92 |  |  |  |  |  |  | # the graph display is actually updated. | 
| 93 |  |  |  |  |  |  | ###################################################################### | 
| 94 |  |  |  |  |  |  | sub _startGraphLayout | 
| 95 |  |  |  |  |  |  | { | 
| 96 |  |  |  |  |  |  | my ($self, $graph, %opt) = @_; | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | my ($filename,$delete_file) = $self->_createDotFile ( $graph, %opt ); | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | # If a previous layout process is running, it needs to be killed | 
| 101 |  |  |  |  |  |  | $self->_stopGraphLayout( %opt ); | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | $self->{layout} = []; | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | if ( ($self->{layout_process} = | 
| 106 |  |  |  |  |  |  | $self->_startDot ( $filename, delete_file => $delete_file, | 
| 107 |  |  |  |  |  |  | %opt )) ) { | 
| 108 |  |  |  |  |  |  | $self->{layout_process}{filename} = $filename; | 
| 109 |  |  |  |  |  |  | $self->{layout_process}{delete_file} = $delete_file; | 
| 110 |  |  |  |  |  |  | $self->{layout_process}{opt} = \%opt; | 
| 111 |  |  |  |  |  |  | $self->_checkGraphLayout (); | 
| 112 |  |  |  |  |  |  | } else { | 
| 113 |  |  |  |  |  |  | $self->_showGraphLayout( %opt ); | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | ###################################################################### | 
| 119 |  |  |  |  |  |  | # Stop a layout task running in the background. | 
| 120 |  |  |  |  |  |  | # It is important to do a waitpid() on all the background processes | 
| 121 |  |  |  |  |  |  | # to prevent them from becoming orphans/zombies | 
| 122 |  |  |  |  |  |  | ######################################################################{ | 
| 123 |  |  |  |  |  |  | sub _stopGraphLayout | 
| 124 |  |  |  |  |  |  | { | 
| 125 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | my $proc = $self->{layout_process}; | 
| 128 |  |  |  |  |  |  | return 0 unless defined $proc; | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | if ( defined $proc->{pid} ) { | 
| 131 |  |  |  |  |  |  | my @sig = qw( TERM TERM TERM TERM KILL ); | 
| 132 |  |  |  |  |  |  | for ( my $i = 0; $i < 5; ++$i ) { | 
| 133 |  |  |  |  |  |  | last unless defined $proc->{pid}; | 
| 134 |  |  |  |  |  |  | kill $sig[$i], $proc->{pid}; | 
| 135 |  |  |  |  |  |  | if ( $self->_checkGraphLayout( noafter => 1 ) ) { | 
| 136 |  |  |  |  |  |  | sleep $i+1; | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | unlink $proc->{filename} if ( $proc->{delete_file} ); | 
| 142 |  |  |  |  |  |  | delete $self->{layout_process}; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | ###################################################################### | 
| 147 |  |  |  |  |  |  | # Check whether the background layout task has finished | 
| 148 |  |  |  |  |  |  | # Also reads any available output the command has generated to | 
| 149 |  |  |  |  |  |  | # this point. | 
| 150 |  |  |  |  |  |  | # If the command is not finished, schedules for this method to be | 
| 151 |  |  |  |  |  |  | # called again in the future, after some period. | 
| 152 |  |  |  |  |  |  | ###################################################################### | 
| 153 |  |  |  |  |  |  | sub _checkGraphLayout | 
| 154 |  |  |  |  |  |  | { | 
| 155 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | my $proc = $self->{layout_process}; | 
| 158 |  |  |  |  |  |  | if ( !defined $proc ) { return 0; } | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | if ( !defined $proc->{pid} ) { return 0; } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | my $finished = 0; | 
| 163 |  |  |  |  |  |  | if ( defined(my $stat = pidStatus($proc->{pid})) ) { | 
| 164 |  |  |  |  |  |  | # Process has exited | 
| 165 |  |  |  |  |  |  | if ( $stat == 0xff00 ) { | 
| 166 |  |  |  |  |  |  | $proc->{error} = "exec failed"; | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  | elsif ( $stat > 0x80 ) { | 
| 169 |  |  |  |  |  |  | $stat >>= 8; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  | else { | 
| 172 |  |  |  |  |  |  | if ( $stat & 0x80 ) { | 
| 173 |  |  |  |  |  |  | $stat &= ~0x80; | 
| 174 |  |  |  |  |  |  | $proc->{error} = "Killed by signal $stat (coredump)"; | 
| 175 |  |  |  |  |  |  | } else { | 
| 176 |  |  |  |  |  |  | $proc->{error} = "Kill by signal $stat"; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  | $proc->{status} = $stat; | 
| 180 |  |  |  |  |  |  | $finished = 1; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | else { | 
| 184 |  |  |  |  |  |  | my $kill = kill ( 0 => $proc->{pid} ); | 
| 185 |  |  |  |  |  |  | if ( !$kill ) { | 
| 186 |  |  |  |  |  |  | $proc->{status} = 127; | 
| 187 |  |  |  |  |  |  | $proc->{error} = "pid $proc->{pid} gone, but no status!"; | 
| 188 |  |  |  |  |  |  | $finished = 1; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | # Read available output... | 
| 193 |  |  |  |  |  |  | while ( $self->_readGraphLayout () ) { last if !$finished; } | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | # When finished, show the new contents | 
| 196 |  |  |  |  |  |  | if ( $finished ) { | 
| 197 |  |  |  |  |  |  | $proc->{pid} = undef; | 
| 198 |  |  |  |  |  |  | $self->_stopGraphLayout(); | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | $self->_showGraphLayout ( %{$proc->{opt}} ); | 
| 201 |  |  |  |  |  |  | return 0; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | else { | 
| 205 |  |  |  |  |  |  | # Not yet finished, so schedule to check again soon | 
| 206 |  |  |  |  |  |  | if ( !defined($opt{noafter}) || !$opt{noafter} ) { | 
| 207 |  |  |  |  |  |  | my $checkDelay = 500; | 
| 208 |  |  |  |  |  |  | if ( defined($proc->{goodread}) ) { $checkDelay = 0; } | 
| 209 |  |  |  |  |  |  | $self->after ( $checkDelay, sub { $self->_checkGraphLayout(%opt); } ); | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | return 1; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | ###################################################################### | 
| 218 |  |  |  |  |  |  | # Display the new graph layout. | 
| 219 |  |  |  |  |  |  | # This is called once the layout of the graph has been completed. | 
| 220 |  |  |  |  |  |  | # The layout data itself is stored as a list layout elements, | 
| 221 |  |  |  |  |  |  | # typically read directly from the background layout task | 
| 222 |  |  |  |  |  |  | ###################################################################### | 
| 223 |  |  |  |  |  |  | sub _showGraphLayout | 
| 224 |  |  |  |  |  |  | { | 
| 225 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | # Erase old contents | 
| 228 |  |  |  |  |  |  | unless ( defined $opt{keep} && $opt{keep} ) { | 
| 229 |  |  |  |  |  |  | $self->delete ( 'all' ); | 
| 230 |  |  |  |  |  |  | delete $self->{fonts}{_default} if exists $self->{fonts}{_default}; | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | # Display new contents | 
| 234 |  |  |  |  |  |  | $self->_parseLayout ( $self->{layout}, %opt ); | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # Update scroll-region to new bounds | 
| 237 |  |  |  |  |  |  | $self->_updateScrollRegion( %opt ); | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | if ( defined $opt{fit} && $opt{fit} ) { | 
| 240 |  |  |  |  |  |  | $self->fit(); | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | 1; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | ###################################################################### | 
| 249 |  |  |  |  |  |  | # Create a (temporary) file on disk containing the graph | 
| 250 |  |  |  |  |  |  | # in canonical GraphViz/dot format. | 
| 251 |  |  |  |  |  |  | # | 
| 252 |  |  |  |  |  |  | # '$graph' can be | 
| 253 |  |  |  |  |  |  | # - a GraphViz instance | 
| 254 |  |  |  |  |  |  | # - a scalar containing graph in dot format: | 
| 255 |  |  |  |  |  |  | #   must match /^\s*(?:di)?graph / | 
| 256 |  |  |  |  |  |  | # - a IO::Handle from which to read a graph in dot format | 
| 257 |  |  |  |  |  |  | #   (contents will be read and converted to a scalar) | 
| 258 |  |  |  |  |  |  | # - a filename giving a file that contains a graph in dot format | 
| 259 |  |  |  |  |  |  | # | 
| 260 |  |  |  |  |  |  | # Returns a filename that contains the DOT description for the graph, | 
| 261 |  |  |  |  |  |  | # and an additional flag to indicate if the file is temprary | 
| 262 |  |  |  |  |  |  | ###################################################################### | 
| 263 |  |  |  |  |  |  | sub _createDotFile | 
| 264 |  |  |  |  |  |  | { | 
| 265 |  |  |  |  |  |  | my ($self, $graph, %opt) = @_; | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | my $filename = undef; | 
| 268 |  |  |  |  |  |  | my $delete_file = undef; | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | my $ref = ref($graph); | 
| 271 |  |  |  |  |  |  | if ( $ref ne '' ) { | 
| 272 |  |  |  |  |  |  | # A blessed reference | 
| 273 |  |  |  |  |  |  | if ( $ref->isa('GraphViz') || | 
| 274 |  |  |  |  |  |  | UNIVERSAL::can( $graph, 'as_canon') ) { | 
| 275 |  |  |  |  |  |  | ($filename, my $fh) = $self->_mktemp(); | 
| 276 |  |  |  |  |  |  | eval { $graph->as_canon ( $fh ); }; | 
| 277 |  |  |  |  |  |  | if ( $@ ) { | 
| 278 |  |  |  |  |  |  | die __PACKAGE__.": Error calling GraphViz::as_canon on $graph: $@"; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  | $fh->close; | 
| 281 |  |  |  |  |  |  | $delete_file = 1; | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | elsif ( $ref->isa('IO::Handle') ) { | 
| 285 |  |  |  |  |  |  | ($filename, my $fh) = $self->_mktemp(); | 
| 286 |  |  |  |  |  |  | while ( <$graph> ) { $fh->print; } | 
| 287 |  |  |  |  |  |  | $fh->close; | 
| 288 |  |  |  |  |  |  | $delete_file = 1; | 
| 289 |  |  |  |  |  |  | } | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | else { | 
| 293 |  |  |  |  |  |  | # Not a blessed reference | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | # Try it as a filename | 
| 296 |  |  |  |  |  |  | # Skip the filename test if it has newlines | 
| 297 |  |  |  |  |  |  | if ( $graph !~ /\n/m && | 
| 298 |  |  |  |  |  |  | -r $graph ) { | 
| 299 |  |  |  |  |  |  | $filename = $graph; | 
| 300 |  |  |  |  |  |  | $delete_file = 0; | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | # Try it as a scalar | 
| 304 |  |  |  |  |  |  | elsif ( $graph =~ /^\s*(?:di)?graph / ) { | 
| 305 |  |  |  |  |  |  | ($filename, my $fh) = $self->_mktemp(); | 
| 306 |  |  |  |  |  |  | $fh->print ( $graph ); | 
| 307 |  |  |  |  |  |  | $fh->close; | 
| 308 |  |  |  |  |  |  | $delete_file = 1; | 
| 309 |  |  |  |  |  |  | } | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | else { | 
| 312 |  |  |  |  |  |  | die __PACKAGE__.": Bad graph"; | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | confess unless defined($filename) && defined($delete_file); | 
| 317 |  |  |  |  |  |  | ($filename, $delete_file); | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | ###################################################################### | 
| 322 |  |  |  |  |  |  | # Create a temp file for writing, open a handle to it | 
| 323 |  |  |  |  |  |  | # | 
| 324 |  |  |  |  |  |  | ###################################################################### | 
| 325 |  |  |  |  |  |  | { | 
| 326 |  |  |  |  |  |  | my $_mktemp_count = 0; | 
| 327 |  |  |  |  |  |  | sub _mktemp | 
| 328 |  |  |  |  |  |  | { | 
| 329 |  |  |  |  |  |  | my $tempDir = $ENV{TEMP} || $ENV{TMP} || '/tmp'; | 
| 330 |  |  |  |  |  |  | my $filename = sprintf ( "%s/Tk-GraphViz.dot.$$.%d.dot", | 
| 331 |  |  |  |  |  |  | $tempDir, $_mktemp_count++ ); | 
| 332 |  |  |  |  |  |  | my $fh = new IO::File ( $filename, 'w' ) || | 
| 333 |  |  |  |  |  |  | confess "Can't write temp file: $filename: $!"; | 
| 334 |  |  |  |  |  |  | binmode($fh); | 
| 335 |  |  |  |  |  |  | ($filename, $fh); | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | ###################################################################### | 
| 341 |  |  |  |  |  |  | # Starting running 'dot' (or some other layout command) in the | 
| 342 |  |  |  |  |  |  | # background, to convert a dot file to layout output format. | 
| 343 |  |  |  |  |  |  | # | 
| 344 |  |  |  |  |  |  | ###################################################################### | 
| 345 |  |  |  |  |  |  | sub _startDot | 
| 346 |  |  |  |  |  |  | { | 
| 347 |  |  |  |  |  |  | my ($self, $filename, %opt) = @_; | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | confess "Can't read file: $filename" | 
| 350 |  |  |  |  |  |  | unless -r $filename; | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | my @layout_cmd = $self->_makeLayoutCommand ( $filename, %opt ); | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | # Simple, non-asynchronous mode: execute the | 
| 355 |  |  |  |  |  |  | # process synchnronously and wait for all its output | 
| 356 |  |  |  |  |  |  | if ( !defined($opt{async}) || !$opt{async} ) { | 
| 357 |  |  |  |  |  |  | my $pipe = new IO::Pipe; | 
| 358 |  |  |  |  |  |  | $pipe->reader ( @layout_cmd ); | 
| 359 |  |  |  |  |  |  | while ( <$pipe> ) { push @{$self->{layout}}, $_; } | 
| 360 |  |  |  |  |  |  | if ( $opt{delete_file} ) { | 
| 361 |  |  |  |  |  |  | unlink $filename; | 
| 362 |  |  |  |  |  |  | } | 
| 363 |  |  |  |  |  |  | return undef; | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | # Now execute it | 
| 367 |  |  |  |  |  |  | my $in = new IO::Handle; | 
| 368 |  |  |  |  |  |  | my $out = new IO::Handle; | 
| 369 |  |  |  |  |  |  | $in->autoflush; | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | local $@ = undef; | 
| 372 |  |  |  |  |  |  | my $proc = {}; | 
| 373 |  |  |  |  |  |  | my $ppid = $$; | 
| 374 |  |  |  |  |  |  | eval { | 
| 375 |  |  |  |  |  |  | $proc->{pid} = open3 ( $in, $out, '>&STDERR', @layout_cmd ); | 
| 376 |  |  |  |  |  |  | reapPid ( $proc->{pid} ); | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | # Fork failure? | 
| 379 |  |  |  |  |  |  | exit(127) if ( $$ != $ppid ); | 
| 380 |  |  |  |  |  |  | }; | 
| 381 |  |  |  |  |  |  | if ( defined($@) && $@ ne '' ) { | 
| 382 |  |  |  |  |  |  | $self->{error} = $@; | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | # Close stdin so child process sees eof on its input | 
| 386 |  |  |  |  |  |  | $in->close; | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | $proc->{output} = $out; | 
| 389 |  |  |  |  |  |  | $proc->{buf} = ''; | 
| 390 |  |  |  |  |  |  | $proc->{buflen} = 0; | 
| 391 |  |  |  |  |  |  | $proc->{eof} = 0; | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | # Enable non-blocking reads on the output | 
| 394 |  |  |  |  |  |  | $self->_disableBlocking ( $out ); | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | return $proc; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | ###################################################################### | 
| 401 |  |  |  |  |  |  | # $self->_disableBlocking ( $fh ) | 
| 402 |  |  |  |  |  |  | # | 
| 403 |  |  |  |  |  |  | # Turn off blocking-mode for the given handle | 
| 404 |  |  |  |  |  |  | ###################################################################### | 
| 405 |  |  |  |  |  |  | sub _disableBlocking | 
| 406 |  |  |  |  |  |  | { | 
| 407 |  |  |  |  |  |  | my ($self, $fh) = @_; | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | my $flags = 0; | 
| 410 |  |  |  |  |  |  | fcntl ( $fh, &F_GETFL, $flags ) or | 
| 411 |  |  |  |  |  |  | confess "Can't get flags for handle"; | 
| 412 |  |  |  |  |  |  | $flags = ($flags+0) | O_NONBLOCK; | 
| 413 |  |  |  |  |  |  | fcntl ( $fh, &F_SETFL, $flags ) or | 
| 414 |  |  |  |  |  |  | confess "Can't set flags for handle"; | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | 1; | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | ###################################################################### | 
| 421 |  |  |  |  |  |  | # Assemble the command for executing dot/neato/etc as a child process | 
| 422 |  |  |  |  |  |  | # to generate the layout.  The layout of the graph will be read from | 
| 423 |  |  |  |  |  |  | # the command's stdout | 
| 424 |  |  |  |  |  |  | ###################################################################### | 
| 425 |  |  |  |  |  |  | sub _makeLayoutCommand | 
| 426 |  |  |  |  |  |  | { | 
| 427 |  |  |  |  |  |  | my ($self, $filename, %opt) = @_; | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | my $layout_cmd = $opt{layout} || 'dot'; | 
| 430 |  |  |  |  |  |  | my @opts = (); | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | if ( defined $opt{graphattrs} ) { | 
| 433 |  |  |  |  |  |  | # Add -Gname=value settings to command line | 
| 434 |  |  |  |  |  |  | my $list = $opt{graphattrs}; | 
| 435 |  |  |  |  |  |  | my $ref = ref($list); | 
| 436 |  |  |  |  |  |  | die __PACKAGE__.": Expected array reference for graphattrs" | 
| 437 |  |  |  |  |  |  | unless defined $ref && $ref eq 'ARRAY'; | 
| 438 |  |  |  |  |  |  | while ( my ($key, $val) = splice @$list, 0, 2 ) { | 
| 439 |  |  |  |  |  |  | push @opts, "-G$key=\"$val\""; | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | if ( defined $opt{nodeattrs} ) { | 
| 444 |  |  |  |  |  |  | # Add -Gname=value settings to command line | 
| 445 |  |  |  |  |  |  | my $list = $opt{nodeattrs}; | 
| 446 |  |  |  |  |  |  | my $ref = ref($list); | 
| 447 |  |  |  |  |  |  | die __PACKAGE__.": Expected array reference for nodeattrs" | 
| 448 |  |  |  |  |  |  | unless defined $ref && $ref eq 'ARRAY'; | 
| 449 |  |  |  |  |  |  | while ( my ($key, $val) = splice @$list, 0, 2 ) { | 
| 450 |  |  |  |  |  |  | push @opts, "-N$key=\"$val\""; | 
| 451 |  |  |  |  |  |  | } | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | if ( defined $opt{edgeattrs} ) { | 
| 455 |  |  |  |  |  |  | # Add -Gname=value settings to command line | 
| 456 |  |  |  |  |  |  | my $list = $opt{edgeattrs}; | 
| 457 |  |  |  |  |  |  | my $ref = ref($list); | 
| 458 |  |  |  |  |  |  | die __PACKAGE__.": Expected array reference for edgeattrs" | 
| 459 |  |  |  |  |  |  | unless defined $ref && $ref eq 'ARRAY'; | 
| 460 |  |  |  |  |  |  | while ( my ($key, $val) = splice @$list, 0, 2 ) { | 
| 461 |  |  |  |  |  |  | push @opts, "-E$key=\"$val\""; | 
| 462 |  |  |  |  |  |  | } | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | return ($layout_cmd, @opts, '-Tdot', $filename); | 
| 466 |  |  |  |  |  |  | } | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | ###################################################################### | 
| 470 |  |  |  |  |  |  | # Read data from the background layout process, in a non-blocking | 
| 471 |  |  |  |  |  |  | # mode.  Reads all the data currently available, up to some reasonable | 
| 472 |  |  |  |  |  |  | # buffer size. | 
| 473 |  |  |  |  |  |  | ###################################################################### | 
| 474 |  |  |  |  |  |  | sub _readGraphLayout | 
| 475 |  |  |  |  |  |  | { | 
| 476 |  |  |  |  |  |  | my ($self) = @_; | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | my $proc = $self->{layout_process}; | 
| 479 |  |  |  |  |  |  | if ( !defined $proc ) { return; } | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | delete $proc->{goodread}; | 
| 482 |  |  |  |  |  |  | my $rv = sysread ( $proc->{output}, $proc->{buf}, 10240, | 
| 483 |  |  |  |  |  |  | $proc->{buflen} ); | 
| 484 |  |  |  |  |  |  | if ( !defined($rv) && $! == EAGAIN ) { | 
| 485 |  |  |  |  |  |  | # Would block, don't do anything right now | 
| 486 |  |  |  |  |  |  | return 0; | 
| 487 |  |  |  |  |  |  | } | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | elsif ( $rv == 0 ) { | 
| 490 |  |  |  |  |  |  | # 0 bytes read -- EOF | 
| 491 |  |  |  |  |  |  | $proc->{eof} = 1; | 
| 492 |  |  |  |  |  |  | return 0; | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | else { | 
| 496 |  |  |  |  |  |  | $proc->{buflen} += $rv; | 
| 497 |  |  |  |  |  |  | $proc->{goodread} = 1; | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | # Go ahead and split the output that's available now, | 
| 500 |  |  |  |  |  |  | # so that this part at least is potentially spread out in time | 
| 501 |  |  |  |  |  |  | # while the background process keeps running. | 
| 502 |  |  |  |  |  |  | $self->_splitGraphLayout (); | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | return $rv; | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  | ###################################################################### | 
| 510 |  |  |  |  |  |  | # Split the buffered data read from the background layout task | 
| 511 |  |  |  |  |  |  | # into individual lines | 
| 512 |  |  |  |  |  |  | ###################################################################### | 
| 513 |  |  |  |  |  |  | sub _splitGraphLayout | 
| 514 |  |  |  |  |  |  | { | 
| 515 |  |  |  |  |  |  | my ($self) = @_; | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | my $proc = $self->{layout_process}; | 
| 518 |  |  |  |  |  |  | if ( !defined $proc ) { return; } | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | my @lines = split ( /\n/, $proc->{buf} ); | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | # If not at eof, keep the last line in the buffer | 
| 523 |  |  |  |  |  |  | if ( !$proc->{eof} ) { | 
| 524 |  |  |  |  |  |  | $proc->{buf} = pop @lines; | 
| 525 |  |  |  |  |  |  | $proc->{buflen} = length($proc->{buf}); | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | push @{$self->{layout}}, @lines; | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | ###################################################################### | 
| 533 |  |  |  |  |  |  | # Parse the layout data in dot 'text' format, as returned | 
| 534 |  |  |  |  |  |  | # by _dot2layout.  Nodes / edges / etc defined in the layout | 
| 535 |  |  |  |  |  |  | # are added as object in the canvas | 
| 536 |  |  |  |  |  |  | ###################################################################### | 
| 537 |  |  |  |  |  |  | sub _parseLayout | 
| 538 |  |  |  |  |  |  | { | 
| 539 |  |  |  |  |  |  | my ($self, $layoutLines, %opt) = @_; | 
| 540 |  |  |  |  |  |  |  | 
| 541 |  |  |  |  |  |  | my $directed = 1; | 
| 542 |  |  |  |  |  |  | my %allNodeAttrs = (); | 
| 543 |  |  |  |  |  |  | my %allEdgeAttrs = (); | 
| 544 |  |  |  |  |  |  | my %graphAttrs = (); | 
| 545 |  |  |  |  |  |  | my ($minX, $minY, $maxX, $maxY) = ( undef, undef, undef, undef ); | 
| 546 |  |  |  |  |  |  | my @saveStack = (); | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | my $accum = undef; | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | foreach ( @$layoutLines ) { | 
| 551 |  |  |  |  |  |  | s/\r//g;  # get rid of any returns ( dos text files) | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | chomp; | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | # Handle line-continuation that gets put in for longer lines, | 
| 556 |  |  |  |  |  |  | # as well as lines that are continued with commas at the end | 
| 557 |  |  |  |  |  |  | if ( defined $accum ) { | 
| 558 |  |  |  |  |  |  | $_ = $accum . $_; | 
| 559 |  |  |  |  |  |  | $accum = undef; | 
| 560 |  |  |  |  |  |  | } | 
| 561 |  |  |  |  |  |  | if ( s/\\\s*$// || | 
| 562 |  |  |  |  |  |  | /\,\s*$/ ) { | 
| 563 |  |  |  |  |  |  | $accum = $_; | 
| 564 |  |  |  |  |  |  | next; | 
| 565 |  |  |  |  |  |  | } | 
| 566 |  |  |  |  |  |  |  | 
| 567 |  |  |  |  |  |  | #STDERR->print ( "gv _parse: $_\n" ); | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | if ( /^\s+node \[(.+)\];/ ) { | 
| 570 |  |  |  |  |  |  | $self->_parseAttrs ( "$1", \%allNodeAttrs ); | 
| 571 |  |  |  |  |  |  | next; | 
| 572 |  |  |  |  |  |  | } | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | if ( /^\s+edge \[(.+)\];/ ) { | 
| 575 |  |  |  |  |  |  | $self->_parseAttrs ( "$1", \%allEdgeAttrs ); | 
| 576 |  |  |  |  |  |  | next; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | if ( /^\s+graph \[(.+)\];/ ) { | 
| 580 |  |  |  |  |  |  | $self->_parseAttrs ( "$1", \%graphAttrs ); | 
| 581 |  |  |  |  |  |  | next; | 
| 582 |  |  |  |  |  |  | } | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | if ( /^\s+subgraph \S+ \{/ || | 
| 585 |  |  |  |  |  |  | /^\s+\{/ ) { | 
| 586 |  |  |  |  |  |  | push @saveStack, [ {%graphAttrs}, | 
| 587 |  |  |  |  |  |  | {%allNodeAttrs}, | 
| 588 |  |  |  |  |  |  | {%allEdgeAttrs} ]; | 
| 589 |  |  |  |  |  |  | delete $graphAttrs{label}; | 
| 590 |  |  |  |  |  |  | delete $graphAttrs{bb}; | 
| 591 |  |  |  |  |  |  | next; | 
| 592 |  |  |  |  |  |  | } | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | if ( /^\s*\}/ ) { | 
| 595 |  |  |  |  |  |  | # End of a graph section | 
| 596 |  |  |  |  |  |  | if ( @saveStack ) { | 
| 597 |  |  |  |  |  |  | # Subgraph | 
| 598 |  |  |  |  |  |  | if ( defined($graphAttrs{bb}) && $graphAttrs{bb} ne '' ) { | 
| 599 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = split ( /\s*,\s*/, $graphAttrs{bb} ); | 
| 600 |  |  |  |  |  |  | $minX = min($minX,$x1); | 
| 601 |  |  |  |  |  |  | $minY = min($minY,$y1); | 
| 602 |  |  |  |  |  |  | $maxX = max($maxX,$x2); | 
| 603 |  |  |  |  |  |  | $maxY = max($maxY,$y2); | 
| 604 |  |  |  |  |  |  | $self->_createSubgraph ( $x1, $y1, $x2, $y2, %graphAttrs ); | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | my ($g,$n,$e) = @{pop @saveStack}; | 
| 608 |  |  |  |  |  |  | %graphAttrs = %$g; | 
| 609 |  |  |  |  |  |  | %allNodeAttrs = %$n; | 
| 610 |  |  |  |  |  |  | %allEdgeAttrs = %$e; | 
| 611 |  |  |  |  |  |  | next; | 
| 612 |  |  |  |  |  |  | } else { | 
| 613 |  |  |  |  |  |  | # End of the graph | 
| 614 |  |  |  |  |  |  | # Create any whole-graph label | 
| 615 |  |  |  |  |  |  | if ( defined($graphAttrs{bb}) ) { | 
| 616 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = split ( /\s*,\s*/, $graphAttrs{bb} ); | 
| 617 |  |  |  |  |  |  | $minX = min($minX,$x1); | 
| 618 |  |  |  |  |  |  | $minY = min($minY,$y1); | 
| 619 |  |  |  |  |  |  | $maxX = max($maxX,$x2); | 
| 620 |  |  |  |  |  |  | $maxY = max($maxY,$y2); | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | # delete bb attribute so rectangle is not drawn around whole graph | 
| 623 |  |  |  |  |  |  | delete  $graphAttrs{bb}; | 
| 624 |  |  |  |  |  |  |  | 
| 625 |  |  |  |  |  |  | $self->_createSubgraph ( $x1, $y1, $x2, $y2, %graphAttrs ); | 
| 626 |  |  |  |  |  |  | } | 
| 627 |  |  |  |  |  |  | last; | 
| 628 |  |  |  |  |  |  | } | 
| 629 |  |  |  |  |  |  | } | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | if ( /\s+(.+) \-[\>\-] (.+) \[(.+)\];/ ) { | 
| 632 |  |  |  |  |  |  | # Edge | 
| 633 |  |  |  |  |  |  | my ($n1,$n2,$attrs) = ($1,$2,$3); | 
| 634 |  |  |  |  |  |  | my %edgeAttrs = %allEdgeAttrs; | 
| 635 |  |  |  |  |  |  | $self->_parseAttrs ( $attrs, \%edgeAttrs ); | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = $self->_createEdge ( $n1, $n2, %edgeAttrs ); | 
| 638 |  |  |  |  |  |  | $minX = min($minX,$x1); | 
| 639 |  |  |  |  |  |  | $minY = min($minY,$y1); | 
| 640 |  |  |  |  |  |  | $maxX = max($maxX,$x2); | 
| 641 |  |  |  |  |  |  | $maxY = max($maxY,$y2); | 
| 642 |  |  |  |  |  |  | next; | 
| 643 |  |  |  |  |  |  | } | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | if ( /\s+(.+) \[(.+)\];/ ) { | 
| 646 |  |  |  |  |  |  | # Node | 
| 647 |  |  |  |  |  |  | my ($name,$attrs) = ($1,$2); | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | # Get rid of any leading/tailing quotes | 
| 650 |  |  |  |  |  |  | $name =~ s/^\"//; | 
| 651 |  |  |  |  |  |  | $name =~ s/\"$//; | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | my %nodeAttrs = %allNodeAttrs; | 
| 654 |  |  |  |  |  |  | $self->_parseAttrs ( $attrs, \%nodeAttrs ); | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = $self->_createNode ( $name, %nodeAttrs ); | 
| 657 |  |  |  |  |  |  | $minX = min($minX,$x1); | 
| 658 |  |  |  |  |  |  | $minY = min($minY,$y1); | 
| 659 |  |  |  |  |  |  | $maxX = max($maxX,$x2); | 
| 660 |  |  |  |  |  |  | $maxY = max($maxY,$y2); | 
| 661 |  |  |  |  |  |  | next; | 
| 662 |  |  |  |  |  |  | } | 
| 663 |  |  |  |  |  |  |  | 
| 664 |  |  |  |  |  |  | } | 
| 665 |  |  |  |  |  |  |  | 
| 666 |  |  |  |  |  |  | } | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | ###################################################################### | 
| 670 |  |  |  |  |  |  | # Parse attributes of a node / edge / graph / etc, | 
| 671 |  |  |  |  |  |  | # store the values in a hash | 
| 672 |  |  |  |  |  |  | ###################################################################### | 
| 673 |  |  |  |  |  |  | sub _parseAttrs | 
| 674 |  |  |  |  |  |  | { | 
| 675 |  |  |  |  |  |  | my ($self, $attrs, $attrHash) = @_; | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | while ( $attrs =~ s/^,?\s*([^=]+)=// ) { | 
| 678 |  |  |  |  |  |  | my ($key) = ($1); | 
| 679 |  |  |  |  |  |  |  | 
| 680 |  |  |  |  |  |  | # Scan forward until end of value reached -- the first | 
| 681 |  |  |  |  |  |  | # comma not in a quoted string. | 
| 682 |  |  |  |  |  |  | # Probably a more efficient method for doing this, but... | 
| 683 |  |  |  |  |  |  | my @chars = split(//, $attrs); | 
| 684 |  |  |  |  |  |  | my $quoted = 0; | 
| 685 |  |  |  |  |  |  | my $val = ''; | 
| 686 |  |  |  |  |  |  | my $last = ''; | 
| 687 |  |  |  |  |  |  | my ($i,$n); | 
| 688 |  |  |  |  |  |  | for ( ($i,$n) = (0, scalar(@chars)); $i < $n; ++$i ) { | 
| 689 |  |  |  |  |  |  | my $ch = $chars[$i]; | 
| 690 |  |  |  |  |  |  | last if $ch eq ',' && !$quoted; | 
| 691 |  |  |  |  |  |  | if ( $ch eq '"' ) { $quoted = !$quoted unless $last eq '\\'; } | 
| 692 |  |  |  |  |  |  | $val .= $ch; | 
| 693 |  |  |  |  |  |  | $last = $ch; | 
| 694 |  |  |  |  |  |  | } | 
| 695 |  |  |  |  |  |  | $attrs = join('', splice ( @chars, $i ) ); | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | # Strip leading and trailing ws in key and value | 
| 698 |  |  |  |  |  |  | $key =~ s/^\s+|\s+$//g; | 
| 699 |  |  |  |  |  |  | $val =~ s/^\s+|\s+$//g; | 
| 700 |  |  |  |  |  |  |  | 
| 701 |  |  |  |  |  |  | if ( $val =~ /^\"(.*)\"$/ ) { $val = $1; } | 
| 702 |  |  |  |  |  |  | $val =~ s/\\\"/\"/g; # Un-escape quotes | 
| 703 |  |  |  |  |  |  | $attrHash->{$key} = $val; | 
| 704 |  |  |  |  |  |  | } | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | } | 
| 707 |  |  |  |  |  |  |  | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  | ###################################################################### | 
| 710 |  |  |  |  |  |  | # Create a subgraph / cluster | 
| 711 |  |  |  |  |  |  | # | 
| 712 |  |  |  |  |  |  | ###################################################################### | 
| 713 |  |  |  |  |  |  | sub _createSubgraph | 
| 714 |  |  |  |  |  |  | { | 
| 715 |  |  |  |  |  |  | my ($self, $x1, $y1, $x2, $y2, %attrs) = @_; | 
| 716 |  |  |  |  |  |  |  | 
| 717 |  |  |  |  |  |  | my $label = $attrs{label}; | 
| 718 |  |  |  |  |  |  | my $color = $attrs{color} || 'black'; | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | # Want box to be filled with background color by default, so that | 
| 721 |  |  |  |  |  |  | # it is 'clickable' | 
| 722 |  |  |  |  |  |  | my $fill = $self->cget('-background'); | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  | my $tags = [ subgraph => $label, %attrs ]; | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | # Get/Check a valid color | 
| 727 |  |  |  |  |  |  | $color = $self->_tryColor($color); | 
| 728 |  |  |  |  |  |  |  | 
| 729 |  |  |  |  |  |  | my @styleArgs; | 
| 730 |  |  |  |  |  |  | if( $attrs{style} ){ | 
| 731 |  |  |  |  |  |  | my $style = $attrs{style}; | 
| 732 |  |  |  |  |  |  | if ( $style =~ /dashed/i ) { | 
| 733 |  |  |  |  |  |  | @styleArgs = (-dash => '-'); | 
| 734 |  |  |  |  |  |  | } | 
| 735 |  |  |  |  |  |  | elsif ( $style =~ /dotted/ ) { | 
| 736 |  |  |  |  |  |  | @styleArgs = (-dash => '.'); | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  | elsif ( $style =~ /filled/ ) { | 
| 739 |  |  |  |  |  |  | $fill = ( $self->_tryColor($attrs{fillcolor}) || $color ); | 
| 740 |  |  |  |  |  |  | } | 
| 741 |  |  |  |  |  |  | elsif( $style =~ /bold/ ) { | 
| 742 |  |  |  |  |  |  | # Bold outline, gets wider line | 
| 743 |  |  |  |  |  |  | push @styleArgs, (-width => 2); | 
| 744 |  |  |  |  |  |  | } | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | # Create the box if coords are defined | 
| 748 |  |  |  |  |  |  | if( $attrs{bb} ) { | 
| 749 |  |  |  |  |  |  | my $id = $self->createRectangle ( $x1, -1 * $y2, $x2, -1 * $y1, | 
| 750 |  |  |  |  |  |  | -outline => $color, | 
| 751 |  |  |  |  |  |  | -fill => $fill, @styleArgs, | 
| 752 |  |  |  |  |  |  | -tags => $tags ); | 
| 753 |  |  |  |  |  |  | $self->lower($id); # make sure it doesn't obscure anything | 
| 754 |  |  |  |  |  |  | } | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | # Create the label, if defined | 
| 757 |  |  |  |  |  |  | if ( defined($attrs{label}) ) { | 
| 758 |  |  |  |  |  |  | my $lp = $attrs{lp} || ''; | 
| 759 |  |  |  |  |  |  | my ($x,$y) = split(/\s*,\s*/,$lp); | 
| 760 |  |  |  |  |  |  | if ( $lp eq '' ) { ($x,$y) = ($x1, $y2); } | 
| 761 |  |  |  |  |  |  |  | 
| 762 |  |  |  |  |  |  | $label =~ s/\\n/\n/g; | 
| 763 |  |  |  |  |  |  | $tags->[0] = 'subgraphlabel'; # Replace 'subgraph' w/ 'subgraphlabel' | 
| 764 |  |  |  |  |  |  | my @args = ( $x, -1 * $y, | 
| 765 |  |  |  |  |  |  | -text => $label, | 
| 766 |  |  |  |  |  |  | -tags => $tags ); | 
| 767 |  |  |  |  |  |  | push @args, ( -state => 'disabled' ); | 
| 768 |  |  |  |  |  |  | if ( $lp eq '' ) { push @args, ( -anchor => 'nw' ); } | 
| 769 |  |  |  |  |  |  |  | 
| 770 |  |  |  |  |  |  | $self->createText ( @args ); | 
| 771 |  |  |  |  |  |  | } | 
| 772 |  |  |  |  |  |  | } | 
| 773 |  |  |  |  |  |  |  | 
| 774 |  |  |  |  |  |  |  | 
| 775 |  |  |  |  |  |  | ###################################################################### | 
| 776 |  |  |  |  |  |  | # Create a node | 
| 777 |  |  |  |  |  |  | # | 
| 778 |  |  |  |  |  |  | ###################################################################### | 
| 779 |  |  |  |  |  |  | sub _createNode | 
| 780 |  |  |  |  |  |  | { | 
| 781 |  |  |  |  |  |  | my ($self, $name, %attrs) = @_; | 
| 782 |  |  |  |  |  |  |  | 
| 783 |  |  |  |  |  |  | my ($x,$y) = split(/,/, $attrs{pos}); | 
| 784 |  |  |  |  |  |  | my $dpi = $self->{dpi}; | 
| 785 |  |  |  |  |  |  | my $w = $attrs{width} * $dpi; #inches | 
| 786 |  |  |  |  |  |  | my $h = $attrs{height} * $dpi; #inches | 
| 787 |  |  |  |  |  |  | my $x1 = $x - $w/2.0; | 
| 788 |  |  |  |  |  |  | my $y1 = $y - $h/2.0; | 
| 789 |  |  |  |  |  |  | my $x2 = $x + $w/2.0; | 
| 790 |  |  |  |  |  |  | my $y2 = $y + $h/2.0; | 
| 791 |  |  |  |  |  |  |  | 
| 792 |  |  |  |  |  |  | my $label = $attrs{label}; | 
| 793 |  |  |  |  |  |  | $label = $attrs{label} = $name unless defined $label; | 
| 794 |  |  |  |  |  |  | if ( $label eq '\N' ) { $label = $attrs{label} = $name; } | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | #STDERR->printf ( "createNode: $name \"$label\" ($x1,$y1) ($x2,$y2)\n" ); | 
| 797 |  |  |  |  |  |  |  | 
| 798 |  |  |  |  |  |  |  | 
| 799 |  |  |  |  |  |  | # Node shape | 
| 800 |  |  |  |  |  |  | my $tags = [ node => $name, %attrs ]; | 
| 801 |  |  |  |  |  |  |  | 
| 802 |  |  |  |  |  |  | my @args = (); | 
| 803 |  |  |  |  |  |  |  | 
| 804 |  |  |  |  |  |  | my $outline = $self->_tryColor($attrs{color}) || 'black'; | 
| 805 |  |  |  |  |  |  | my $fill = $self->_tryColor($attrs{fillcolor}) || $self->cget('-background'); | 
| 806 |  |  |  |  |  |  | my $fontcolor = $self->_tryColor($attrs{fontcolor}) || 'black'; | 
| 807 |  |  |  |  |  |  | my $shape = $attrs{shape} || ''; | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | foreach my $style ( split ( /,/, $attrs{style}||'' ) ) { | 
| 810 |  |  |  |  |  |  | if ( $style eq 'filled' ) { | 
| 811 |  |  |  |  |  |  | $fill = ( $self->_tryColor($attrs{fillcolor}) || | 
| 812 |  |  |  |  |  |  | $self->_tryColor($attrs{color}) || | 
| 813 |  |  |  |  |  |  | 'lightgrey' ); | 
| 814 |  |  |  |  |  |  | } | 
| 815 |  |  |  |  |  |  | elsif ( $style eq 'invis' ) { | 
| 816 |  |  |  |  |  |  | $outline = undef; | 
| 817 |  |  |  |  |  |  | $fill = undef; | 
| 818 |  |  |  |  |  |  | } | 
| 819 |  |  |  |  |  |  | elsif ( $style eq 'dashed' ) { | 
| 820 |  |  |  |  |  |  | push @args, -dash => '--'; | 
| 821 |  |  |  |  |  |  | } | 
| 822 |  |  |  |  |  |  | elsif ( $style eq 'dotted' ) { | 
| 823 |  |  |  |  |  |  | push @args, -dash => '.'; | 
| 824 |  |  |  |  |  |  | } | 
| 825 |  |  |  |  |  |  | elsif ( $style eq 'bold' ) { | 
| 826 |  |  |  |  |  |  | push @args, -width => 2.0; | 
| 827 |  |  |  |  |  |  | } | 
| 828 |  |  |  |  |  |  | elsif ( $style =~ /setlinewidth\((\d+)\)/ ) { | 
| 829 |  |  |  |  |  |  | push @args, -width => "$1"; | 
| 830 |  |  |  |  |  |  | } | 
| 831 |  |  |  |  |  |  | } | 
| 832 |  |  |  |  |  |  |  | 
| 833 |  |  |  |  |  |  | push @args, -outline => $outline if ( defined($outline) ); | 
| 834 |  |  |  |  |  |  | push @args, -fill => $fill if ( defined($fill) ); | 
| 835 |  |  |  |  |  |  |  | 
| 836 |  |  |  |  |  |  | my $orient = $attrs{orientation} || 0.0; | 
| 837 |  |  |  |  |  |  |  | 
| 838 |  |  |  |  |  |  | # Node label | 
| 839 |  |  |  |  |  |  | $label =~ s/\\n/\n/g; | 
| 840 |  |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  | unless ( $shape eq 'record' ) { | 
| 842 |  |  |  |  |  |  | # Normal non-record node types | 
| 843 |  |  |  |  |  |  | $self->_createShapeNode ( $shape, $x1, -1*$y2, $x2, -1*$y1, | 
| 844 |  |  |  |  |  |  | $orient, @args, -tags => $tags ); | 
| 845 |  |  |  |  |  |  |  | 
| 846 |  |  |  |  |  |  | $label = undef if ( $shape eq 'point' ); | 
| 847 |  |  |  |  |  |  |  | 
| 848 |  |  |  |  |  |  | # Node label | 
| 849 |  |  |  |  |  |  | if ( defined $label ) { | 
| 850 |  |  |  |  |  |  | $tags->[0] = 'nodelabel'; # Replace 'node' w/ 'nodelabel' | 
| 851 |  |  |  |  |  |  | @args = ( ($x1 + $x2)/2, -1*($y2 + $y1)/2, -text => $label, | 
| 852 |  |  |  |  |  |  | -anchor => 'center', -justify => 'center', | 
| 853 |  |  |  |  |  |  | -tags => $tags, -fill => $fontcolor ); | 
| 854 |  |  |  |  |  |  | push @args, ( -state => 'disabled' ); | 
| 855 |  |  |  |  |  |  | $self->createText ( @args ); | 
| 856 |  |  |  |  |  |  | } | 
| 857 |  |  |  |  |  |  | } | 
| 858 |  |  |  |  |  |  | else { | 
| 859 |  |  |  |  |  |  | # Record node types | 
| 860 |  |  |  |  |  |  | $self->_createRecordNode ( $label, %attrs, tags => $tags ); | 
| 861 |  |  |  |  |  |  | } | 
| 862 |  |  |  |  |  |  |  | 
| 863 |  |  |  |  |  |  | # Return the bounding box of the node | 
| 864 |  |  |  |  |  |  | ($x1,$y1,$x2,$y2); | 
| 865 |  |  |  |  |  |  | } | 
| 866 |  |  |  |  |  |  |  | 
| 867 |  |  |  |  |  |  |  | 
| 868 |  |  |  |  |  |  | ###################################################################### | 
| 869 |  |  |  |  |  |  | # Create an item of a specific shape, generally used for creating | 
| 870 |  |  |  |  |  |  | # node shapes. | 
| 871 |  |  |  |  |  |  | ###################################################################### | 
| 872 |  |  |  |  |  |  | my %polyShapes = | 
| 873 |  |  |  |  |  |  | ( box => [ [ 0, 0 ], [ 0, 1 ], [ 1, 1 ], [ 1, 0 ] ], | 
| 874 |  |  |  |  |  |  | rect => [ [ 0, 0 ], [ 0, 1 ], [ 1, 1 ], [ 1, 0 ] ], | 
| 875 |  |  |  |  |  |  | rectangle => [ [ 0, 0 ], [ 0, 1 ], [ 1, 1 ], [ 1, 0 ] ], | 
| 876 |  |  |  |  |  |  | triangle => [ [ 0, .75 ], [ 0.5, 0 ], [ 1, .75 ] ], | 
| 877 |  |  |  |  |  |  | invtriangle => [ [ 0, .25 ], [ 0.5, 1 ], [ 1, .25 ] ], | 
| 878 |  |  |  |  |  |  | diamond => [ [ 0, 0.5 ], [ 0.5, 1.0 ], [ 1.0, 0.5 ], [ 0.5, 0.0 ] ], | 
| 879 |  |  |  |  |  |  | pentagon => [ [ .5, 0 ], [ 1, .4 ], [ .75, 1 ], [ .25, 1 ], [ 0, .4 ] ], | 
| 880 |  |  |  |  |  |  | hexagon => [ [ 0, .5 ], [ .33, 0 ], [ .66, 0 ], | 
| 881 |  |  |  |  |  |  | [ 1, .5 ], [ .66, 1 ], [ .33, 1 ] ], | 
| 882 |  |  |  |  |  |  | septagon => [ [ .5, 0 ], [ .85, .3 ], [ 1, .7 ], [ .75, 1 ], | 
| 883 |  |  |  |  |  |  | [ .25, 1 ], [ 0, .7 ], [ .15, .3 ] ], | 
| 884 |  |  |  |  |  |  | octagon => [ [ 0, .3 ], [ 0, .7 ], [ .3, 1 ], [ .7, 1 ], | 
| 885 |  |  |  |  |  |  | [ 1, .7 ], [ 1, .3 ], [ .7, 0 ], [ .3, 0 ] ], | 
| 886 |  |  |  |  |  |  | trapezium => [ [ 0, 1 ], [ .21, 0 ], [ .79, 0 ], [ 1, 1 ] ], | 
| 887 |  |  |  |  |  |  | invtrapezium => [ [ 0, 0], [ .21, 1 ], [ .79, 1 ], [ 1, 0 ] ], | 
| 888 |  |  |  |  |  |  | parallelogram => [ [ 0, 1 ], [ .20, 0 ], [ 1, 0 ], [ .80, 1 ] ], | 
| 889 |  |  |  |  |  |  | house => [ [ 0, .9 ], [ 0, .5 ], [ .5, 0 ], [ 1, .5 ], [ 1, .9 ] ], | 
| 890 |  |  |  |  |  |  | invhouse => [ [ 0, .1 ], [ 0, .5 ], [ .5, 1 ], [ 1, .5 ], [ 1, .1 ] ], | 
| 891 |  |  |  |  |  |  | folder => [ [ 0, 0.1 ], [ 0, 1 ], [ 1, 1 ], [ 1, 0.1 ], | 
| 892 |  |  |  |  |  |  | [0.9, 0 ], [0.7 , 0 ] , [0.6, 0.1 ] ], | 
| 893 |  |  |  |  |  |  | component => [ [ 0, 0 ], [ 0, 0.1 ], [ 0.03, 0.1 ], [ -0.03, 0.1 ], | 
| 894 |  |  |  |  |  |  | [ -0.03, 0.3 ], [ 0.03 , 0.3 ], [ 0.03, 0.1 ], | 
| 895 |  |  |  |  |  |  | [ 0.03 , 0.3 ], [ 0 , 0.3 ], [ 0, 0.7 ], [ 0.03, 0.7 ], | 
| 896 |  |  |  |  |  |  | [ -0.03, 0.7 ], [ -0.03, 0.9 ], [ 0.03 , 0.9 ], | 
| 897 |  |  |  |  |  |  | [ 0.03, 0.7 ], [ 0.03 , 0.9 ], [ 0 , 0.9 ], | 
| 898 |  |  |  |  |  |  | [ 0, 1 ], [ 1, 1 ], [ 1, 0 ] ], | 
| 899 |  |  |  |  |  |  | ); | 
| 900 |  |  |  |  |  |  |  | 
| 901 |  |  |  |  |  |  | sub _createShapeNode | 
| 902 |  |  |  |  |  |  | { | 
| 903 |  |  |  |  |  |  | my ($self, $shape, $x1, $y1, $x2, $y2, $orient, %args) = @_; | 
| 904 |  |  |  |  |  |  |  | 
| 905 |  |  |  |  |  |  | #STDERR->printf ( "createShape: $shape ($x1,$y1) ($x2,$y2)\n" ); | 
| 906 |  |  |  |  |  |  | my $id = undef; | 
| 907 |  |  |  |  |  |  |  | 
| 908 |  |  |  |  |  |  | my @extraArgs = (); | 
| 909 |  |  |  |  |  |  |  | 
| 910 |  |  |  |  |  |  | # Special handling for recursive calls to create periphery shapes | 
| 911 |  |  |  |  |  |  | # (for double-, triple-, etc) | 
| 912 |  |  |  |  |  |  | my $periphShape = $args{_periph}; | 
| 913 |  |  |  |  |  |  | if ( defined $periphShape ) { | 
| 914 |  |  |  |  |  |  | delete $args{_periph}; | 
| 915 |  |  |  |  |  |  |  | 
| 916 |  |  |  |  |  |  | # Periphery shapes are drawn non-filled, so they are | 
| 917 |  |  |  |  |  |  | # not clickable | 
| 918 |  |  |  |  |  |  | push @extraArgs, ( -fill => undef, -state => 'disabled' ); | 
| 919 |  |  |  |  |  |  | }; | 
| 920 |  |  |  |  |  |  |  | 
| 921 |  |  |  |  |  |  |  | 
| 922 |  |  |  |  |  |  | # Simple shapes: defined in the polyShape hash | 
| 923 |  |  |  |  |  |  | if ( exists $polyShapes{$shape} ) { | 
| 924 |  |  |  |  |  |  | $id = $self->_createPolyShape ( $polyShapes{$shape}, | 
| 925 |  |  |  |  |  |  | $x1, $y1, $x2, $y2, $orient, | 
| 926 |  |  |  |  |  |  | %args, @extraArgs ); | 
| 927 |  |  |  |  |  |  | } | 
| 928 |  |  |  |  |  |  |  | 
| 929 |  |  |  |  |  |  | # Other special-case shapes: | 
| 930 |  |  |  |  |  |  |  | 
| 931 |  |  |  |  |  |  | elsif ( $shape =~ s/^double// ) { | 
| 932 |  |  |  |  |  |  | my $diam = max(abs($x2-$x1),abs($y2-$y1)); | 
| 933 |  |  |  |  |  |  | my $inset = max(2,min(5,$diam*.1)); | 
| 934 |  |  |  |  |  |  | return $self->_createShapeNode ( $shape, $x1, $y1, $x2, $y2, $orient, | 
| 935 |  |  |  |  |  |  | %args, _periph => [ 1, $inset ] ); | 
| 936 |  |  |  |  |  |  | } | 
| 937 |  |  |  |  |  |  |  | 
| 938 |  |  |  |  |  |  | elsif ( $shape =~ s/^triple// ) { | 
| 939 |  |  |  |  |  |  | my $diam = max(abs($x2-$x1),abs($y2-$y1)); | 
| 940 |  |  |  |  |  |  | my $inset = min(5,$diam*.1); | 
| 941 |  |  |  |  |  |  | return $self->_createShapeNode ( $shape, $x1, $y1, $x2, $y2, $orient, | 
| 942 |  |  |  |  |  |  | %args, _periph => [ 2, $inset ] ); | 
| 943 |  |  |  |  |  |  | } | 
| 944 |  |  |  |  |  |  |  | 
| 945 |  |  |  |  |  |  | elsif (  $shape eq 'plaintext' ) { | 
| 946 |  |  |  |  |  |  | # Don't draw an outline for plaintext | 
| 947 |  |  |  |  |  |  | $id = 0; | 
| 948 |  |  |  |  |  |  | } | 
| 949 |  |  |  |  |  |  |  | 
| 950 |  |  |  |  |  |  | elsif ( $shape eq 'point' ) { | 
| 951 |  |  |  |  |  |  | # Draw point as a small oval | 
| 952 |  |  |  |  |  |  | $shape = 'oval'; | 
| 953 |  |  |  |  |  |  | } | 
| 954 |  |  |  |  |  |  |  | 
| 955 |  |  |  |  |  |  | elsif ( $shape eq 'ellipse' || $shape eq 'circle' ) { | 
| 956 |  |  |  |  |  |  | $shape = 'oval'; | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  |  | 
| 959 |  |  |  |  |  |  | elsif ( $shape eq 'oval' ) { | 
| 960 |  |  |  |  |  |  |  | 
| 961 |  |  |  |  |  |  | } | 
| 962 |  |  |  |  |  |  |  | 
| 963 |  |  |  |  |  |  | elsif ( $shape eq '' ) { | 
| 964 |  |  |  |  |  |  | # Default shape = ellipse | 
| 965 |  |  |  |  |  |  | $shape = 'oval'; | 
| 966 |  |  |  |  |  |  | } | 
| 967 |  |  |  |  |  |  |  | 
| 968 |  |  |  |  |  |  | else { | 
| 969 |  |  |  |  |  |  | warn __PACKAGE__.": Unsupported shape type: '$shape', using box"; | 
| 970 |  |  |  |  |  |  | } | 
| 971 |  |  |  |  |  |  |  | 
| 972 |  |  |  |  |  |  | if ( !defined $id ) { | 
| 973 |  |  |  |  |  |  | if ( $shape eq 'oval' ) { | 
| 974 |  |  |  |  |  |  | $id = $self->createOval ( $x1, $y1, $x2, $y2, %args, @extraArgs ); | 
| 975 |  |  |  |  |  |  | } else { | 
| 976 |  |  |  |  |  |  | $id = $self->createRectangle ( $x1, $y1, $x2, $y2, %args, @extraArgs ); | 
| 977 |  |  |  |  |  |  | } | 
| 978 |  |  |  |  |  |  | } | 
| 979 |  |  |  |  |  |  |  | 
| 980 |  |  |  |  |  |  | # Need to create additional periphery shapes? | 
| 981 |  |  |  |  |  |  | if ( defined $periphShape ) { | 
| 982 |  |  |  |  |  |  | # This method of stepping in a fixed ammount in x and y is not | 
| 983 |  |  |  |  |  |  | # correct, because the aspect of the overall shape changes... | 
| 984 |  |  |  |  |  |  | my $inset = $periphShape->[1]; | 
| 985 |  |  |  |  |  |  | $x1 += $inset; | 
| 986 |  |  |  |  |  |  | $y1 += $inset; | 
| 987 |  |  |  |  |  |  | $x2 -= $inset; | 
| 988 |  |  |  |  |  |  | $y2 -= $inset; | 
| 989 |  |  |  |  |  |  | if ( --$periphShape->[0] > 0 ) { | 
| 990 |  |  |  |  |  |  | @extraArgs = ( _periph => $periphShape ); | 
| 991 |  |  |  |  |  |  | } else { | 
| 992 |  |  |  |  |  |  | @extraArgs = (); | 
| 993 |  |  |  |  |  |  | } | 
| 994 |  |  |  |  |  |  | return $self->_createShapeNode ( $shape, $x1, $y1, $x2, $y2, $orient, | 
| 995 |  |  |  |  |  |  | %args, @extraArgs ); | 
| 996 |  |  |  |  |  |  | } | 
| 997 |  |  |  |  |  |  |  | 
| 998 |  |  |  |  |  |  | $id; | 
| 999 |  |  |  |  |  |  | } | 
| 1000 |  |  |  |  |  |  |  | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | ###################################################################### | 
| 1003 |  |  |  |  |  |  | # Create an arbitrary polygonal shape, using a set of unit points. | 
| 1004 |  |  |  |  |  |  | # The points will be scaled to fit the given bounding box. | 
| 1005 |  |  |  |  |  |  | ###################################################################### | 
| 1006 |  |  |  |  |  |  | sub _createPolyShape | 
| 1007 |  |  |  |  |  |  | { | 
| 1008 |  |  |  |  |  |  | my ($self, $upts, $x1, $y1, $x2, $y2, $orient, %args) = @_; | 
| 1009 |  |  |  |  |  |  |  | 
| 1010 |  |  |  |  |  |  | my ($ox, $oy) = 1.0; | 
| 1011 |  |  |  |  |  |  | if ( $orient != 0 ) { | 
| 1012 |  |  |  |  |  |  | $orient %= 360.0; | 
| 1013 |  |  |  |  |  |  |  | 
| 1014 |  |  |  |  |  |  | # Convert to radians, and rotate ccw instead of cw | 
| 1015 |  |  |  |  |  |  | $orient *= 0.017453; # pi / 180.0 | 
| 1016 |  |  |  |  |  |  | my $c = cos($orient); | 
| 1017 |  |  |  |  |  |  | my $s = sin($orient); | 
| 1018 |  |  |  |  |  |  | my $s_plus_c = $s + $c; | 
| 1019 |  |  |  |  |  |  | my @rupts = (); | 
| 1020 |  |  |  |  |  |  | foreach my $upt ( @$upts ) { | 
| 1021 |  |  |  |  |  |  | my ($ux, $uy) = @$upt; | 
| 1022 |  |  |  |  |  |  | $ux -= 0.5; | 
| 1023 |  |  |  |  |  |  | $uy -= 0.5; | 
| 1024 |  |  |  |  |  |  |  | 
| 1025 |  |  |  |  |  |  | #STDERR->printf ( "orient: rotate (%.2f,%.2f) by %g deg\n", | 
| 1026 |  |  |  |  |  |  | #		       $ux, $uy, $orient / 0.017453 ); | 
| 1027 |  |  |  |  |  |  | $ux = $ux * $c - $uy * $s; # x' = x cos(t) - y sin(t) | 
| 1028 |  |  |  |  |  |  | $uy = $uy * $s_plus_c;     # y' = y sin(t) + y cos(t) | 
| 1029 |  |  |  |  |  |  | #STDERR->printf ( "       --> (%.2f,%.2f)\n", $ux, $uy  ); | 
| 1030 |  |  |  |  |  |  |  | 
| 1031 |  |  |  |  |  |  | $ux += 0.5; | 
| 1032 |  |  |  |  |  |  | $uy += 0.5; | 
| 1033 |  |  |  |  |  |  |  | 
| 1034 |  |  |  |  |  |  | push @rupts, [ $ux, $uy ]; | 
| 1035 |  |  |  |  |  |  | } | 
| 1036 |  |  |  |  |  |  | $upts = \@rupts; | 
| 1037 |  |  |  |  |  |  | } | 
| 1038 |  |  |  |  |  |  |  | 
| 1039 |  |  |  |  |  |  | my $dx = $x2 - $x1; | 
| 1040 |  |  |  |  |  |  | my $dy = $y2 - $y1; | 
| 1041 |  |  |  |  |  |  | my @pts = (); | 
| 1042 |  |  |  |  |  |  | foreach my $upt ( @$upts ) { | 
| 1043 |  |  |  |  |  |  | my ($ux, $uy ) = @$upt; | 
| 1044 |  |  |  |  |  |  |  | 
| 1045 |  |  |  |  |  |  | push @pts, ( $x1 + $ux*$dx, $y1 + $uy*$dy ); | 
| 1046 |  |  |  |  |  |  | } | 
| 1047 |  |  |  |  |  |  | $self->createPolygon ( @pts, %args ); | 
| 1048 |  |  |  |  |  |  | } | 
| 1049 |  |  |  |  |  |  |  | 
| 1050 |  |  |  |  |  |  |  | 
| 1051 |  |  |  |  |  |  | ###################################################################### | 
| 1052 |  |  |  |  |  |  | # Draw the node record shapes | 
| 1053 |  |  |  |  |  |  | ###################################################################### | 
| 1054 |  |  |  |  |  |  | sub _createRecordNode | 
| 1055 |  |  |  |  |  |  | { | 
| 1056 |  |  |  |  |  |  | my ($self, $label, %attrs) = @_; | 
| 1057 |  |  |  |  |  |  |  | 
| 1058 |  |  |  |  |  |  | my $tags = $attrs{tags}; | 
| 1059 |  |  |  |  |  |  |  | 
| 1060 |  |  |  |  |  |  | # Get Rectangle Coords | 
| 1061 |  |  |  |  |  |  | my $rects = $attrs{rects}; | 
| 1062 |  |  |  |  |  |  | my @rects = split(' ', $rects); | 
| 1063 |  |  |  |  |  |  | my @rectsCoords = map [ split(',',$_) ], @rects; | 
| 1064 |  |  |  |  |  |  |  | 
| 1065 |  |  |  |  |  |  | # Setup to parse the label (Label parser object created using Parse::Yapp) | 
| 1066 |  |  |  |  |  |  | my $parser = new Tk::GraphViz::parseRecordLabel(); | 
| 1067 |  |  |  |  |  |  | $parser->YYData->{INPUT} = $label; | 
| 1068 |  |  |  |  |  |  |  | 
| 1069 |  |  |  |  |  |  | # And parse it... | 
| 1070 |  |  |  |  |  |  | my $structure = $parser->YYParse | 
| 1071 |  |  |  |  |  |  | ( yylex => \&Tk::GraphViz::parseRecordLabel::Lexer, | 
| 1072 |  |  |  |  |  |  | yyerror => \&Tk::GraphViz::parseRecordLabel::Error, | 
| 1073 |  |  |  |  |  |  | yydebug => 0 ); | 
| 1074 |  |  |  |  |  |  | die __PACKAGE__.": Error Parsing Record Node Label '$label'\n" | 
| 1075 |  |  |  |  |  |  | unless $structure; | 
| 1076 |  |  |  |  |  |  |  | 
| 1077 |  |  |  |  |  |  | my @labels = @$structure; | 
| 1078 |  |  |  |  |  |  |  | 
| 1079 |  |  |  |  |  |  | # Draw the rectangles | 
| 1080 |  |  |  |  |  |  | my $portIndex = 1;  # Ports numbered from 1. This is used for the port name | 
| 1081 |  |  |  |  |  |  | # in the tags, if no port name is defined in the dot file | 
| 1082 |  |  |  |  |  |  | foreach my $rectCoords ( @rectsCoords ) { | 
| 1083 |  |  |  |  |  |  | my ($port, $text) = %{shift @labels}; | 
| 1084 |  |  |  |  |  |  |  | 
| 1085 |  |  |  |  |  |  | # use port index for name, if one not defined | 
| 1086 |  |  |  |  |  |  | $port = $portIndex unless ( $port =~ /\S/); | 
| 1087 |  |  |  |  |  |  |  | 
| 1088 |  |  |  |  |  |  | my %portTags = (@$tags); # copy of tags | 
| 1089 |  |  |  |  |  |  | $portTags{port} = $port; | 
| 1090 |  |  |  |  |  |  |  | 
| 1091 |  |  |  |  |  |  | # get rid of leading trailing whitespace | 
| 1092 |  |  |  |  |  |  | $text =~ s/^\s+//; | 
| 1093 |  |  |  |  |  |  | $text =~ s/\s+$//; | 
| 1094 |  |  |  |  |  |  |  | 
| 1095 |  |  |  |  |  |  | $portTags{label} = $text; | 
| 1096 |  |  |  |  |  |  |  | 
| 1097 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = @$rectCoords; | 
| 1098 |  |  |  |  |  |  | $self->createRectangle ( $x1, -$y1, $x2, -$y2, -tags => [%portTags] ); | 
| 1099 |  |  |  |  |  |  |  | 
| 1100 |  |  |  |  |  |  | # Find midpoint for label anchor point | 
| 1101 |  |  |  |  |  |  | my $midX = ($x1 + $x2)/2; | 
| 1102 |  |  |  |  |  |  | my $midY = ($y1 + $y2)/2; | 
| 1103 |  |  |  |  |  |  | $portTags{nodelabel} = delete $portTags{node}; # Replace 'node' w/ 'nodelabel' | 
| 1104 |  |  |  |  |  |  | $self->createText ( $midX, -$midY, -text => $text, -tags => [%portTags]); | 
| 1105 |  |  |  |  |  |  |  | 
| 1106 |  |  |  |  |  |  | $portIndex++; | 
| 1107 |  |  |  |  |  |  | } | 
| 1108 |  |  |  |  |  |  | } | 
| 1109 |  |  |  |  |  |  |  | 
| 1110 |  |  |  |  |  |  |  | 
| 1111 |  |  |  |  |  |  | ###################################################################### | 
| 1112 |  |  |  |  |  |  | # Create a edge | 
| 1113 |  |  |  |  |  |  | # | 
| 1114 |  |  |  |  |  |  | ###################################################################### | 
| 1115 |  |  |  |  |  |  | sub _createEdge | 
| 1116 |  |  |  |  |  |  | { | 
| 1117 |  |  |  |  |  |  | my ($self, $n1, $n2, %attrs) = @_; | 
| 1118 |  |  |  |  |  |  |  | 
| 1119 |  |  |  |  |  |  | my $x1 = undef; | 
| 1120 |  |  |  |  |  |  | my $y1 = undef; | 
| 1121 |  |  |  |  |  |  | my $x2 = undef; | 
| 1122 |  |  |  |  |  |  | my $y2 = undef; | 
| 1123 |  |  |  |  |  |  |  | 
| 1124 |  |  |  |  |  |  | my $tags = [ edge => "$n1 $n2", | 
| 1125 |  |  |  |  |  |  | node1 => $n1, node2 => $n2, | 
| 1126 |  |  |  |  |  |  | %attrs ]; | 
| 1127 |  |  |  |  |  |  |  | 
| 1128 |  |  |  |  |  |  | # Parse the edge position | 
| 1129 |  |  |  |  |  |  | my $pos = $attrs{pos} || return; | 
| 1130 |  |  |  |  |  |  | my ($startEndCoords,@coords) = $self->_parseEdgePos ( $pos ); | 
| 1131 |  |  |  |  |  |  | my $arrowhead = $attrs{arrowhead}; | 
| 1132 |  |  |  |  |  |  | my $arrowtail = $attrs{arrowtail}; | 
| 1133 |  |  |  |  |  |  |  | 
| 1134 |  |  |  |  |  |  | my @args = (); | 
| 1135 |  |  |  |  |  |  |  | 
| 1136 |  |  |  |  |  |  | # Convert Biezer control points to 4 real points to smooth against | 
| 1137 |  |  |  |  |  |  | #  Canvas line smoothing doesn't use beizers, so we supply more points | 
| 1138 |  |  |  |  |  |  | #   along the manually-calculated bezier points. | 
| 1139 |  |  |  |  |  |  |  | 
| 1140 |  |  |  |  |  |  | @coords = map @$_, @coords; #flatten coords array | 
| 1141 |  |  |  |  |  |  |  | 
| 1142 |  |  |  |  |  |  | my @newCoords; | 
| 1143 |  |  |  |  |  |  | my ($startIndex, $stopIndex); | 
| 1144 |  |  |  |  |  |  | $startIndex = 0; | 
| 1145 |  |  |  |  |  |  | $stopIndex  = 7; | 
| 1146 |  |  |  |  |  |  | my $lastFlag = 0; | 
| 1147 |  |  |  |  |  |  | my @controlPoints; | 
| 1148 |  |  |  |  |  |  | while($stopIndex <= $#coords){ | 
| 1149 |  |  |  |  |  |  | @controlPoints = @coords[$startIndex..$stopIndex]; | 
| 1150 |  |  |  |  |  |  |  | 
| 1151 |  |  |  |  |  |  | # If this is the last set, set the flag, so we will get | 
| 1152 |  |  |  |  |  |  | # the last point | 
| 1153 |  |  |  |  |  |  | $lastFlag = 1 if( $stopIndex == $#coords); | 
| 1154 |  |  |  |  |  |  |  | 
| 1155 |  |  |  |  |  |  | push @newCoords, | 
| 1156 |  |  |  |  |  |  | $self->_bezierInterpolate(\@controlPoints, 0.1, $lastFlag); | 
| 1157 |  |  |  |  |  |  |  | 
| 1158 |  |  |  |  |  |  | $startIndex += 6; | 
| 1159 |  |  |  |  |  |  | $stopIndex += 6; | 
| 1160 |  |  |  |  |  |  | } | 
| 1161 |  |  |  |  |  |  |  | 
| 1162 |  |  |  |  |  |  | # Add start/end coords | 
| 1163 |  |  |  |  |  |  | if(defined($startEndCoords->{s})){ | 
| 1164 |  |  |  |  |  |  | unshift @newCoords, @{ $startEndCoords->{s} }; # put at the begining | 
| 1165 |  |  |  |  |  |  | } | 
| 1166 |  |  |  |  |  |  | if(defined($startEndCoords->{e})){ | 
| 1167 |  |  |  |  |  |  | push @newCoords, @{ $startEndCoords->{e}}; # put at the end | 
| 1168 |  |  |  |  |  |  | } | 
| 1169 |  |  |  |  |  |  |  | 
| 1170 |  |  |  |  |  |  | # Convert Sign of y-values of coords, record min/max | 
| 1171 |  |  |  |  |  |  | for( my $i = 0; $i < @newCoords; $i+= 2){ | 
| 1172 |  |  |  |  |  |  | my ($x,$y) = @newCoords[$i, $i+1]; | 
| 1173 |  |  |  |  |  |  | push @args, $x, -1*$y; | 
| 1174 |  |  |  |  |  |  | #printf ( "  $x,$y\n" ); | 
| 1175 |  |  |  |  |  |  | $x1 = min($x1, $x); | 
| 1176 |  |  |  |  |  |  | $y1 = min($y1, $y); | 
| 1177 |  |  |  |  |  |  | $x2 = max($x2, $x); | 
| 1178 |  |  |  |  |  |  | $y2 = max($y2, $y); | 
| 1179 |  |  |  |  |  |  | } | 
| 1180 |  |  |  |  |  |  |  | 
| 1181 |  |  |  |  |  |  | #STDERR->printf ( "createEdge: $n1->$n2 ($x1,$y1) ($x2,$y2)\n" ); | 
| 1182 |  |  |  |  |  |  | if ( defined($startEndCoords->{s}) && | 
| 1183 |  |  |  |  |  |  | defined($startEndCoords->{e}) && | 
| 1184 |  |  |  |  |  |  | (not defined $arrowhead) && | 
| 1185 |  |  |  |  |  |  | (not defined $arrowtail) ) { # two-sided arrow | 
| 1186 |  |  |  |  |  |  | push @args, -arrow => 'both'; | 
| 1187 |  |  |  |  |  |  | } | 
| 1188 |  |  |  |  |  |  | elsif ( defined($startEndCoords->{e}) && | 
| 1189 |  |  |  |  |  |  | (not defined $arrowhead) ) { # arrow just at the end | 
| 1190 |  |  |  |  |  |  | push @args, -arrow => 'last'; | 
| 1191 |  |  |  |  |  |  | } | 
| 1192 |  |  |  |  |  |  | elsif ( defined($startEndCoords->{s}) && | 
| 1193 |  |  |  |  |  |  | (not defined $arrowtail) ) { # arrow just at the start | 
| 1194 |  |  |  |  |  |  | push @args, -arrow => 'first'; | 
| 1195 |  |  |  |  |  |  | } | 
| 1196 |  |  |  |  |  |  |  | 
| 1197 |  |  |  |  |  |  | my $color = $attrs{color}; | 
| 1198 |  |  |  |  |  |  |  | 
| 1199 |  |  |  |  |  |  | foreach my $style ( split(/,/, $attrs{style}||'') ) { | 
| 1200 |  |  |  |  |  |  | if ( $style eq 'dashed' ) { | 
| 1201 |  |  |  |  |  |  | push @args, -dash => '--'; | 
| 1202 |  |  |  |  |  |  | } | 
| 1203 |  |  |  |  |  |  | elsif ( $style eq 'dotted' ) { | 
| 1204 |  |  |  |  |  |  | push @args, -dash => ','; | 
| 1205 |  |  |  |  |  |  | } | 
| 1206 |  |  |  |  |  |  | elsif ( $style =~ /setlinewidth\((\d+)\)/ ) { | 
| 1207 |  |  |  |  |  |  | push @args, -width => "$1"; | 
| 1208 |  |  |  |  |  |  | } | 
| 1209 |  |  |  |  |  |  | elsif ( $style =~ /invis/ ) { | 
| 1210 |  |  |  |  |  |  | # invisible edge, make same as background | 
| 1211 |  |  |  |  |  |  | $color = $self->cget('-background'); | 
| 1212 |  |  |  |  |  |  | } | 
| 1213 |  |  |  |  |  |  | } | 
| 1214 |  |  |  |  |  |  |  | 
| 1215 |  |  |  |  |  |  | push @args, -fill => ( $self->_tryColor($color) || 'black' ); | 
| 1216 |  |  |  |  |  |  |  | 
| 1217 |  |  |  |  |  |  | # Create the line | 
| 1218 |  |  |  |  |  |  | $self->createLine ( @args, -smooth => 1, -tags => $tags ); | 
| 1219 |  |  |  |  |  |  |  | 
| 1220 |  |  |  |  |  |  | # Create the arrowhead (at end of line) | 
| 1221 |  |  |  |  |  |  | if ( defined($arrowhead) && $arrowhead =~ /^(.*)dot$/ ) { | 
| 1222 |  |  |  |  |  |  | my $modifier = $1; | 
| 1223 |  |  |  |  |  |  |  | 
| 1224 |  |  |  |  |  |  | # easy implementation for calculating the arrow position | 
| 1225 |  |  |  |  |  |  | my ($x1, $y1) = @newCoords[(@newCoords-2), (@newCoords-1)]; | 
| 1226 |  |  |  |  |  |  | my ($x2, $y2) = @newCoords[(@newCoords-4), (@newCoords-3)]; | 
| 1227 |  |  |  |  |  |  | my $x = ($x1 + $x2)/2; | 
| 1228 |  |  |  |  |  |  | my $y = ($y1 + $y2)/2; | 
| 1229 |  |  |  |  |  |  | my @args = ($x-4, -1*($y-4), $x+4, -1*($y+4)); | 
| 1230 |  |  |  |  |  |  |  | 
| 1231 |  |  |  |  |  |  | # check for modifiers | 
| 1232 |  |  |  |  |  |  | if ($modifier eq "o") { | 
| 1233 |  |  |  |  |  |  | push @args, -fill => $self->cget('-background'); | 
| 1234 |  |  |  |  |  |  | } else { | 
| 1235 |  |  |  |  |  |  | push @args, -fill => ($self->_tryColor($color) || 'black'); | 
| 1236 |  |  |  |  |  |  | } | 
| 1237 |  |  |  |  |  |  |  | 
| 1238 |  |  |  |  |  |  | # draw | 
| 1239 |  |  |  |  |  |  | $self->createOval ( @args ); | 
| 1240 |  |  |  |  |  |  | } | 
| 1241 |  |  |  |  |  |  |  | 
| 1242 |  |  |  |  |  |  | # Create the arrowtail (at start of line) | 
| 1243 |  |  |  |  |  |  | if ( defined($arrowtail) && $arrowtail =~ /^(.*)dot$/ ) { | 
| 1244 |  |  |  |  |  |  | my $modifier = $1; | 
| 1245 |  |  |  |  |  |  |  | 
| 1246 |  |  |  |  |  |  | # easy implementation for calculating the arrow position | 
| 1247 |  |  |  |  |  |  | my ($x1, $y1) = @newCoords[0, 1]; | 
| 1248 |  |  |  |  |  |  | my ($x2, $y2) = @newCoords[2, 3]; | 
| 1249 |  |  |  |  |  |  | my $x = ($x1 + $x2)/2; | 
| 1250 |  |  |  |  |  |  | my $y = ($y1 + $y2)/2; | 
| 1251 |  |  |  |  |  |  | my @args = ($x-4, -1*($y-4), $x+4, -1*($y+4)); | 
| 1252 |  |  |  |  |  |  |  | 
| 1253 |  |  |  |  |  |  | # check for modifiers | 
| 1254 |  |  |  |  |  |  | if ($modifier eq "o") { | 
| 1255 |  |  |  |  |  |  | push @args, -fill => $self->cget('-background'); | 
| 1256 |  |  |  |  |  |  | } else { | 
| 1257 |  |  |  |  |  |  | push @args, -fill => ($self->_tryColor($color) || 'black'); | 
| 1258 |  |  |  |  |  |  | } | 
| 1259 |  |  |  |  |  |  |  | 
| 1260 |  |  |  |  |  |  | # draw | 
| 1261 |  |  |  |  |  |  | $self->createOval ( @args ); | 
| 1262 |  |  |  |  |  |  | } | 
| 1263 |  |  |  |  |  |  |  | 
| 1264 |  |  |  |  |  |  | # Create optional label | 
| 1265 |  |  |  |  |  |  | my $label = $attrs{label}; | 
| 1266 |  |  |  |  |  |  | my $lp = $attrs{lp}; | 
| 1267 |  |  |  |  |  |  | if ( defined($label) && defined($lp) ) { | 
| 1268 |  |  |  |  |  |  | $label =~ s/\\n/\n/g; | 
| 1269 |  |  |  |  |  |  | $tags->[0] = 'edgelabel'; # Replace 'edge' w/ 'edgelabel' | 
| 1270 |  |  |  |  |  |  | my ($x,$y) = split(/,/, $lp); | 
| 1271 |  |  |  |  |  |  | my @args = ( $x, -1*$y, -text => $label, -tags => $tags, | 
| 1272 |  |  |  |  |  |  | -justify => 'center' ); | 
| 1273 |  |  |  |  |  |  | push @args, ( -state => 'disabled' ); | 
| 1274 |  |  |  |  |  |  | $self->createText ( @args ); | 
| 1275 |  |  |  |  |  |  | } | 
| 1276 |  |  |  |  |  |  |  | 
| 1277 |  |  |  |  |  |  |  | 
| 1278 |  |  |  |  |  |  | # Return the bounding box of the edge | 
| 1279 |  |  |  |  |  |  | ($x1,$y1,$x2,$y2); | 
| 1280 |  |  |  |  |  |  | } | 
| 1281 |  |  |  |  |  |  |  | 
| 1282 |  |  |  |  |  |  |  | 
| 1283 |  |  |  |  |  |  | ###################################################################### | 
| 1284 |  |  |  |  |  |  | # Parse the coordinates for an edge from the 'pos' string | 
| 1285 |  |  |  |  |  |  | # | 
| 1286 |  |  |  |  |  |  | ###################################################################### | 
| 1287 |  |  |  |  |  |  | sub _parseEdgePos | 
| 1288 |  |  |  |  |  |  | { | 
| 1289 |  |  |  |  |  |  | my ($self, $pos) = @_; | 
| 1290 |  |  |  |  |  |  |  | 
| 1291 |  |  |  |  |  |  | # Note: Arrows can be at the start and end, i.e. | 
| 1292 |  |  |  |  |  |  | #    pos =  s,410,104 e,558,59 417,98 ... | 
| 1293 |  |  |  |  |  |  | #      (See example graph 'graphs/directed/ldbxtried.dot') | 
| 1294 |  |  |  |  |  |  |  | 
| 1295 |  |  |  |  |  |  | # hash of start/end coords | 
| 1296 |  |  |  |  |  |  | # Example: e => [ 12, 3 ], s = [ 1, 3 ] | 
| 1297 |  |  |  |  |  |  | my %startEnd; | 
| 1298 |  |  |  |  |  |  |  | 
| 1299 |  |  |  |  |  |  | # Process all start/end points (could be none, 1, or 2) | 
| 1300 |  |  |  |  |  |  | while ( $pos =~ s/^([se])\s*\,\s*(\d+)\s*\,\s*(\d+)\s+// ) { | 
| 1301 |  |  |  |  |  |  | my ($where, $x, $y) = ($1, $2, $3); | 
| 1302 |  |  |  |  |  |  | $startEnd{$where} = [ $x, $y ]; | 
| 1303 |  |  |  |  |  |  | } | 
| 1304 |  |  |  |  |  |  |  | 
| 1305 |  |  |  |  |  |  | my @loc = split(/ |,/, $pos); | 
| 1306 |  |  |  |  |  |  | my @coords = (); | 
| 1307 |  |  |  |  |  |  | while ( @loc >= 2 ) { | 
| 1308 |  |  |  |  |  |  | my ($x,$y) = splice(@loc,0,2); | 
| 1309 |  |  |  |  |  |  | push @coords, [$x,$y]; | 
| 1310 |  |  |  |  |  |  | } | 
| 1311 |  |  |  |  |  |  |  | 
| 1312 |  |  |  |  |  |  | (\%startEnd, @coords); | 
| 1313 |  |  |  |  |  |  | } | 
| 1314 |  |  |  |  |  |  |  | 
| 1315 |  |  |  |  |  |  |  | 
| 1316 |  |  |  |  |  |  | ###################################################################### | 
| 1317 |  |  |  |  |  |  | # Sub to make points on a curve, based on Bezier control points | 
| 1318 |  |  |  |  |  |  | #  Inputs: | 
| 1319 |  |  |  |  |  |  | #   $controlPoints: Array of control points (x/y P0,1,2,3) | 
| 1320 |  |  |  |  |  |  | #   $tinc:  Increment to use for t (t = 0 to 1 ) | 
| 1321 |  |  |  |  |  |  | #   $lastFlag: Flag = 1 to generate the last point (where t = 1) | 
| 1322 |  |  |  |  |  |  | # | 
| 1323 |  |  |  |  |  |  | #  Output; | 
| 1324 |  |  |  |  |  |  | #   @outputPoints: Array of points along the biezier curve | 
| 1325 |  |  |  |  |  |  | # | 
| 1326 |  |  |  |  |  |  | #  Equations used | 
| 1327 |  |  |  |  |  |  | #Found Bezier Equations at http://pfaedit.sourceforge.net/bezier.html | 
| 1328 |  |  |  |  |  |  | # | 
| 1329 |  |  |  |  |  |  | #	A cubic Bezier curve may be viewed as: | 
| 1330 |  |  |  |  |  |  | #	x = ax*t3 + bx*t2 + cx*t +dx | 
| 1331 |  |  |  |  |  |  | #	 y = ay*t3 + by*t2 + cy*t +dy | 
| 1332 |  |  |  |  |  |  | # | 
| 1333 |  |  |  |  |  |  | #	Where | 
| 1334 |  |  |  |  |  |  | # | 
| 1335 |  |  |  |  |  |  | #	dx = P0.x | 
| 1336 |  |  |  |  |  |  | #	dy = P0.y | 
| 1337 |  |  |  |  |  |  | #	cx = 3*P1.x-3*P0.x | 
| 1338 |  |  |  |  |  |  | #	cy = 3*P1.y-3*P0.y | 
| 1339 |  |  |  |  |  |  | #	bx = 3*P2.x-6*P1.x+3*P0.x | 
| 1340 |  |  |  |  |  |  | #	by = 3*P2.y-6*P1.y+3*P0.y | 
| 1341 |  |  |  |  |  |  | #	ax = P3.x-3*P2.x+3*P1.x-P0.x | 
| 1342 |  |  |  |  |  |  | #	ay = P3.y-3*P2.y+3*P1.y-P0.y | 
| 1343 |  |  |  |  |  |  | ###################################################################### | 
| 1344 |  |  |  |  |  |  | sub _bezierInterpolate | 
| 1345 |  |  |  |  |  |  | { | 
| 1346 |  |  |  |  |  |  | my ($self,$controlPoints, $tinc, $lastFlag) = @_; | 
| 1347 |  |  |  |  |  |  |  | 
| 1348 |  |  |  |  |  |  | # interpolation constants | 
| 1349 |  |  |  |  |  |  | my ($ax,$bx,$cx,$dx); | 
| 1350 |  |  |  |  |  |  | my ($ay,$by,$cy,$dy); | 
| 1351 |  |  |  |  |  |  |  | 
| 1352 |  |  |  |  |  |  | $dx =    $controlPoints->[0]; | 
| 1353 |  |  |  |  |  |  | $cx =  3*$controlPoints->[2] - 3*$controlPoints->[0]; | 
| 1354 |  |  |  |  |  |  | $bx =  3*$controlPoints->[4] - 6*$controlPoints->[2] + 3*$controlPoints->[0]; | 
| 1355 |  |  |  |  |  |  | $ax = (  $controlPoints->[6] - 3*$controlPoints->[4] + 3*$controlPoints->[2] | 
| 1356 |  |  |  |  |  |  | - $controlPoints->[0] ); | 
| 1357 |  |  |  |  |  |  |  | 
| 1358 |  |  |  |  |  |  | $dy =    $controlPoints->[1]; | 
| 1359 |  |  |  |  |  |  | $cy =  3*$controlPoints->[3] - 3*$controlPoints->[1]; | 
| 1360 |  |  |  |  |  |  | $by =  3*$controlPoints->[5] - 6*$controlPoints->[3] + 3*$controlPoints->[1]; | 
| 1361 |  |  |  |  |  |  | $ay = (  $controlPoints->[7] - 3*$controlPoints->[5] + 3*$controlPoints->[3] | 
| 1362 |  |  |  |  |  |  | - $controlPoints->[1] ); | 
| 1363 |  |  |  |  |  |  |  | 
| 1364 |  |  |  |  |  |  | my @outputPoints; | 
| 1365 |  |  |  |  |  |  | for( my $t=0; $t <= 1; $t+=$tinc ){ | 
| 1366 |  |  |  |  |  |  | # don't do the last point unless lastflag set | 
| 1367 |  |  |  |  |  |  | next if($t == 1 && !$lastFlag); | 
| 1368 |  |  |  |  |  |  |  | 
| 1369 |  |  |  |  |  |  | # Compute X point | 
| 1370 |  |  |  |  |  |  | push @outputPoints, ($ax*$t**3 + $bx*$t**2 + $cx*$t +$dx); | 
| 1371 |  |  |  |  |  |  |  | 
| 1372 |  |  |  |  |  |  | # Compute Y point | 
| 1373 |  |  |  |  |  |  | push @outputPoints, ($ay*$t**3 + $by*$t**2 + $cy*$t +$dy); | 
| 1374 |  |  |  |  |  |  | } | 
| 1375 |  |  |  |  |  |  |  | 
| 1376 |  |  |  |  |  |  | return @outputPoints; | 
| 1377 |  |  |  |  |  |  | } | 
| 1378 |  |  |  |  |  |  |  | 
| 1379 |  |  |  |  |  |  |  | 
| 1380 |  |  |  |  |  |  | ###################################################################### | 
| 1381 |  |  |  |  |  |  | # Update scroll region to new bounds, to encompass | 
| 1382 |  |  |  |  |  |  | # the entire contents of the canvas | 
| 1383 |  |  |  |  |  |  | ###################################################################### | 
| 1384 |  |  |  |  |  |  | sub _updateScrollRegion | 
| 1385 |  |  |  |  |  |  | { | 
| 1386 |  |  |  |  |  |  | my ($self) = @_; | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 |  |  |  |  |  |  | # Ignore passed in in bbox, get a new one | 
| 1389 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = $self->bbox('all'); | 
| 1390 |  |  |  |  |  |  | return 0 unless defined $x1; | 
| 1391 |  |  |  |  |  |  |  | 
| 1392 |  |  |  |  |  |  | # Set canvas size from graph bounding box | 
| 1393 |  |  |  |  |  |  | my $m = 0;#$self->{margin}; | 
| 1394 |  |  |  |  |  |  | $self->configure ( -scrollregion => [ $x1-$m, $y1-$m, $x2+$m, $y2+$m ], | 
| 1395 |  |  |  |  |  |  | -confine => 1 ); | 
| 1396 |  |  |  |  |  |  |  | 
| 1397 |  |  |  |  |  |  | # Reset original scale factor | 
| 1398 |  |  |  |  |  |  | $self->{_scaled} = 1.0; | 
| 1399 |  |  |  |  |  |  |  | 
| 1400 |  |  |  |  |  |  | 1; | 
| 1401 |  |  |  |  |  |  | } | 
| 1402 |  |  |  |  |  |  |  | 
| 1403 |  |  |  |  |  |  |  | 
| 1404 |  |  |  |  |  |  | ###################################################################### | 
| 1405 |  |  |  |  |  |  | # Update the scale factor | 
| 1406 |  |  |  |  |  |  | # | 
| 1407 |  |  |  |  |  |  | # Called by operations that do scaling | 
| 1408 |  |  |  |  |  |  | ###################################################################### | 
| 1409 |  |  |  |  |  |  | sub _scaleAndMoveView | 
| 1410 |  |  |  |  |  |  | { | 
| 1411 |  |  |  |  |  |  | my ($self, $scale, $x, $y) = @_; | 
| 1412 |  |  |  |  |  |  |  | 
| 1413 |  |  |  |  |  |  | $self->scale ( 'all' => 0, 0, $scale, $scale ); | 
| 1414 |  |  |  |  |  |  | my $new_scaled = $self->{_scaled} * $scale; | 
| 1415 |  |  |  |  |  |  | #STDERR->printf ( "\nscaled: %s -> %s\n", | 
| 1416 |  |  |  |  |  |  | #		       $self->{_scaled}, $new_scaled ); | 
| 1417 |  |  |  |  |  |  |  | 
| 1418 |  |  |  |  |  |  | # Scale the fonts: | 
| 1419 |  |  |  |  |  |  | my $fonts = $self->{fonts}; | 
| 1420 |  |  |  |  |  |  | #print "new_scaled = $new_scaled\n"; | 
| 1421 |  |  |  |  |  |  | foreach my $fontName ( keys %$fonts ) { | 
| 1422 |  |  |  |  |  |  | my $font = $fonts->{$fontName}{font}; | 
| 1423 |  |  |  |  |  |  | my $origSize = $fonts->{$fontName}{origSize}; | 
| 1424 |  |  |  |  |  |  |  | 
| 1425 |  |  |  |  |  |  | # Flag to indicate size is negative (i.e. specified in pixels) | 
| 1426 |  |  |  |  |  |  | my $negativeSize = $origSize < 0 ? -1 : 1; | 
| 1427 |  |  |  |  |  |  | $origSize = abs($origSize); # Make abs value for finding scale | 
| 1428 |  |  |  |  |  |  |  | 
| 1429 |  |  |  |  |  |  | # Fonts can't go below size 2, or they suddenly jump up to size 6... | 
| 1430 |  |  |  |  |  |  | my $newSize = max(2,int( $origSize*$new_scaled + 0.5)); | 
| 1431 |  |  |  |  |  |  |  | 
| 1432 |  |  |  |  |  |  | $newSize *= $negativeSize; | 
| 1433 |  |  |  |  |  |  |  | 
| 1434 |  |  |  |  |  |  | $font->configure ( -size => $newSize ); | 
| 1435 |  |  |  |  |  |  | #print "Font '$fontName' Origsize = $origSize, newsize $newSize, actual size ".$font->actual(-size)."\n"; | 
| 1436 |  |  |  |  |  |  | } | 
| 1437 |  |  |  |  |  |  |  | 
| 1438 |  |  |  |  |  |  | $self->{_scaled} = $new_scaled; | 
| 1439 |  |  |  |  |  |  |  | 
| 1440 |  |  |  |  |  |  | # Reset scroll region | 
| 1441 |  |  |  |  |  |  | my @sr = $self->cget( '-scrollregion' ); | 
| 1442 |  |  |  |  |  |  | my $sr = \@sr; | 
| 1443 |  |  |  |  |  |  | if ( @sr == 1 ) { $sr = $sr[0]; } | 
| 1444 |  |  |  |  |  |  | $_ *= $scale foreach ( @$sr ); | 
| 1445 |  |  |  |  |  |  | $self->configure ( -scrollregion => $sr ); | 
| 1446 |  |  |  |  |  |  |  | 
| 1447 |  |  |  |  |  |  | # Change the view to center on correct area | 
| 1448 |  |  |  |  |  |  | # $x and $y are expected to be coords in the pre-scaled system | 
| 1449 |  |  |  |  |  |  | my ($left, $right) = $self->xview; | 
| 1450 |  |  |  |  |  |  | my ($top, $bot) = $self->yview; | 
| 1451 |  |  |  |  |  |  | my $xpos = ($x*$scale-$sr->[0])/($sr->[2]-$sr->[0]) - ($right-$left)/2.0; | 
| 1452 |  |  |  |  |  |  | my $ypos = ($y*$scale-$sr->[1])/($sr->[3]-$sr->[1]) - ($bot-$top)/2.0; | 
| 1453 |  |  |  |  |  |  | $self->xview( moveto => $xpos ); | 
| 1454 |  |  |  |  |  |  | $self->yview( moveto => $ypos ); | 
| 1455 |  |  |  |  |  |  |  | 
| 1456 |  |  |  |  |  |  | #($left, $right) = $self->xview; | 
| 1457 |  |  |  |  |  |  | #($top, $bot) = $self->yview; | 
| 1458 |  |  |  |  |  |  | #STDERR->printf( "scaled: midx=%s midy=%s\n", | 
| 1459 |  |  |  |  |  |  | #		  ($left+$right)/2.0, ($top+$bot)/2.0 ); | 
| 1460 |  |  |  |  |  |  | 1; | 
| 1461 |  |  |  |  |  |  | } | 
| 1462 |  |  |  |  |  |  |  | 
| 1463 |  |  |  |  |  |  |  | 
| 1464 |  |  |  |  |  |  | ###################################################################### | 
| 1465 |  |  |  |  |  |  | # Setup some standard bindings. | 
| 1466 |  |  |  |  |  |  | # | 
| 1467 |  |  |  |  |  |  | # This enables some standard useful functionality for scrolling, | 
| 1468 |  |  |  |  |  |  | # zooming, etc. | 
| 1469 |  |  |  |  |  |  | # | 
| 1470 |  |  |  |  |  |  | # The bindings need to interfere as little as possible with typical | 
| 1471 |  |  |  |  |  |  | # bindings that might be employed in an application using this | 
| 1472 |  |  |  |  |  |  | # widget (e.g. Button-1). | 
| 1473 |  |  |  |  |  |  | # | 
| 1474 |  |  |  |  |  |  | # Also, creating these bindings (by calling this method) is strictly | 
| 1475 |  |  |  |  |  |  | # optional. | 
| 1476 |  |  |  |  |  |  | ###################################################################### | 
| 1477 |  |  |  |  |  |  | sub createBindings | 
| 1478 |  |  |  |  |  |  | { | 
| 1479 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 |  |  |  |  |  |  | if ( scalar(keys %opt) == 0 # Empty options list | 
| 1482 |  |  |  |  |  |  | || defined $opt{'-default'} && $opt{'-default'} ) { | 
| 1483 |  |  |  |  |  |  |  | 
| 1484 |  |  |  |  |  |  | # Default zoom bindings | 
| 1485 |  |  |  |  |  |  | $opt{'-zoom'} = 1; | 
| 1486 |  |  |  |  |  |  |  | 
| 1487 |  |  |  |  |  |  | # Default scroll bindings | 
| 1488 |  |  |  |  |  |  | $opt{'-scroll'} = 1; | 
| 1489 |  |  |  |  |  |  |  | 
| 1490 |  |  |  |  |  |  | # Key-pad bindings | 
| 1491 |  |  |  |  |  |  | $opt{'-keypad'} = 1; | 
| 1492 |  |  |  |  |  |  | } | 
| 1493 |  |  |  |  |  |  |  | 
| 1494 |  |  |  |  |  |  | if ( defined $opt{'-zoom'} ) { | 
| 1495 |  |  |  |  |  |  | $self->_createZoomBindings( %opt ); | 
| 1496 |  |  |  |  |  |  | } | 
| 1497 |  |  |  |  |  |  |  | 
| 1498 |  |  |  |  |  |  | if ( defined $opt{'-scroll'} ) { | 
| 1499 |  |  |  |  |  |  | $self->_createScrollBindings( %opt ); | 
| 1500 |  |  |  |  |  |  | } | 
| 1501 |  |  |  |  |  |  |  | 
| 1502 |  |  |  |  |  |  | if ( defined $opt{'-keypad'} ) { | 
| 1503 |  |  |  |  |  |  | $self->_createKeypadBindings( %opt ); | 
| 1504 |  |  |  |  |  |  | } | 
| 1505 |  |  |  |  |  |  |  | 
| 1506 |  |  |  |  |  |  | } | 
| 1507 |  |  |  |  |  |  |  | 
| 1508 |  |  |  |  |  |  |  | 
| 1509 |  |  |  |  |  |  | ###################################################################### | 
| 1510 |  |  |  |  |  |  | # Setup bindings for zooming operations | 
| 1511 |  |  |  |  |  |  | # | 
| 1512 |  |  |  |  |  |  | # These are bound to a specific mouse button and optional modifiers. | 
| 1513 |  |  |  |  |  |  | # - To zoom in: drag out a box from top-left/right to bottom-right/left | 
| 1514 |  |  |  |  |  |  | #   enclosing the new region to display | 
| 1515 |  |  |  |  |  |  | # - To zoom out: drag out a box from bottom-left/right to top-right/left. | 
| 1516 |  |  |  |  |  |  | #   size of the box determines zoom out factor. | 
| 1517 |  |  |  |  |  |  | ###################################################################### | 
| 1518 |  |  |  |  |  |  | sub _createZoomBindings | 
| 1519 |  |  |  |  |  |  | { | 
| 1520 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 1521 |  |  |  |  |  |  |  | 
| 1522 |  |  |  |  |  |  | # Interpret zooming options | 
| 1523 |  |  |  |  |  |  |  | 
| 1524 |  |  |  |  |  |  | # What mouse button + modifiers starts zoom? | 
| 1525 |  |  |  |  |  |  | my $zoomSpec = $opt{'-zoom'}; | 
| 1526 |  |  |  |  |  |  | die __PACKAGE__.": No -zoom option" unless defined $zoomSpec; | 
| 1527 |  |  |  |  |  |  | if ( $zoomSpec =~ /^\<.+\>$/ ) { | 
| 1528 |  |  |  |  |  |  | # This should be a partial bind event spec, e.g. <1>, or | 
| 1529 |  |  |  |  |  |  | # -- it must end in a button number | 
| 1530 |  |  |  |  |  |  | die __PACKAGE__.": Illegal -zoom option" | 
| 1531 |  |  |  |  |  |  | unless ( $zoomSpec =~ /^\<.+\-\d\>$/ || | 
| 1532 |  |  |  |  |  |  | $zoomSpec =~ /^\<\d\>$/ ); | 
| 1533 |  |  |  |  |  |  | } | 
| 1534 |  |  |  |  |  |  | else { | 
| 1535 |  |  |  |  |  |  | # Anything else: use the default | 
| 1536 |  |  |  |  |  |  | $zoomSpec = ''; | 
| 1537 |  |  |  |  |  |  | } | 
| 1538 |  |  |  |  |  |  |  | 
| 1539 |  |  |  |  |  |  | # Color for zoom rect | 
| 1540 |  |  |  |  |  |  | my $zoomColor = $opt{'-zoomcolor'} || 'red'; | 
| 1541 |  |  |  |  |  |  |  | 
| 1542 |  |  |  |  |  |  | # Initial press starts drawing zoom rect | 
| 1543 |  |  |  |  |  |  | my $startEvent = $zoomSpec; | 
| 1544 |  |  |  |  |  |  | $startEvent =~ s/(\d\>)$/ButtonPress-$1/; | 
| 1545 |  |  |  |  |  |  | #STDERR->printf ( "startEvent = $startEvent\n" ); | 
| 1546 |  |  |  |  |  |  | $self->Tk::bind ( $startEvent => sub { $self->_startZoom ( $zoomSpec, | 
| 1547 |  |  |  |  |  |  | $zoomColor ) }); | 
| 1548 |  |  |  |  |  |  | } | 
| 1549 |  |  |  |  |  |  |  | 
| 1550 |  |  |  |  |  |  |  | 
| 1551 |  |  |  |  |  |  | ###################################################################### | 
| 1552 |  |  |  |  |  |  | # Called whenever a zoom event is started.  This creates the initial | 
| 1553 |  |  |  |  |  |  | # zoom rectangle, and installs (temporary) bindings for mouse motion | 
| 1554 |  |  |  |  |  |  | # and release to drag out the zoom rect and then compute the zoom | 
| 1555 |  |  |  |  |  |  | # operation. | 
| 1556 |  |  |  |  |  |  | # | 
| 1557 |  |  |  |  |  |  | # The motion / button release bindings have to be installed temporarily | 
| 1558 |  |  |  |  |  |  | # so they don't conflict with other bindings (such as for scrolling | 
| 1559 |  |  |  |  |  |  | # or panning).  The original bindings for those events have to be | 
| 1560 |  |  |  |  |  |  | # restored once the zoom operation is completed. | 
| 1561 |  |  |  |  |  |  | ###################################################################### | 
| 1562 |  |  |  |  |  |  | sub _startZoom | 
| 1563 |  |  |  |  |  |  | { | 
| 1564 |  |  |  |  |  |  | my ($self, $zoomSpec, $zoomColor) = @_; | 
| 1565 |  |  |  |  |  |  |  | 
| 1566 |  |  |  |  |  |  | # Start of the zoom rectangle | 
| 1567 |  |  |  |  |  |  | my $x = $self->canvasx ( $Tk::event->x ); | 
| 1568 |  |  |  |  |  |  | my $y = $self->canvasy ( $Tk::event->y ); | 
| 1569 |  |  |  |  |  |  | my @zoomCoords = ( $x, $y, $x, $y ); | 
| 1570 |  |  |  |  |  |  | my $zoomRect = $self->createRectangle | 
| 1571 |  |  |  |  |  |  | ( @zoomCoords, -outline => $zoomColor ); | 
| 1572 |  |  |  |  |  |  |  | 
| 1573 |  |  |  |  |  |  | # Install the Motion binding to drag out the rectangle -- store the | 
| 1574 |  |  |  |  |  |  | # origin binding. | 
| 1575 |  |  |  |  |  |  | my $dragEvent = ''; | 
| 1576 |  |  |  |  |  |  | #STDERR->printf ( "dragEvent = $dragEvent\n" ); | 
| 1577 |  |  |  |  |  |  | my $origDragBind = $self->Tk::bind ( $dragEvent ); | 
| 1578 |  |  |  |  |  |  | $self->Tk::bind ( $dragEvent => sub { | 
| 1579 |  |  |  |  |  |  | $zoomCoords[2] = $self->canvasx ( $Tk::event->x ); | 
| 1580 |  |  |  |  |  |  | $zoomCoords[3] = $self->canvasy ( $Tk::event->y ); | 
| 1581 |  |  |  |  |  |  | $self->coords ( $zoomRect => @zoomCoords ); | 
| 1582 |  |  |  |  |  |  | } ); | 
| 1583 |  |  |  |  |  |  |  | 
| 1584 |  |  |  |  |  |  | # Releasing button finishes zoom rect, and causes zoom to happen. | 
| 1585 |  |  |  |  |  |  | my $stopEvent = $zoomSpec; | 
| 1586 |  |  |  |  |  |  | $stopEvent =~ s/^\<.*(\d\>)$/ | 
| 1587 |  |  |  |  |  |  | #STDERR->printf ( "stopEvent = $stopEvent\n" ); | 
| 1588 |  |  |  |  |  |  | my $threshold = 10; | 
| 1589 |  |  |  |  |  |  | my $origStopBind = $self->Tk::bind ( $stopEvent ); | 
| 1590 |  |  |  |  |  |  | $self->Tk::bind ( $stopEvent => sub { | 
| 1591 |  |  |  |  |  |  | # Delete the rect | 
| 1592 |  |  |  |  |  |  | $self->delete ( $zoomRect ); | 
| 1593 |  |  |  |  |  |  |  | 
| 1594 |  |  |  |  |  |  | # Restore original bindings | 
| 1595 |  |  |  |  |  |  | $self->Tk::bind ( $dragEvent => $origDragBind ); | 
| 1596 |  |  |  |  |  |  | $self->Tk::bind ( $stopEvent => $origStopBind ); | 
| 1597 |  |  |  |  |  |  |  | 
| 1598 |  |  |  |  |  |  | # Was the rectangle big enough? | 
| 1599 |  |  |  |  |  |  | my $dx = $zoomCoords[2] - $zoomCoords[0]; | 
| 1600 |  |  |  |  |  |  | my $dy = $zoomCoords[3] - $zoomCoords[1]; | 
| 1601 |  |  |  |  |  |  |  | 
| 1602 |  |  |  |  |  |  | return if ( abs($dx) < $threshold || | 
| 1603 |  |  |  |  |  |  | abs($dy) < $threshold ); | 
| 1604 |  |  |  |  |  |  |  | 
| 1605 |  |  |  |  |  |  | # Find the zooming factor | 
| 1606 |  |  |  |  |  |  | my $zx = $self->width() / abs($dx); | 
| 1607 |  |  |  |  |  |  | my $zy = $self->height() / abs($dy); | 
| 1608 |  |  |  |  |  |  | my $scale = min($zx, $zy); | 
| 1609 |  |  |  |  |  |  |  | 
| 1610 |  |  |  |  |  |  | # Zoom in our out? | 
| 1611 |  |  |  |  |  |  | # top->bottom drag means out, | 
| 1612 |  |  |  |  |  |  | # bottom->top drag means in. | 
| 1613 |  |  |  |  |  |  | # (0,0) is top left, so $dy > 0 means top->bottom | 
| 1614 |  |  |  |  |  |  | if ( $dy > 0 ) { | 
| 1615 |  |  |  |  |  |  | # Zooming in! | 
| 1616 |  |  |  |  |  |  | #STDERR->printf ( "Zooming in: $scale\n" ); | 
| 1617 |  |  |  |  |  |  | } else { | 
| 1618 |  |  |  |  |  |  | # Zooming out! | 
| 1619 |  |  |  |  |  |  | $scale = 1 - 1.0 / $scale; | 
| 1620 |  |  |  |  |  |  | #STDERR->printf ( "Zooming out: $scale\n" ); | 
| 1621 |  |  |  |  |  |  | } | 
| 1622 |  |  |  |  |  |  |  | 
| 1623 |  |  |  |  |  |  | # Scale everying up / down | 
| 1624 |  |  |  |  |  |  | $self->_scaleAndMoveView | 
| 1625 |  |  |  |  |  |  | ( $scale, | 
| 1626 |  |  |  |  |  |  | ($zoomCoords[0]+$zoomCoords[2])/2.0, | 
| 1627 |  |  |  |  |  |  | ($zoomCoords[1]+$zoomCoords[3])/2.0 ); | 
| 1628 |  |  |  |  |  |  | }); | 
| 1629 |  |  |  |  |  |  |  | 
| 1630 |  |  |  |  |  |  | 1; | 
| 1631 |  |  |  |  |  |  | } | 
| 1632 |  |  |  |  |  |  |  | 
| 1633 |  |  |  |  |  |  |  | 
| 1634 |  |  |  |  |  |  | ###################################################################### | 
| 1635 |  |  |  |  |  |  | # Setup bindings for scrolling / panning operations | 
| 1636 |  |  |  |  |  |  | # | 
| 1637 |  |  |  |  |  |  | ###################################################################### | 
| 1638 |  |  |  |  |  |  | sub _createScrollBindings | 
| 1639 |  |  |  |  |  |  | { | 
| 1640 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 1641 |  |  |  |  |  |  |  | 
| 1642 |  |  |  |  |  |  | # Interpret scrolling options | 
| 1643 |  |  |  |  |  |  |  | 
| 1644 |  |  |  |  |  |  | # What mouse button + modifiers starts scroll? | 
| 1645 |  |  |  |  |  |  | my $scrollSpec = $opt{'-scroll'}; | 
| 1646 |  |  |  |  |  |  | die __PACKAGE__.": No -scroll option" unless defined $scrollSpec; | 
| 1647 |  |  |  |  |  |  | if ( $scrollSpec =~ /^\<.+\>$/ ) { | 
| 1648 |  |  |  |  |  |  | # This should be a partial bind event spec, e.g. <1>, or | 
| 1649 |  |  |  |  |  |  | # -- it must end in a button number | 
| 1650 |  |  |  |  |  |  | die __PACKAGE__.": Illegal -scroll option" | 
| 1651 |  |  |  |  |  |  | unless ( $scrollSpec =~ /^\<.+\-\d\>$/ || | 
| 1652 |  |  |  |  |  |  | $scrollSpec =~ /^\<\d\>$/ ); | 
| 1653 |  |  |  |  |  |  | } | 
| 1654 |  |  |  |  |  |  | else { | 
| 1655 |  |  |  |  |  |  | # Anything else: use the default | 
| 1656 |  |  |  |  |  |  | $scrollSpec = '<2>'; | 
| 1657 |  |  |  |  |  |  | } | 
| 1658 |  |  |  |  |  |  |  | 
| 1659 |  |  |  |  |  |  | # Initial press starts panning | 
| 1660 |  |  |  |  |  |  | my $startEvent = $scrollSpec; | 
| 1661 |  |  |  |  |  |  | $startEvent =~ s/(\d\>)$/ButtonPress-$1/; | 
| 1662 |  |  |  |  |  |  | #STDERR->printf ( "startEvent = $startEvent\n" ); | 
| 1663 |  |  |  |  |  |  | $self->Tk::bind ( $startEvent => sub { $self->_startScroll | 
| 1664 |  |  |  |  |  |  | ( $scrollSpec ) } ); | 
| 1665 |  |  |  |  |  |  | } | 
| 1666 |  |  |  |  |  |  |  | 
| 1667 |  |  |  |  |  |  |  | 
| 1668 |  |  |  |  |  |  | ###################################################################### | 
| 1669 |  |  |  |  |  |  | # Called whenever a scroll event is started.  This installs (temporary) | 
| 1670 |  |  |  |  |  |  | # bindings for mouse motion and release to complete the scrolling. | 
| 1671 |  |  |  |  |  |  | # | 
| 1672 |  |  |  |  |  |  | # The motion / button release bindings have to be installed temporarily | 
| 1673 |  |  |  |  |  |  | # so they don't conflict with other bindings (such as for zooming) | 
| 1674 |  |  |  |  |  |  | # The original bindings for those events have to be restored once the | 
| 1675 |  |  |  |  |  |  | # zoom operation is completed. | 
| 1676 |  |  |  |  |  |  | ###################################################################### | 
| 1677 |  |  |  |  |  |  | sub _startScroll | 
| 1678 |  |  |  |  |  |  | { | 
| 1679 |  |  |  |  |  |  | my ($self, $scrollSpec) = @_; | 
| 1680 |  |  |  |  |  |  |  | 
| 1681 |  |  |  |  |  |  | # State data to keep track of scroll operation | 
| 1682 |  |  |  |  |  |  | my $startx = $self->canvasx ( $Tk::event->x ); | 
| 1683 |  |  |  |  |  |  | my $starty = $self->canvasy ( $Tk::event->y ); | 
| 1684 |  |  |  |  |  |  |  | 
| 1685 |  |  |  |  |  |  | # Dragging causes scroll to happen | 
| 1686 |  |  |  |  |  |  | my $dragEvent = ''; | 
| 1687 |  |  |  |  |  |  | #STDERR->printf ( "dragEvent = $dragEvent\n" ); | 
| 1688 |  |  |  |  |  |  | my $origDragBind = $self->Tk::bind ( $dragEvent ); | 
| 1689 |  |  |  |  |  |  | $self->Tk::bind ( $dragEvent => sub { | 
| 1690 |  |  |  |  |  |  | my $x = $self->canvasx ( $Tk::event->x ); | 
| 1691 |  |  |  |  |  |  | my $y = $self->canvasy ( $Tk::event->y ); | 
| 1692 |  |  |  |  |  |  |  | 
| 1693 |  |  |  |  |  |  | # Compute scroll ammount | 
| 1694 |  |  |  |  |  |  | my $dx = $x - $startx; | 
| 1695 |  |  |  |  |  |  | my $dy = $y - $starty; | 
| 1696 |  |  |  |  |  |  | #STDERR->printf ( "Scrolling: dx=$dx, dy=$dy\n" ); | 
| 1697 |  |  |  |  |  |  |  | 
| 1698 |  |  |  |  |  |  | # Feels better is scroll speed is reduced. | 
| 1699 |  |  |  |  |  |  | # Also is more natural inverted, feeld like dragging | 
| 1700 |  |  |  |  |  |  | # the canvas | 
| 1701 |  |  |  |  |  |  | $dx *= -.9; | 
| 1702 |  |  |  |  |  |  | $dy *= -.9; | 
| 1703 |  |  |  |  |  |  |  | 
| 1704 |  |  |  |  |  |  | my ($xv) = $self->xview(); | 
| 1705 |  |  |  |  |  |  | my ($yv) = $self->yview(); | 
| 1706 |  |  |  |  |  |  | my @sr = $self->cget( '-scrollregion' ); | 
| 1707 |  |  |  |  |  |  | #STDERR->printf ( "  xv=$xv, yv=$yv\n" ); | 
| 1708 |  |  |  |  |  |  | my $xpct = $xv + $dx/($sr[2]-$sr[0]); | 
| 1709 |  |  |  |  |  |  | my $ypct = $yv + $dy/($sr[3]-$sr[1]); | 
| 1710 |  |  |  |  |  |  | #STDERR->printf ( "  xpct=$xpct, ypct=$ypct\n" ); | 
| 1711 |  |  |  |  |  |  | $self->xview ( moveto => $xpct ); | 
| 1712 |  |  |  |  |  |  | $self->yview ( moveto => $ypct ); | 
| 1713 |  |  |  |  |  |  |  | 
| 1714 |  |  |  |  |  |  | # This is the new reference point for | 
| 1715 |  |  |  |  |  |  | # next motion event | 
| 1716 |  |  |  |  |  |  | $startx = $x; | 
| 1717 |  |  |  |  |  |  | $starty = $y; | 
| 1718 |  |  |  |  |  |  | #STDERR->printf ( "  scrolled\n" ); | 
| 1719 |  |  |  |  |  |  |  | 
| 1720 |  |  |  |  |  |  | } ); | 
| 1721 |  |  |  |  |  |  |  | 
| 1722 |  |  |  |  |  |  | # Releasing button finishes scrolling | 
| 1723 |  |  |  |  |  |  | my $stopEvent = $scrollSpec; | 
| 1724 |  |  |  |  |  |  | $stopEvent =~ s/^\<.*(\d\>)$/ | 
| 1725 |  |  |  |  |  |  | #STDERR->printf ( "stopEvent = $stopEvent\n" ); | 
| 1726 |  |  |  |  |  |  | my $origStopBind = $self->Tk::bind ( $stopEvent ); | 
| 1727 |  |  |  |  |  |  | $self->Tk::bind ( $stopEvent => sub { | 
| 1728 |  |  |  |  |  |  |  | 
| 1729 |  |  |  |  |  |  | # Restore original bindings | 
| 1730 |  |  |  |  |  |  | $self->Tk::bind ( $dragEvent => $origDragBind ); | 
| 1731 |  |  |  |  |  |  | $self->Tk::bind ( $stopEvent => $origStopBind ); | 
| 1732 |  |  |  |  |  |  |  | 
| 1733 |  |  |  |  |  |  | } ); | 
| 1734 |  |  |  |  |  |  |  | 
| 1735 |  |  |  |  |  |  | 1; | 
| 1736 |  |  |  |  |  |  | } | 
| 1737 |  |  |  |  |  |  |  | 
| 1738 |  |  |  |  |  |  |  | 
| 1739 |  |  |  |  |  |  | ###################################################################### | 
| 1740 |  |  |  |  |  |  | # Setup bindings for keypad keys to do zooming and scrolling | 
| 1741 |  |  |  |  |  |  | # | 
| 1742 |  |  |  |  |  |  | # This binds +/- on the keypad to zoom in and out, and the arrow/number | 
| 1743 |  |  |  |  |  |  | # keys to scroll. | 
| 1744 |  |  |  |  |  |  | ###################################################################### | 
| 1745 |  |  |  |  |  |  | sub _createKeypadBindings | 
| 1746 |  |  |  |  |  |  | { | 
| 1747 |  |  |  |  |  |  | my ($self, %opt) = @_; | 
| 1748 |  |  |  |  |  |  |  | 
| 1749 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1750 |  |  |  |  |  |  | sub { $self->zoom( -in => 1.15 ) } ); | 
| 1751 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1752 |  |  |  |  |  |  | sub { $self->zoom( -out => 1.15 ) } ); | 
| 1753 |  |  |  |  |  |  |  | 
| 1754 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1755 |  |  |  |  |  |  | sub { $self->xview( scroll => -1, 'units' ); | 
| 1756 |  |  |  |  |  |  | $self->yview( scroll => 1, 'units' ) } ); | 
| 1757 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1758 |  |  |  |  |  |  | sub { $self->yview( scroll => 1, 'units' ) } ); | 
| 1759 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1760 |  |  |  |  |  |  | sub { $self->xview( scroll => 1, 'units' ); | 
| 1761 |  |  |  |  |  |  | $self->yview( scroll => 1, 'units' ) } ); | 
| 1762 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1763 |  |  |  |  |  |  | sub { $self->xview( scroll => -1, 'units' ) } ); | 
| 1764 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1765 |  |  |  |  |  |  | sub { $self->xview( scroll => 1, 'units' ) } ); | 
| 1766 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1767 |  |  |  |  |  |  | sub { $self->xview( scroll => -1, 'units' ); | 
| 1768 |  |  |  |  |  |  | $self->yview( scroll => -1, 'units' ) } ); | 
| 1769 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1770 |  |  |  |  |  |  | sub { $self->yview( scroll => -1, 'units' ) } ); | 
| 1771 |  |  |  |  |  |  | $self->Tk::bind ( '' => | 
| 1772 |  |  |  |  |  |  | sub { $self->xview( scroll => 1, 'units' ); | 
| 1773 |  |  |  |  |  |  | $self->yview( scroll => -1, 'units' ) } ); | 
| 1774 |  |  |  |  |  |  |  | 
| 1775 |  |  |  |  |  |  | 1; | 
| 1776 |  |  |  |  |  |  | } | 
| 1777 |  |  |  |  |  |  |  | 
| 1778 |  |  |  |  |  |  |  | 
| 1779 |  |  |  |  |  |  | ####################################################################### | 
| 1780 |  |  |  |  |  |  | ## Setup binding for 'fit' operation | 
| 1781 |  |  |  |  |  |  | ## | 
| 1782 |  |  |  |  |  |  | ## 'fit' scales the entire contents of the graph to fit within the | 
| 1783 |  |  |  |  |  |  | ## visible portion of the canvas. | 
| 1784 |  |  |  |  |  |  | ####################################################################### | 
| 1785 |  |  |  |  |  |  | #sub _createFitBindings | 
| 1786 |  |  |  |  |  |  | #{ | 
| 1787 |  |  |  |  |  |  | #  my ($self, %opt) = @_; | 
| 1788 |  |  |  |  |  |  | # | 
| 1789 |  |  |  |  |  |  | #  # Interpret options | 
| 1790 |  |  |  |  |  |  | # | 
| 1791 |  |  |  |  |  |  | #  # What event to bind to? | 
| 1792 |  |  |  |  |  |  | #  my $fitEvent = $opt{'-fit'}; | 
| 1793 |  |  |  |  |  |  | #  die __PACKAGE__.": No -fit option" unless defined $fitEvent; | 
| 1794 |  |  |  |  |  |  | #  if ( $fitEvent =~ /^\<.+\>$/ ) { | 
| 1795 |  |  |  |  |  |  | #    die __PACKAGE__.": Illegal -fit option" | 
| 1796 |  |  |  |  |  |  | #      unless ( $fitEvent =~ /^\<.+\>$/ ); | 
| 1797 |  |  |  |  |  |  | #  } | 
| 1798 |  |  |  |  |  |  | #  else { | 
| 1799 |  |  |  |  |  |  | #    # Anything else: use the default | 
| 1800 |  |  |  |  |  |  | #    $fitEvent = ''; | 
| 1801 |  |  |  |  |  |  | #  } | 
| 1802 |  |  |  |  |  |  | # | 
| 1803 |  |  |  |  |  |  | #  STDERR->printf ( "fit event = $fitEvent\n" ); | 
| 1804 |  |  |  |  |  |  | #  $self->Tk::bind ( $fitEvent => sub { $self->fit( 'all' ) }); | 
| 1805 |  |  |  |  |  |  | #  1; | 
| 1806 |  |  |  |  |  |  | #} | 
| 1807 |  |  |  |  |  |  |  | 
| 1808 |  |  |  |  |  |  |  | 
| 1809 |  |  |  |  |  |  | ###################################################################### | 
| 1810 |  |  |  |  |  |  | # Scale the graph to fit within the canvas | 
| 1811 |  |  |  |  |  |  | # | 
| 1812 |  |  |  |  |  |  | ###################################################################### | 
| 1813 |  |  |  |  |  |  | sub fit | 
| 1814 |  |  |  |  |  |  | { | 
| 1815 |  |  |  |  |  |  | my ($self, $idOrTag) = @_; | 
| 1816 |  |  |  |  |  |  | $idOrTag = 'all' unless defined $idOrTag; | 
| 1817 |  |  |  |  |  |  |  | 
| 1818 |  |  |  |  |  |  | my $w = $self->width(); | 
| 1819 |  |  |  |  |  |  | my $h = $self->height(); | 
| 1820 |  |  |  |  |  |  | my ($x1,$y1,$x2,$y2) = $self->bbox( $idOrTag ); | 
| 1821 |  |  |  |  |  |  | return 0 unless ( defined $x1 && defined $x2 && | 
| 1822 |  |  |  |  |  |  | defined $y1 && defined $y2 ); | 
| 1823 |  |  |  |  |  |  |  | 
| 1824 |  |  |  |  |  |  | my $dx = abs($x2 - $x1); | 
| 1825 |  |  |  |  |  |  | my $dy = abs($y2 - $y1); | 
| 1826 |  |  |  |  |  |  |  | 
| 1827 |  |  |  |  |  |  | my $scalex = $w / $dx; | 
| 1828 |  |  |  |  |  |  | my $scaley = $h / $dy; | 
| 1829 |  |  |  |  |  |  | my $scale = min ( $scalex, $scaley ); | 
| 1830 |  |  |  |  |  |  | if ( $scalex >= 1.0 && $scaley >= 1.0 ) { | 
| 1831 |  |  |  |  |  |  | $scale = max ( $scalex, $scaley ); | 
| 1832 |  |  |  |  |  |  | } | 
| 1833 |  |  |  |  |  |  |  | 
| 1834 |  |  |  |  |  |  | $self->_scaleAndMoveView ( $scale, 0, 0 ); | 
| 1835 |  |  |  |  |  |  | $self->xview( moveto => 0 ); | 
| 1836 |  |  |  |  |  |  | $self->yview( moveto => 0 ); | 
| 1837 |  |  |  |  |  |  |  | 
| 1838 |  |  |  |  |  |  | 1; | 
| 1839 |  |  |  |  |  |  | } | 
| 1840 |  |  |  |  |  |  |  | 
| 1841 |  |  |  |  |  |  |  | 
| 1842 |  |  |  |  |  |  | ###################################################################### | 
| 1843 |  |  |  |  |  |  | # Zoom in or out, keep top-level centered. | 
| 1844 |  |  |  |  |  |  | # | 
| 1845 |  |  |  |  |  |  | ###################################################################### | 
| 1846 |  |  |  |  |  |  | sub zoom | 
| 1847 |  |  |  |  |  |  | { | 
| 1848 |  |  |  |  |  |  | my ($self, $dir, $scale) = @_; | 
| 1849 |  |  |  |  |  |  |  | 
| 1850 |  |  |  |  |  |  | if ( $dir eq '-in' ) { | 
| 1851 |  |  |  |  |  |  | # Make things bigger | 
| 1852 |  |  |  |  |  |  | } | 
| 1853 |  |  |  |  |  |  | elsif ( $dir eq '-out' ) { | 
| 1854 |  |  |  |  |  |  | # Make things smaller | 
| 1855 |  |  |  |  |  |  | $scale = 1 / $scale; | 
| 1856 |  |  |  |  |  |  | } | 
| 1857 |  |  |  |  |  |  |  | 
| 1858 |  |  |  |  |  |  | my ($xv1,$xv2) = $self->xview(); | 
| 1859 |  |  |  |  |  |  | my ($yv1,$yv2) = $self->yview(); | 
| 1860 |  |  |  |  |  |  | my $xvm = ($xv2 + $xv1)/2.0; | 
| 1861 |  |  |  |  |  |  | my $yvm = ($yv2 + $yv1)/2.0; | 
| 1862 |  |  |  |  |  |  | my ($l, $t, $r, $b) = $self->cget( -scrollregion ); | 
| 1863 |  |  |  |  |  |  |  | 
| 1864 |  |  |  |  |  |  | $self->_scaleAndMoveView ( $scale, | 
| 1865 |  |  |  |  |  |  | $l + $xvm *($r - $l), | 
| 1866 |  |  |  |  |  |  | $t + $yvm *($b - $t) ); | 
| 1867 |  |  |  |  |  |  |  | 
| 1868 |  |  |  |  |  |  | 1; | 
| 1869 |  |  |  |  |  |  | } | 
| 1870 |  |  |  |  |  |  |  | 
| 1871 |  |  |  |  |  |  |  | 
| 1872 |  |  |  |  |  |  | sub zoomTo | 
| 1873 |  |  |  |  |  |  | { | 
| 1874 |  |  |  |  |  |  | my ($self, $tagOrId) = @_; | 
| 1875 |  |  |  |  |  |  |  | 
| 1876 |  |  |  |  |  |  | $self->fit(); | 
| 1877 |  |  |  |  |  |  |  | 
| 1878 |  |  |  |  |  |  | my @bb = $self->bbox( $tagOrId ); | 
| 1879 |  |  |  |  |  |  | return unless @bb == 4 && defined($bb[0]); | 
| 1880 |  |  |  |  |  |  |  | 
| 1881 |  |  |  |  |  |  | my $w = $bb[2] - $bb[0]; | 
| 1882 |  |  |  |  |  |  | my $h = $bb[3] - $bb[1]; | 
| 1883 |  |  |  |  |  |  | my $scale = 2; | 
| 1884 |  |  |  |  |  |  | my $x1 = $bb[0] - $scale * $w; | 
| 1885 |  |  |  |  |  |  | my $y1 = $bb[1] - $scale * $h; | 
| 1886 |  |  |  |  |  |  | my $x2 = $bb[2] + $scale * $w; | 
| 1887 |  |  |  |  |  |  | my $y2 = $bb[3] + $scale * $h; | 
| 1888 |  |  |  |  |  |  |  | 
| 1889 |  |  |  |  |  |  | #STDERR->printf("zoomTo:  bb = @bb\n". | 
| 1890 |  |  |  |  |  |  | #		 "         w=$w h=$h\n". | 
| 1891 |  |  |  |  |  |  | #		 "         x1,$y1, $x2,$y2\n" ); | 
| 1892 |  |  |  |  |  |  |  | 
| 1893 |  |  |  |  |  |  | $self->zoomToRect( $x1, $y1, $x2, $y2 ); | 
| 1894 |  |  |  |  |  |  | } | 
| 1895 |  |  |  |  |  |  |  | 
| 1896 |  |  |  |  |  |  |  | 
| 1897 |  |  |  |  |  |  | sub zoomToRect | 
| 1898 |  |  |  |  |  |  | { | 
| 1899 |  |  |  |  |  |  | my ($self, @box) = @_; | 
| 1900 |  |  |  |  |  |  |  | 
| 1901 |  |  |  |  |  |  | # make sure x1,y1 = lower left, x2,y2 = upper right | 
| 1902 |  |  |  |  |  |  | ($box[0],$box[2]) = ($box[2],$box[0]) if $box[2] < $box[0]; | 
| 1903 |  |  |  |  |  |  | ($box[1],$box[3]) = ($box[3],$box[1]) if $box[3] < $box[1]; | 
| 1904 |  |  |  |  |  |  |  | 
| 1905 |  |  |  |  |  |  | # What is the scale relative to current bounds? | 
| 1906 |  |  |  |  |  |  | my ($l,$r) = $self->xview; | 
| 1907 |  |  |  |  |  |  | my ($t,$b) = $self->yview; | 
| 1908 |  |  |  |  |  |  | my $curr_w = $r - $l; | 
| 1909 |  |  |  |  |  |  | my $curr_h = $b - $t; | 
| 1910 |  |  |  |  |  |  |  | 
| 1911 |  |  |  |  |  |  | my @sr = $self->cget( -scrollregion ); | 
| 1912 |  |  |  |  |  |  | my $sr_w = $sr[2] - $sr[0]; | 
| 1913 |  |  |  |  |  |  | my $sr_h = $sr[3] - $sr[1]; | 
| 1914 |  |  |  |  |  |  | my $new_l = max(0.0,$box[0] / $sr_w); | 
| 1915 |  |  |  |  |  |  | my $new_t = max(0.0,$box[1] / $sr_h); | 
| 1916 |  |  |  |  |  |  | my $new_r = min(1.0,$box[2] / $sr_w); | 
| 1917 |  |  |  |  |  |  | my $new_b = min(1.0,$box[3] / $sr_h); | 
| 1918 |  |  |  |  |  |  |  | 
| 1919 |  |  |  |  |  |  | my $new_w = $new_r - $new_l; | 
| 1920 |  |  |  |  |  |  | my $new_h = $new_b - $new_t; | 
| 1921 |  |  |  |  |  |  |  | 
| 1922 |  |  |  |  |  |  | my $scale = max( $curr_w/$new_w, $curr_h/$new_h ); | 
| 1923 |  |  |  |  |  |  |  | 
| 1924 |  |  |  |  |  |  | $self->_scaleAndMoveView( $scale, | 
| 1925 |  |  |  |  |  |  | ($box[0] + $box[2])/2.0, | 
| 1926 |  |  |  |  |  |  | ($box[1] + $box[3])/2.0 ); | 
| 1927 |  |  |  |  |  |  |  | 
| 1928 |  |  |  |  |  |  | 1; | 
| 1929 |  |  |  |  |  |  | } | 
| 1930 |  |  |  |  |  |  |  | 
| 1931 |  |  |  |  |  |  |  | 
| 1932 |  |  |  |  |  |  | ###################################################################### | 
| 1933 |  |  |  |  |  |  | # Over-ridden createText Method | 
| 1934 |  |  |  |  |  |  | # | 
| 1935 |  |  |  |  |  |  | # Handles the embedded \l\r\n graphViz control characters | 
| 1936 |  |  |  |  |  |  | ###################################################################### | 
| 1937 |  |  |  |  |  |  | sub createText | 
| 1938 |  |  |  |  |  |  | { | 
| 1939 |  |  |  |  |  |  | my ($self, $x, $y, %attrs) = @_; | 
| 1940 |  |  |  |  |  |  |  | 
| 1941 |  |  |  |  |  |  | if( defined($attrs{-text}) ) { | 
| 1942 |  |  |  |  |  |  |  | 
| 1943 |  |  |  |  |  |  | # Set Justification, based on any \n \l \r in the text label | 
| 1944 |  |  |  |  |  |  | my $label = $attrs{-text}; | 
| 1945 |  |  |  |  |  |  | my $justify = 'center'; | 
| 1946 |  |  |  |  |  |  |  | 
| 1947 |  |  |  |  |  |  | # Per the dotguide.pdf, a '\l', '\r', or '\n' is | 
| 1948 |  |  |  |  |  |  | #  just a line terminator, not a newline. So in cases | 
| 1949 |  |  |  |  |  |  | #   where the label ends in one of these characters, we are | 
| 1950 |  |  |  |  |  |  | #   going to remove the newline char later | 
| 1951 |  |  |  |  |  |  | my $removeNewline; | 
| 1952 |  |  |  |  |  |  | if( $label =~ /\\[nlr]$/){ | 
| 1953 |  |  |  |  |  |  | $removeNewline = 1; | 
| 1954 |  |  |  |  |  |  | } | 
| 1955 |  |  |  |  |  |  |  | 
| 1956 |  |  |  |  |  |  | if( $label =~ s/\\l/\n/g ){ | 
| 1957 |  |  |  |  |  |  | $justify = 'left'; | 
| 1958 |  |  |  |  |  |  | } | 
| 1959 |  |  |  |  |  |  | if( $label =~ s/\\r/\n/g ){ | 
| 1960 |  |  |  |  |  |  | $justify = 'right'; | 
| 1961 |  |  |  |  |  |  | } | 
| 1962 |  |  |  |  |  |  |  | 
| 1963 |  |  |  |  |  |  | # Change \n to actual \n | 
| 1964 |  |  |  |  |  |  | if( $label =~ s/\\n/\n/g ){ | 
| 1965 |  |  |  |  |  |  | $justify  = 'center'; | 
| 1966 |  |  |  |  |  |  | } | 
| 1967 |  |  |  |  |  |  |  | 
| 1968 |  |  |  |  |  |  | # remove ending newline if flag set | 
| 1969 |  |  |  |  |  |  | if( $removeNewline){ | 
| 1970 |  |  |  |  |  |  | $label =~ s/\n$//; | 
| 1971 |  |  |  |  |  |  | } | 
| 1972 |  |  |  |  |  |  |  | 
| 1973 |  |  |  |  |  |  | # Fix  any escaped chars | 
| 1974 |  |  |  |  |  |  | #   like \} to }, and \\{ to \{ | 
| 1975 |  |  |  |  |  |  | $label =~ s/\\(?!\\)(.)/$1/g; | 
| 1976 |  |  |  |  |  |  |  | 
| 1977 |  |  |  |  |  |  | $attrs{-text} = $label; | 
| 1978 |  |  |  |  |  |  | $attrs{-justify} = $justify; | 
| 1979 |  |  |  |  |  |  |  | 
| 1980 |  |  |  |  |  |  | # Fix the label tag, if there is one | 
| 1981 |  |  |  |  |  |  | my $tags; | 
| 1982 |  |  |  |  |  |  | if( defined($tags = $attrs{-tags})){ | 
| 1983 |  |  |  |  |  |  | my %tags = (@$tags); | 
| 1984 |  |  |  |  |  |  | $tags{label} = $label if(defined($tags{label})); | 
| 1985 |  |  |  |  |  |  | $attrs{-tags} = [%tags]; | 
| 1986 |  |  |  |  |  |  | } | 
| 1987 |  |  |  |  |  |  |  | 
| 1988 |  |  |  |  |  |  | # Get the default font, if not defined already | 
| 1989 |  |  |  |  |  |  | my $fonts = $self->{fonts}; | 
| 1990 |  |  |  |  |  |  | unless(defined($fonts->{_default}) ){ | 
| 1991 |  |  |  |  |  |  |  | 
| 1992 |  |  |  |  |  |  | # Create dummy item, so we can see what font is used | 
| 1993 |  |  |  |  |  |  | my $dummyID = $self->SUPER::createText | 
| 1994 |  |  |  |  |  |  | ( 100,25, -text => "You should never see this" ); | 
| 1995 |  |  |  |  |  |  | my $defaultfont = $self->itemcget($dummyID,-font); | 
| 1996 |  |  |  |  |  |  |  | 
| 1997 |  |  |  |  |  |  | # Make a copy that we will mess with: | 
| 1998 |  |  |  |  |  |  | $defaultfont = $defaultfont->Clone; | 
| 1999 |  |  |  |  |  |  | $fonts->{_default}{font}     = $defaultfont; | 
| 2000 |  |  |  |  |  |  | $fonts->{_default}{origSize} = $defaultfont->actual(-size); | 
| 2001 |  |  |  |  |  |  |  | 
| 2002 |  |  |  |  |  |  | # Delete the dummy item | 
| 2003 |  |  |  |  |  |  | $self->delete($dummyID); | 
| 2004 |  |  |  |  |  |  | } | 
| 2005 |  |  |  |  |  |  |  | 
| 2006 |  |  |  |  |  |  | # Assign the default font | 
| 2007 |  |  |  |  |  |  | unless( defined($attrs{-font}) ){ | 
| 2008 |  |  |  |  |  |  | $attrs{-font} = $fonts->{_default}{font}; | 
| 2009 |  |  |  |  |  |  | } | 
| 2010 |  |  |  |  |  |  |  | 
| 2011 |  |  |  |  |  |  | } | 
| 2012 |  |  |  |  |  |  |  | 
| 2013 |  |  |  |  |  |  | # Call Inherited createText | 
| 2014 |  |  |  |  |  |  | $self->SUPER::createText ( $x, $y, %attrs ); | 
| 2015 |  |  |  |  |  |  | } | 
| 2016 |  |  |  |  |  |  |  | 
| 2017 |  |  |  |  |  |  |  | 
| 2018 |  |  |  |  |  |  | ###################################################################### | 
| 2019 |  |  |  |  |  |  | #  Sub to try a color name, returns the color name if recognized | 
| 2020 |  |  |  |  |  |  | #   'black' and issues a warning if not | 
| 2021 |  |  |  |  |  |  | ###################################################################### | 
| 2022 |  |  |  |  |  |  | sub _tryColor | 
| 2023 |  |  |  |  |  |  | { | 
| 2024 |  |  |  |  |  |  | my ($self,$color) = @_; | 
| 2025 |  |  |  |  |  |  |  | 
| 2026 |  |  |  |  |  |  | return undef unless defined($color); | 
| 2027 |  |  |  |  |  |  |  | 
| 2028 |  |  |  |  |  |  | # Special cases | 
| 2029 |  |  |  |  |  |  | if( $color eq 'crimson' ) { | 
| 2030 |  |  |  |  |  |  | # crimison not defined in Tk, so use GraphViz's definition | 
| 2031 |  |  |  |  |  |  | return sprintf("#%02X%02x%02X", 246,231,220); | 
| 2032 |  |  |  |  |  |  | } | 
| 2033 |  |  |  |  |  |  | elsif( $color =~ /^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)\s+(-?\d+\.?\d*)\s*$/ ) { | 
| 2034 |  |  |  |  |  |  | # three color numbers | 
| 2035 |  |  |  |  |  |  | my($hue,$sat,$bright) = ($1,$2,$3); | 
| 2036 |  |  |  |  |  |  | return $self->_hsb2rgb($hue,$sat,$bright); | 
| 2037 |  |  |  |  |  |  | } | 
| 2038 |  |  |  |  |  |  |  | 
| 2039 |  |  |  |  |  |  | # Don't check color if it is a hex rgb value | 
| 2040 |  |  |  |  |  |  | unless( $color =~ /^\#\w+/ ) { | 
| 2041 |  |  |  |  |  |  | my $tryColor = $color; | 
| 2042 |  |  |  |  |  |  | $tryColor =~ s/\_//g; # get rid of any underscores | 
| 2043 |  |  |  |  |  |  | my @rgb; | 
| 2044 |  |  |  |  |  |  | eval { @rgb = $self->rgb($tryColor); }; | 
| 2045 |  |  |  |  |  |  | if ($@) { | 
| 2046 |  |  |  |  |  |  | warn __PACKAGE__.": Unkown color $color, using black instead\n"; | 
| 2047 |  |  |  |  |  |  | $color = 'black'; | 
| 2048 |  |  |  |  |  |  | } else { | 
| 2049 |  |  |  |  |  |  | $color = $tryColor; | 
| 2050 |  |  |  |  |  |  | } | 
| 2051 |  |  |  |  |  |  | } | 
| 2052 |  |  |  |  |  |  |  | 
| 2053 |  |  |  |  |  |  | $color; | 
| 2054 |  |  |  |  |  |  | } | 
| 2055 |  |  |  |  |  |  |  | 
| 2056 |  |  |  |  |  |  |  | 
| 2057 |  |  |  |  |  |  | ###################################################################### | 
| 2058 |  |  |  |  |  |  | # Sub to convert from Hue-Sat-Brightness to RGB hex number | 
| 2059 |  |  |  |  |  |  | # | 
| 2060 |  |  |  |  |  |  | ###################################################################### | 
| 2061 |  |  |  |  |  |  | sub _hsb2rgb | 
| 2062 |  |  |  |  |  |  | { | 
| 2063 |  |  |  |  |  |  | my ($self,$h,$s,$v) = @_; | 
| 2064 |  |  |  |  |  |  |  | 
| 2065 |  |  |  |  |  |  | my ($r,$g,$b); | 
| 2066 |  |  |  |  |  |  | if( $s <= 0){ | 
| 2067 |  |  |  |  |  |  | $v = int($v); | 
| 2068 |  |  |  |  |  |  | ($r,$g,$b) = ($v,$v,$v); | 
| 2069 |  |  |  |  |  |  | } | 
| 2070 |  |  |  |  |  |  | else{ | 
| 2071 |  |  |  |  |  |  | if( $h >= 1){ | 
| 2072 |  |  |  |  |  |  | $h = 0; | 
| 2073 |  |  |  |  |  |  | } | 
| 2074 |  |  |  |  |  |  | $h = 6*$h; | 
| 2075 |  |  |  |  |  |  | my $f = $h - int($h); | 
| 2076 |  |  |  |  |  |  | my $p = $v * (1 - $s); | 
| 2077 |  |  |  |  |  |  | my $q = $v * ( 1 - ($s * $f)); | 
| 2078 |  |  |  |  |  |  | my $t = $v * ( 1 - ($s * (1-$f))); | 
| 2079 |  |  |  |  |  |  | my $i = int($h); | 
| 2080 |  |  |  |  |  |  | if( $i == 0){	   ($r,$g,$b)  = ($v, $t, $p);} | 
| 2081 |  |  |  |  |  |  | elsif( $i == 1){ ($r,$g,$b)  = ($q, $v, $p);} | 
| 2082 |  |  |  |  |  |  | elsif( $i == 2){($r,$g,$b)   = ($p, $v, $t);} | 
| 2083 |  |  |  |  |  |  | elsif( $i == 3){($r,$g,$b)   = ($p, $q, $v);} | 
| 2084 |  |  |  |  |  |  | elsif( $i == 4){($r,$g,$b)   = ($t, $p, $v);} | 
| 2085 |  |  |  |  |  |  | elsif( $i == 5){($r,$g,$b)   = ($v, $p, $q);} | 
| 2086 |  |  |  |  |  |  |  | 
| 2087 |  |  |  |  |  |  | } | 
| 2088 |  |  |  |  |  |  |  | 
| 2089 |  |  |  |  |  |  | sprintf("#%02X%02x%02X", 255*$r, 255*$g, 244*$b); | 
| 2090 |  |  |  |  |  |  | } | 
| 2091 |  |  |  |  |  |  |  | 
| 2092 |  |  |  |  |  |  |  | 
| 2093 |  |  |  |  |  |  | ###################################################################### | 
| 2094 |  |  |  |  |  |  | # Utility functions | 
| 2095 |  |  |  |  |  |  | ###################################################################### | 
| 2096 |  |  |  |  |  |  |  | 
| 2097 |  |  |  |  |  |  | sub min { | 
| 2098 |  |  |  |  |  |  | if ( defined($_[0]) ) { | 
| 2099 |  |  |  |  |  |  | if ( defined($_[1]) ) { return ($_[0] < $_[1])? $_[0] : $_[1]; } | 
| 2100 |  |  |  |  |  |  | else { return $_[0]; } | 
| 2101 |  |  |  |  |  |  | } else { | 
| 2102 |  |  |  |  |  |  | if ( defined($_[1]) ) { return $_[1]; } | 
| 2103 |  |  |  |  |  |  | else { return undef; } | 
| 2104 |  |  |  |  |  |  | } | 
| 2105 |  |  |  |  |  |  | } | 
| 2106 |  |  |  |  |  |  |  | 
| 2107 |  |  |  |  |  |  | sub max { | 
| 2108 |  |  |  |  |  |  | if ( defined($_[0]) ) { | 
| 2109 |  |  |  |  |  |  | if ( defined($_[1]) ) { return ($_[0] > $_[1])? $_[0] : $_[1]; } | 
| 2110 |  |  |  |  |  |  | else { return $_[0]; } | 
| 2111 |  |  |  |  |  |  | } else { | 
| 2112 |  |  |  |  |  |  | if ( defined($_[1]) ) { return $_[1]; } | 
| 2113 |  |  |  |  |  |  | else { return undef; } | 
| 2114 |  |  |  |  |  |  | } | 
| 2115 |  |  |  |  |  |  | } | 
| 2116 |  |  |  |  |  |  |  | 
| 2117 |  |  |  |  |  |  | __END__ |