commsfwtools/commstools/svg/parseseq.pl
changeset 0 dfb7c4ff071f
child 18 9644881fedd0
equal deleted inserted replaced
-1:000000000000 0:dfb7c4ff071f
       
     1 # Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     2 # All rights reserved.
       
     3 # This component and the accompanying materials are made available
       
     4 # under the terms of "Eclipse Public License v1.0"
       
     5 # which accompanies this distribution, and is available
       
     6 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     7 #
       
     8 # Initial Contributors:
       
     9 # Nokia Corporation - initial contribution.
       
    10 #
       
    11 # Contributors:
       
    12 #
       
    13 # Description:
       
    14 # parseseq.pl  [<arguments>] <sequence file>
       
    15 # Arguments:
       
    16 # -p			Include popups (when clicking on messages etc).
       
    17 # -r <begin row>,<end row>	Display rows in the range specified.  Row numbers are zero based and inclusive.
       
    18 # Negative row numbers count backwards from the end row.  <begin row> and/or
       
    19 # <end row> may be omitted, in which case the beginning and ending rows respectively
       
    20 # are assumed.  If "r" argument is not specified, a default of the last 400 rows
       
    21 # is assumed (equivalent to an argument of "-r -400,").
       
    22 # -R <rows per view>		The maximum number of rows in each "view" (page).  The overall sequence is generated as a number
       
    23 # of individual HTML/SVG files, each showing a fragment of the sequence.  Each of these is
       
    24 # termed a "view".  This option indicates the maximum number of rows per view.  Note
       
    25 # that because the sequence is split into equal sized views, the actual size of each
       
    26 # view can vary from half values of this parameter to its full value.  This parameter
       
    27 # only limits the maximum size of each view.
       
    28 # -H			On "trimmed" displays (i.e. only displaying last <n> lines), don't
       
    29 # hide object timelines that don't have any messages within the area
       
    30 # being displayed (but do have messages outside of that area).
       
    31 # -x <file>		File containing "[DontHideShortName]" section which contains truncated names
       
    32 # of classes (minus the instance numbering at the end) that should *not* be
       
    33 # hidden from "trimmed" displays (i.e. displays where only the last <n> lines
       
    34 # are displayed).
       
    35 # -M			Exclude messages from the display.  Used for getting an overview of node and activity creation.
       
    36 # -v			Print on standard output a list of objects which have not been displayed because
       
    37 # they exchanged no messages or were outside of the displayed view.
       
    38 # -V			Print on standard output some progress messages (useful when parsing long logs that
       
    39 # take several minutes).
       
    40 # -f					Do not display page links in a fixed position window.
       
    41 # -n                    Print node names vertically as opposed to horizontally.
       
    42 # -N                    Node to filter available list by
       
    43 # -A                    Activity to filter available list by
       
    44 # -h                    This help message
       
    45 # todo:
       
    46 # - use findObject() within findOrCreateobject()
       
    47 # - hide objects whose lifetime extends entirely across the displayed area, but exchange no messages in that area
       
    48 # 
       
    49 #
       
    50 
       
    51 #require 'getopts.pl';
       
    52 use Getopt::Std;
       
    53 use HTML::Entities;
       
    54 use strict;
       
    55 use Cwd;
       
    56 
       
    57 my $version = "2.2 (27/02/08)";
       
    58 
       
    59 getopts("MHpx:N:A:vVr:R:hnf");
       
    60 our($opt_M, $opt_H, $opt_p, $opt_x, $opt_N, $opt_A, $opt_v, $opt_V, $opt_r, $opt_R, $opt_h, $opt_n, $opt_f);
       
    61 
       
    62 if ($opt_h)
       
    63 {
       
    64     print <<HELP;
       
    65 Usage:
       
    66     parseseq.pl  [<arguments>] <sequence file>
       
    67 
       
    68  Arguments:
       
    69 
       
    70  -p			Include popups (when clicking on messages etc).
       
    71 
       
    72  -r <begin row>,<end row>   Display rows in the range specified.  Row numbers are zero based and inclusive.
       
    73                             Negative row numbers count backwards from the end row.  <begin row> and/or
       
    74                             <end row> may be omitted, in which case the beginning and ending rows respectively
       
    75                             are assumed.  If "r" argument is not specified, a default of the last 400 rows
       
    76                             is assumed (equivalent to an argument of "-r -400,").
       
    77 
       
    78  -R <rows per view>         The maximum number of rows in each "view" (page).  The overall sequence is generated
       
    79                             as a number of individual HTML/SVG files, each showing a fragment of the sequence.
       
    80 			    Each of these is termed a "view".  This option indicates the maximum number of rows
       
    81 			    per view.  Note that because the sequence is split into equal sized views, the actual
       
    82 			    size of each view can vary from half values of this parameter to its full value.
       
    83 			    This parameter only limits the maximum size of each view.
       
    84 
       
    85  -H                         On "trimmed" displays (i.e. only displaying last <n> lines), don't
       
    86                             hide object timelines that don't have any messages within the area
       
    87                             being displayed (but do have messages outside of that area).
       
    88 
       
    89  -x <file>		    File containing "[DontHideShortName]" section which contains truncated names
       
    90                             of classes (minus the instance numbering at the end) that should *not* be
       
    91                             hidden from "trimmed" displays (i.e. displays where only the last <n> lines
       
    92                             are displayed).
       
    93 
       
    94  -M                         Exclude messages from the display.  Used for getting an overview of node and activity
       
    95                             creation.
       
    96 
       
    97  -v                         Print on standard output a list of objects which have not been displayed because
       
    98                             they exchanged no messages or were outside of the displayed view.
       
    99 
       
   100  -V                         Print on standard output some progress messages (useful when parsing long logs that
       
   101                             take several minutes).
       
   102 
       
   103  -f							Do not display page links in a fixed position window.
       
   104 							
       
   105  -n                         Print node names vertically as opposed to horizontally.
       
   106  
       
   107  -N                         Node to filter available list by. If set, you will be given a list of node activities
       
   108                             which contain the string you passed in, in the node. Select one of these and it will
       
   109 			    generate the diagram only for the lifetime of that activity.
       
   110  
       
   111  -A                         Activity to filter available list by. If set, you will be given a list of node
       
   112                             activities which match the string you passed in, in the activity.
       
   113 
       
   114 			    e.g.
       
   115 			    >parseseq -A start \log.seq
       
   116                             Reading sequence file
       
   117                             [1] Conn1 :: ConnectionStart
       
   118                             [2] IPCPr :: IpCprStart
       
   119                             [3] IPProtoCPr :: IPProtoCprStart
       
   120                             [4] AgentCPr :: PRStart
       
   121                             [5] AgentCPr :: CprDataClientStart
       
   122                             [6] AgentSCPr :: AgentSCprStart
       
   123                             Select activity to draw:
       
   124                             ...
       
   125 			    
       
   126  -h                         This help message
       
   127 HELP
       
   128     exit (0);
       
   129 }
       
   130 
       
   131 #
       
   132 # Flags
       
   133 #
       
   134 
       
   135 # remove objects that are created but have no messages
       
   136 my $trimSilentFlag = 1;
       
   137 
       
   138 # sort objects left-to-right by type
       
   139 my $sortObjectsFlag = 1;
       
   140 
       
   141 my $maxRowsToDisplay = 300;
       
   142 if ($opt_R) {
       
   143 	die "Parameter to -R option must be between 100 and 1000\n" if $opt_R < 100 || $opt_R > 1000;
       
   144 	$maxRowsToDisplay = $opt_R;
       
   145 }
       
   146 
       
   147 # whether to output timestamps in the form "hh:mm:ss,dd" where the ",dd" represents the number of seconds since the last timestamp (where possible)
       
   148 my $outputTimeDeltas = 0;
       
   149 
       
   150 # constants controlling progress messages
       
   151 my $sequenceProgressIncrement = 10000;
       
   152 my $trimProgressTrigger = 1000;
       
   153 my $trimProgressPercentIncrement = 10;
       
   154 
       
   155 #
       
   156 # constants controlling layout
       
   157 #
       
   158 
       
   159 my $incrementY = 19;
       
   160 my $fontWidth = 5;
       
   161 my $fontHeight = 9;
       
   162 my $leftMargin = 125;
       
   163 my $leftSeperatorMargin = 55;
       
   164 my $screenWidth = 50;
       
   165 my $rightMargin = 30;
       
   166 my $topMargin = 20;
       
   167 my $bottomMargin = 22;
       
   168 my $objectSpacing = 90;
       
   169 my $arrowWidth = 10;
       
   170 my $textArrowSpacing = 1;
       
   171 my $messageSpacingAboveLine = 1;
       
   172 my $objectVerticalLineSpacing = 2;
       
   173 my $objectUnderlineToLifelineGap = 2;
       
   174 my $objectNameRepeatCount = 35;
       
   175 my $lifelineBottomOverrun = 5;
       
   176 my $activitySpacing = 6;
       
   177 my $textMattSize = $activitySpacing * 2 + 2;	# text is on top of node lifeline and first two activity lines
       
   178 my $lifelineTextLeftMargin = 8;
       
   179 my $rotatedTextOffset = 3;
       
   180 #
       
   181 # derived constants
       
   182 #
       
   183 
       
   184 my $topOffset = $topMargin + $objectVerticalLineSpacing;
       
   185 
       
   186 #
       
   187 # colour constants
       
   188 #
       
   189 
       
   190 my $objectNameColour1 = "red";
       
   191 my $objectNameColour2 = "blue";
       
   192 my $objectNameLightColour1 = "salmon";
       
   193 my $objectNameLightColour2 = "rgb(50,50,200)";
       
   194 my $aliveColour = "black";
       
   195 my $deadColour = "white";
       
   196 my $seperatorColour = "rgb(245,245,245)";
       
   197 my $receiveColour = "black";
       
   198 my $postColour = "green";
       
   199 
       
   200 my $activityColour = "skyblue";
       
   201 my $activityEndUnknownColour = "salmon";
       
   202 
       
   203 # magic value for Creation/Deletion unknown
       
   204 
       
   205 my $objectUnknown = -2;
       
   206 
       
   207 #
       
   208 # changeable global variables that need to be re-initialised per view drawn
       
   209 # (see initialiseGlobals())
       
   210 #
       
   211 
       
   212 my $currentY;
       
   213 
       
   214 #
       
   215 # changeable global variables that need to be initialised once before views are drawn
       
   216 #
       
   217 
       
   218 my $minOrder = 100;
       
   219 my $maxOrder = 0;
       
   220 
       
   221 my $limitedRowText;
       
   222 
       
   223 # hash for quick lookup of objects
       
   224 my %objhash = ();
       
   225 my @silentObjectsTrimmed = ();
       
   226 my @messages = ();
       
   227 my @sequences = ();
       
   228 my @objects = ();
       
   229 my %dontHideShortName = ();
       
   230 
       
   231 #
       
   232 # Data Structures:
       
   233 #
       
   234 # "objects" is array of hashes:
       
   235 #	{ Name => , X =>,   Y =>,  MsgCount =>, Creation =>, Deletion =>, Order => }
       
   236 #
       
   237 # "sequences" is an array of hashes:
       
   238 #	{ Action => , Object => Text =>, SrcObj =>, DestObj =>, Msg => }
       
   239 
       
   240 
       
   241 # Input file format:
       
   242 #
       
   243 #	<action> <arguments> ...
       
   244 #
       
   245 # specifically:
       
   246 #
       
   247 #	[p|r] <message name> <source object> <destination object>
       
   248 #	t <object> <text>
       
   249 #	oc <object> <placement order>
       
   250 #	od <object>
       
   251 #	l <left hand label>
       
   252 #
       
   253 # actions:
       
   254 #
       
   255 # p		Post
       
   256 # r		Receive
       
   257 # t		Text
       
   258 # oc	Object Create
       
   259 # od	Object Destroyed
       
   260 # l		Label
       
   261 #
       
   262 # All fields are space separated text.  For example:
       
   263 #
       
   264 # P StartFlow SCPR Flow
       
   265 #
       
   266 
       
   267 readIniFile();
       
   268 initialiseGlobals();
       
   269     
       
   270 print "Reading sequence file\n" if ($opt_V);
       
   271 readSequences();
       
   272 
       
   273 if ($opt_N || $opt_A)
       
   274     {
       
   275     trimDownToActivity();
       
   276     }
       
   277 
       
   278 if ($trimSilentFlag)
       
   279 	{
       
   280 	print "Trimming silent objects\n" if ($opt_V);
       
   281 	trimSilentObjects();
       
   282 	}
       
   283 
       
   284 my $beginRow = 0;
       
   285 my $endRow = 99999;
       
   286 
       
   287 # Count the number of rows that would be displayed
       
   288 # (doesn't actually draw them because of "0" argument)
       
   289 
       
   290 print "Generating sequence pages\n" if ($opt_V);
       
   291 
       
   292 my $totalRows = drawSequences($beginRow, $endRow, 0);
       
   293 fixObjectsWithUnknownCreationDeletion();
       
   294 
       
   295 my $rowsToDisplay = $totalRows;
       
   296 
       
   297 if ($opt_r) {
       
   298 	$rowsToDisplay = processRowArgument($totalRows);
       
   299 	}
       
   300 
       
   301 createSubDir();
       
   302 
       
   303 if ($totalRows <= $maxRowsToDisplay) {
       
   304 	drawView(0, $totalRows - 1, 0, 0);
       
   305 } else {
       
   306 	my $viewsToDisplay = int($rowsToDisplay / $maxRowsToDisplay) + 1;
       
   307 	my $rowsPerView = int(($rowsToDisplay + $viewsToDisplay - 1) / $viewsToDisplay);
       
   308 	my $viewNumber = 0;
       
   309 	createViewMap();
       
   310 	while ($beginRow < $totalRows) {
       
   311 		$endRow = $beginRow + $rowsPerView - 1;
       
   312 		if ($endRow >= $totalRows) {
       
   313 			drawView($beginRow, $totalRows - 1, $viewNumber, $viewNumber);	# last view
       
   314 		} else {
       
   315 			drawView($beginRow, $endRow, $viewNumber, $viewsToDisplay - 1);
       
   316 		}
       
   317 		++$viewNumber;
       
   318 		$beginRow = $endRow + 1;
       
   319 	}
       
   320 	closeViewMap($viewNumber);
       
   321 }
       
   322 
       
   323 print "\n" if ($opt_V);
       
   324 
       
   325 if ($opt_v)
       
   326 	{
       
   327 	printArray("\nObjects without any message exchange", \@silentObjectsTrimmed);
       
   328 	}
       
   329 
       
   330 
       
   331 #
       
   332 # Generate the SVG and HTML files for a particular "view" (range of rows)
       
   333 #
       
   334 
       
   335 sub drawView($$$$) {
       
   336 	my ($beginRow, $endRow, $viewNumber, $lastViewNumber) = @_;
       
   337 	print "Generating page $viewNumber of $lastViewNumber\r" if ($opt_V);
       
   338 	if ($opt_H != 1)
       
   339 		{
       
   340 		hideObjectsOutsideOfView($beginRow, $endRow);
       
   341 		}
       
   342 	
       
   343 	my $screenWidth = calculateObjectColumnPositions();
       
   344 	my $screenHeight = (($endRow - $beginRow + 1) * $incrementY) + $topMargin + $bottomMargin;
       
   345 
       
   346 	#
       
   347 	# Filename convention for views: log0.svg, log1.svg, ..., log<n>.svg, log.svg - i.e. log.svg is the last view
       
   348 	# (considered the one most interesting and hence gets undecorated filename).
       
   349 	#
       
   350 
       
   351 	my $fileName = "log";
       
   352 	if ($viewNumber != $lastViewNumber) {
       
   353 		$fileName .= $viewNumber;
       
   354 		}
       
   355 
       
   356 	open SVG, ">html/${fileName}.svg" || die "Cannot open html/${fileName}.svg for writing\n";
       
   357 	#open RTTTL, ">html/${fileName}.rtttlpre" || die "Cannot open html/${fileName}.rttlpre for writing\n";
       
   358 
       
   359 	outputDocHeader($screenWidth, $screenHeight);
       
   360 	drawObjectNames(0);
       
   361 	updateObjectViewList($viewNumber);
       
   362 	drawObjectLifelines($beginRow, $endRow, $screenHeight);
       
   363 	drawActivities($beginRow, $endRow, $screenHeight, $viewNumber);
       
   364 	my @labelsOnPage = ();
       
   365 	my @objectsDestroyed = ();
       
   366 	my @objectsCreated = ();
       
   367 	drawSequences($beginRow, $endRow, 1, \@labelsOnPage, \@objectsCreated, \@objectsDestroyed);
       
   368 	outputViewMap($viewNumber, $lastViewNumber, \@labelsOnPage, \@objectsCreated, \@objectsDestroyed);
       
   369 	outputDocFooter();
       
   370 
       
   371 	close SVG;
       
   372 	#close RTTTL;
       
   373 
       
   374 	outputHTMLEmbedder($screenWidth, $screenHeight, $viewNumber, $lastViewNumber, $fileName);
       
   375 	if ($opt_v) {
       
   376 		printHiddenObjects("\nObjects outside of displayed view $viewNumber");
       
   377 	}
       
   378 
       
   379 	resetForNextView();
       
   380 }
       
   381 
       
   382 sub createSubDir() {
       
   383 	if (! -d html) {
       
   384 		mkdir "html" || die "Cannot create 'html' subdirectory\n";
       
   385 	}
       
   386 }
       
   387 sub resetForNextView() {
       
   388 	# objects to hide must be calculated afresh per view
       
   389 	unhideAllObjects();
       
   390 	initialiseGlobals();
       
   391 }
       
   392 
       
   393 #
       
   394 # (re)initialise globals required per view drawn
       
   395 #
       
   396 sub initialiseGlobals() {
       
   397 	$currentY = $topMargin;
       
   398 	}
       
   399 
       
   400 
       
   401 ####################
       
   402 # Message routines
       
   403 ####################
       
   404 
       
   405 sub findOrCreateMessage($)
       
   406 	{
       
   407 	my $msgName = $_[0];
       
   408 	my $msg;
       
   409 	foreach $msg (@messages) {
       
   410 		if ($msg eq $msgName) {
       
   411 			return \$msg;
       
   412 			}
       
   413 		}
       
   414 	push @messages, $msgName;
       
   415 	return \$messages[$#messages];
       
   416 	}
       
   417 
       
   418 ###################
       
   419 # Object routines
       
   420 ###################
       
   421 
       
   422 sub readSequences()
       
   423 {
       
   424 	my $line = 0;
       
   425 	while (<>) {
       
   426 		die unless s/^(\w+)\s+//;
       
   427 		my $action = $1;
       
   428 		if ($action eq "t")
       
   429 			{
       
   430 			# Text
       
   431 			# "t <object> <text>"
       
   432 			# Place text item on the timeline of a particular object
       
   433 			my $pos;
       
   434 			if (s/^([^\s]*)\s+//) { $pos = $1; }
       
   435 			my $objRef = findOrCreateObject($pos);
       
   436 			chomp;
       
   437 			my $text = $_;
       
   438 			push @sequences, { Action => $action, Object => $objRef, Text => $text };
       
   439 			}
       
   440 		elsif ($action eq "oc")
       
   441 			{
       
   442 			# Object Create
       
   443 			# oc <object name> <display order> <addr>
       
   444 			split;
       
   445 			my $objName = shift @_;
       
   446 			my $order = shift @_;
       
   447 
       
   448 			my $objRef = findOrCreateObject($objName);
       
   449 			$objRef->{Order} = $order;
       
   450 			if ($order < $minOrder)
       
   451 				{
       
   452 				$minOrder = $order;
       
   453 				}
       
   454 			if ($order > $maxOrder)
       
   455 				{
       
   456 				$maxOrder = $order;
       
   457 				}
       
   458 			chomp;
       
   459 			push @sequences, { Action => $action, Object => $objRef };
       
   460 			}
       
   461 		elsif ($action eq "od")
       
   462 			{
       
   463 			# Object Destroyed
       
   464 			# od <object name> <addr>
       
   465 			split;
       
   466 			my $objName = shift @_;
       
   467 			my $objRef = findOrCreateObject($objName);
       
   468 			chomp;
       
   469 			push @sequences, { Action => $action, Object => $objRef };
       
   470 			}
       
   471 		elsif ($action eq "ac")
       
   472 			{
       
   473 			# Activity Created
       
   474 			# ac <object name> <activity addr> <activity name>
       
   475 			split;
       
   476 			my $objRef = findObjectByName(shift @_);
       
   477 			if ($objRef != 0)
       
   478 				{
       
   479 				my $addr = shift @_;
       
   480 				my $name = shift @_;
       
   481 				push @sequences, { Action => $action, Object => $objRef, Addr => $addr, Name => $name };
       
   482 				}
       
   483 			chomp;
       
   484 			}
       
   485 		elsif ($action eq "ad")
       
   486 			{
       
   487 			# Activity Destroyed
       
   488 			# ad <addr>
       
   489 			split;
       
   490 			my $addr = shift @_;
       
   491 			chomp;
       
   492 			push @sequences, { Action => $action, Addr => $addr };
       
   493 			}
       
   494 		elsif ($action eq "p" || $action eq "r" || $action eq "sc")
       
   495 			{
       
   496 			# Post/Receive/SynchronousCall
       
   497 			# [P|R|SC] <message> <source object> <destination object>
       
   498 			my $msg;
       
   499 			if (/^"/)
       
   500 				{
       
   501 				s/^"([^"]+)"\s+//;
       
   502 				$msg = $1;
       
   503 				}
       
   504 			else
       
   505 				{
       
   506 				s/^(\S+)\s+//;
       
   507 				$msg = $1;
       
   508 				}
       
   509 			my $msgRef = findOrCreateMessage($msg);
       
   510 			split;
       
   511 			my $srcRef = findOrCreateObject(shift @_);
       
   512 			my $destRef = findOrCreateObject(shift @_);
       
   513 			$srcRef->{MsgCount}++;
       
   514 			$destRef->{MsgCount}++;
       
   515 			if (!$opt_M) {
       
   516 				push @sequences, { Action => $action, Msg => $msgRef, SrcObj => $srcRef, DestObj => $destRef };
       
   517 				}
       
   518 			}
       
   519 		elsif ($action eq "l" || $action eq "pn" || $action eq "ts") {
       
   520 			# Label (on lefthand side - for time stamp)
       
   521 			# l <text>
       
   522 			#
       
   523 			# Popup - associated with [n]ext message
       
   524 			# pn <text>
       
   525 			chomp;
       
   526 			push @sequences, { Action => $action, Text => $_ };
       
   527 		}
       
   528 		++$line;
       
   529 		if ($opt_V) {
       
   530 			if ($line >= $sequenceProgressIncrement && ($line % $sequenceProgressIncrement == 0)) {
       
   531 				print "line $line\r";
       
   532 			}
       
   533 		}
       
   534 	}
       
   535 	if ($opt_V && $line >= $sequenceProgressIncrement) {
       
   536 		print "line $line (complete)\n";
       
   537 	}
       
   538 }
       
   539 
       
   540 sub findObjectByName()
       
   541 	{
       
   542 	my $objName = $_[0];
       
   543 	if (exists($objhash{$objName})) {
       
   544 	    return $objhash{$objName}
       
   545 	}
       
   546 	return 0;
       
   547 	}
       
   548 
       
   549 sub findOrCreateObject()
       
   550 	{
       
   551 	my $objName = $_[0];
       
   552 	my $obj = &findObjectByName($objName);
       
   553 	if ($obj != 0) {
       
   554 	    return $obj;
       
   555 	}
       
   556 	
       
   557 	push @objects, { Name => $objName, X => 0, Y => 0, MsgCount => 0, Order => 0, Creation => $objectUnknown, Deletion => $objectUnknown, Hide => 0 };
       
   558 
       
   559 	$obj = $objects[$#objects];
       
   560 	$objhash{$objName} = $obj;
       
   561 	
       
   562 	return $obj;
       
   563 	}
       
   564 
       
   565 sub printArray($$)
       
   566 	{
       
   567 	my ($title, $arrayRef) = @_;
       
   568 	print $title, ":\n";
       
   569 	foreach my $i (@$arrayRef)
       
   570 		{
       
   571 		print "$i\n";
       
   572 		}
       
   573 	}
       
   574 
       
   575 
       
   576 sub printHiddenObjects($)
       
   577 	{
       
   578 	print $_[0], ":\n";
       
   579 	foreach my $i (@objects)
       
   580 		{
       
   581 		if ($i->{Hide} == 1)
       
   582 			{
       
   583 			print $i->{Name}, "\n";	
       
   584 		}
       
   585 		}
       
   586 	}
       
   587 
       
   588 sub processRowArgument($)
       
   589 #
       
   590 # Process the "-r <begin>,<end>" argument.  If either <begin> and/or <end> are missing,
       
   591 # then assume that the beginning and ending row are the first and last row respectively.
       
   592 #
       
   593 # Input argument:	Total number of rows in sequence
       
   594 # Outputs:			Beginning and ending row to display (in $beginRow, $endRow)
       
   595 # Return value:		Total number of rows to display
       
   596 	{
       
   597 	my $totalRows = $_[0];
       
   598 	die unless $totalRows;
       
   599 
       
   600 	if (! $opt_r) {
       
   601 		$opt_r = "$maxRowsToDisplay,";
       
   602 	}
       
   603 		
       
   604 	$opt_r =~ m/(-?[0-9]*),(-?[0-9]*)/;
       
   605 		
       
   606 	if (! $1)
       
   607 		{
       
   608 		$beginRow = 0;
       
   609 		}
       
   610 	else
       
   611 		{
       
   612 		$beginRow = $1;
       
   613 		if ($beginRow < 0) {
       
   614 			$beginRow += $totalRows;
       
   615 			if ($beginRow < 0) {
       
   616 				$beginRow = 0;
       
   617 				}
       
   618 			}
       
   619 		}
       
   620 	if (! $2)
       
   621 		{
       
   622 		$endRow = $totalRows - 1;
       
   623 		}
       
   624 	else
       
   625 		{
       
   626 		$endRow = $2;
       
   627 		if ($endRow < 0) {
       
   628 			$endRow += $totalRows;
       
   629 			if ($endRow < 0) {
       
   630 				$endRow = 0;
       
   631 				}
       
   632 			}
       
   633 		}
       
   634 	
       
   635 	die "Invalid row range specification ($beginRow, $endRow)" if ($beginRow > $endRow);
       
   636 
       
   637 	print "Total rows = $totalRows\n";
       
   638 
       
   639 	my $newTotalRows = $endRow - $beginRow + 1;
       
   640 	if ($newTotalRows < $totalRows) {
       
   641 	        my $endRowText = ($endRow == $totalRows - 1) ? "the end (row $endRow)" : $endRow;
       
   642 		SetLimitedRowText("Only displaying $newTotalRows rows - row $beginRow to $endRowText");
       
   643 		print limitedRowText(), "\n";
       
   644 		return $newTotalRows;
       
   645 		}
       
   646 	else {
       
   647 		return $totalRows;
       
   648 		}
       
   649 	}
       
   650 
       
   651 sub SetLimitedRowText($)
       
   652 	{
       
   653 	$limitedRowText = $_[0];
       
   654 	}
       
   655 	
       
   656 sub limitedRowText()
       
   657 	{
       
   658 	return $limitedRowText;
       
   659 	}
       
   660 
       
   661 #
       
   662 # Set X position for each object.  Return final screen width.
       
   663 #
       
   664 
       
   665 sub calculateObjectColumnPositions()
       
   666 	{
       
   667 	# loop through each order
       
   668 	my $x = $leftMargin;
       
   669 	my $colno = 1;
       
   670 	my $order;
       
   671 	
       
   672 	#
       
   673 	# Objects can either be placed by sorting left-to-right by type, or else
       
   674 	# placing objects as they are created.
       
   675 	#
       
   676 	
       
   677 	if ($sortObjectsFlag == 1)
       
   678 		{
       
   679 		for ($order = 0 ; $order <= $maxOrder ; $order++)
       
   680 			{
       
   681 			foreach my $obj (@objects)
       
   682 				{
       
   683 				if ($obj->{Hide} == 1)
       
   684 						{
       
   685 						next;
       
   686 						}
       
   687 				if ($obj->{Order} == $order)
       
   688 					{
       
   689 					$obj->{X} = $x;
       
   690 					$x += $objectSpacing;
       
   691 
       
   692 					$obj->{colno} = $colno;
       
   693 					$colno++;
       
   694 
       
   695 # print "$obj->{Name}, order $obj->{Order}, X $obj->{X}\n";
       
   696 					}
       
   697 				}
       
   698 			}
       
   699 		}
       
   700 	else
       
   701 		{
       
   702 		foreach my $obj (@objects)
       
   703 			{
       
   704 			if ($obj->{Hide} == 1)
       
   705 				{
       
   706 				next;
       
   707 				}
       
   708 # print "$obj->{Name}, order $obj->{Order}, X $obj->{X}\n";
       
   709 			$obj->{X} = $x;
       
   710 			$x += $objectSpacing;
       
   711 
       
   712 			$obj->{colno} = $colno;
       
   713 			$colno++;
       
   714 			}
       
   715 		}
       
   716 	return $x;	
       
   717 	}
       
   718 
       
   719 sub hideObjectsOutsideOfView($$)
       
   720 	{
       
   721 	my ($beginRow, $endRow) = @_;
       
   722 
       
   723 	my $obj;
       
   724 	foreach my $obj (@objects) {
       
   725 		# Don't need this as we now track process creation/destruction properly
       
   726 		#if ($obj->{Name} =~ /^!/)
       
   727 		#	{
       
   728 		#	next;
       
   729 		#	}
       
   730 
       
   731 		if (IsCrossingRange($beginRow, $endRow, $obj->{Creation}, $obj->{Deletion}) != 1)
       
   732 			{
       
   733 			$obj->{Hide} = 1;
       
   734 			}
       
   735 		}
       
   736 	}
       
   737 
       
   738 sub unhideAllObjects()
       
   739 	{
       
   740 	foreach my $obj (@objects) {
       
   741 		$obj->{Hide} = 0;
       
   742 		}
       
   743 	}
       
   744 
       
   745 sub trimDownToActivity()
       
   746 {
       
   747     my @activities = undef;
       
   748     my $i = 0;
       
   749     
       
   750     for ($i = 0; $i < $#sequences; $i++)
       
   751     {
       
   752 	my $s = $sequences[$i];
       
   753 
       
   754 	if ($s->{Action} eq "ac" && ($opt_N eq "" || $s->{Object}{Name} =~ /$opt_N/i) && ($opt_A eq "" || $s->{Name} =~ /$opt_A/i))
       
   755 	{
       
   756 	    push @activities, { Index => $i, Addr => $s->{Addr} };
       
   757 	    my $ai = $#activities;
       
   758 	    print "[$ai] ".$s->{Object}{Name}." :: ".$s->{Name}."\n";
       
   759 	}
       
   760     }
       
   761     print "Select activity to draw: ";
       
   762     my $selected = <STDIN>;
       
   763     chomp($selected);
       
   764     $selected += 0;
       
   765 
       
   766 
       
   767     my $adfound = 0;
       
   768 
       
   769     my $selectedact = $activities[$selected];
       
   770     my $selectedseq = $sequences[$selectedact->{Index}];
       
   771     
       
   772     my %usednodes = ();
       
   773     $usednodes{$selectedseq->{Object}} = 1;
       
   774     $selectedseq->{Object}{MsgCount}++;
       
   775     for ($i = 0; $i < $#sequences ; $i++)
       
   776     {
       
   777 	if ($i >= $selectedact->{Index} && $adfound == 0)
       
   778 	{
       
   779 	    my $s = $sequences[$i];
       
   780 	    my $action = $s->{Action};
       
   781 	    if ($action eq "p" || $action eq "r" || $action eq "sc")
       
   782 	    {
       
   783 		if ($usednodes{$s->{SrcObj}} == 1)
       
   784 		{
       
   785 		    $usednodes{$s->{DestObj}} = 1;
       
   786 		}
       
   787 		else
       
   788 		{
       
   789 		    $s->{KillMe} = 1;
       
   790 		}
       
   791 	    }
       
   792 	    if ($action eq "ad" && $s->{Addr} eq $selectedact->{Addr})
       
   793 	    {
       
   794 		$adfound = 1;
       
   795 	    }
       
   796 	}
       
   797 	else
       
   798 	{
       
   799 	    # remove message posts
       
   800 	    my $action = $sequences[$i]->{Action};
       
   801 	    if ($action eq "p" || $action eq "r" || $action eq "sc" || $action eq "t" || $action eq "ac" || $action eq "ad")
       
   802 	    {
       
   803 		$sequences[$i]->{KillMe} = 1;
       
   804 	    }
       
   805 	}
       
   806     }
       
   807 
       
   808     for ($i = $#sequences; $i >= 0; $i--)
       
   809     {
       
   810 	if ($sequences[$i]->{KillMe} == 1)
       
   811 	{
       
   812 	    my $action = $sequences[$i]->{Action};
       
   813 	    if ($action eq "p" || $action eq "r" || $action eq "sc")
       
   814 	    {
       
   815 		$sequences[$i]->{SrcObj}{MsgCount}--;
       
   816 		$sequences[$i]->{DestObj}{MsgCount}--;
       
   817 	    }
       
   818 	    splice @sequences, $i, 1;
       
   819 	}
       
   820     }
       
   821 
       
   822 }
       
   823 
       
   824 sub trimSilentObjects()
       
   825 	{
       
   826 	# get rid of objects that didn't end up with any messages sent to/from them.
       
   827 	# Exceptions are objects beginning with "!", which are forced to be displayed (for example,
       
   828 	# exe timelines displaying socket/host resolver operations).
       
   829 	my $i;
       
   830 	my %trimObjects = ();
       
   831 	# NOTE: in the following "for" loop, "i" is incremented in the body.  Beware if using "next" etc.
       
   832 	#
       
   833 
       
   834 	my $objectCount = scalar(@objects);
       
   835 	my $percentIncrement = 0;
       
   836 
       
   837 	if ($opt_V && $objectCount > $trimProgressTrigger) {
       
   838 		$percentIncrement = $objectCount/$trimProgressPercentIncrement;
       
   839 	}
       
   840 
       
   841 	for ($i = 0 ; $i <  $objectCount ; ) {	# we are splicing @objects in the loop, so do we need to recalculate scalar() every time?
       
   842 		my $objectName = $objects[$i]->{Name};
       
   843 		if ($objectName =~ /^!/)
       
   844 			{
       
   845 			++$i;
       
   846 			next;
       
   847 			}
       
   848 
       
   849 		$objectName =~ s/[0-9]+$//;
       
   850 		if ($dontHideShortName{$objectName})
       
   851 			{
       
   852 			++$i;
       
   853 			next;
       
   854 			}
       
   855 
       
   856 		if ($objects[$i]->{MsgCount} == 0)
       
   857 			{
       
   858 			push @silentObjectsTrimmed, $objects[$i]->{Name} if ($opt_v);
       
   859 
       
   860 			$trimObjects{$objects[$i]} = 1;
       
   861 			$objects[$i]->{KillMe} = 1;
       
   862 #			deleteObject($i);
       
   863 			$objectCount = scalar(@objects);		# is this needed given we aren't incrementing i?
       
   864 			$percentIncrement = $objectCount/$trimProgressPercentIncrement if ($percentIncrement);
       
   865 			# Don't increment "i" here
       
   866 			++$i;
       
   867 			}
       
   868 		else
       
   869 			{
       
   870 			++$i;
       
   871 			}
       
   872 		if ($percentIncrement && $i > $percentIncrement && ($i % $percentIncrement) == 0) {
       
   873 			print int($i * 100 / $objectCount), "% complete\r";
       
   874 			}
       
   875 		}
       
   876 
       
   877 	for (my $i = 0; $i < scalar(@sequences); $i++) {
       
   878 	    my $action = $sequences[$i]->{Action};
       
   879 	    my $obj = $sequences[$i]->{Object};
       
   880 	    if ( ($action eq "t" || $action eq "oc") && exists($trimObjects{$obj})) {
       
   881 		$sequences[$i]->{KillMe} = 1;
       
   882 	    } 
       
   883 	}
       
   884 
       
   885 	for (my $i = $#sequences; $i >= 0; $i--) {
       
   886 	    if ($sequences[$i]->{KillMe} == 1) {
       
   887 		splice @sequences, $i, 1;
       
   888 	    }
       
   889 	}
       
   890 
       
   891 	for (my $i = $#objects; $i >= 0; $i--) {
       
   892 	    if ($objects[$i]->{KillMe} == 1) {
       
   893 		splice @objects, $i, 1;
       
   894 	    }
       
   895 	}
       
   896 
       
   897 	print "100% complete\n" if ($percentIncrement);		# last percentage printed in loop may be, say, "88%" so make it look 100% complete
       
   898 	}
       
   899 
       
   900 sub deleteObject($)
       
   901 	{
       
   902 	my ($i) = @_;			# object index to delete
       
   903 	my $j;
       
   904 
       
   905 	for ($j = 0 ; $j < scalar(@sequences) ; )
       
   906 		{
       
   907 		my $action = $sequences[$j]->{Action};
       
   908 		if ( (($action eq "t") || ($action eq "oc")) &&
       
   909 		     ($sequences[$j]->{Object} == $objects[$i]) )
       
   910 			{
       
   911 			splice @sequences, $j, 1;
       
   912 			}
       
   913 		else
       
   914 			{
       
   915 			++$j;
       
   916 			}
       
   917 		}
       
   918 		splice @objects, $i, 1;
       
   919 	}
       
   920 
       
   921 sub drawObjectNames($)
       
   922 	{
       
   923 	my ($repeatedNameFlag) = @_;
       
   924 	my $i;
       
   925 	my $colour;
       
   926 	foreach $i (@objects) {
       
   927 		if ($i->{Hide} == 1)
       
   928 			{
       
   929 			next;
       
   930 			}
       
   931 		my $colour;
       
   932 		if ($repeatedNameFlag == 1)
       
   933 			{
       
   934 			if ($i->{Order} == 0)
       
   935 				{
       
   936 				$colour = "!silver";
       
   937 				}
       
   938 			else
       
   939 				{
       
   940 				$colour = (($i->{Order} % 2) == 0) ? $objectNameLightColour1 : $objectNameLightColour2;
       
   941 				$colour = "!" . $colour;
       
   942 				}
       
   943 
       
   944 			}
       
   945 		else
       
   946 			{
       
   947 			if ($i->{Order} == 0)
       
   948 				{
       
   949 				$colour = "";
       
   950 				}
       
   951 			else
       
   952 				{
       
   953 				$colour = (($i->{Order} % 2) == 0) ? $objectNameColour1 : $objectNameColour2;
       
   954 				}
       
   955 			}
       
   956 		my $name = $i->{Name};
       
   957 		$name =~ s/^!//;
       
   958 		# **** REMOVE THIS HACK for debugEvent() ****
       
   959 		if ($opt_n)
       
   960 		    {
       
   961 		    my $x = $i->{X} + $rotatedTextOffset;
       
   962 		    my $transform="rotate(90, $x, $currentY)";
       
   963 			# If node names are displayed vertically, then the text matt which creates a break in the
       
   964 			# node lifeline for the node name text is not required (a "!" in front of the colour name).
       
   965 			$colour =~ s/^!//;
       
   966 		    outputText($x + $rotatedTextOffset, $name, $currentY, "begin", $colour, qq{id="$name" onclick="debugEvent(evt)" transform="$transform" font-size="12" font-family="sans-serif"});
       
   967 		    }
       
   968 		else
       
   969 		    {
       
   970 		    outputText($i->{X}, $name, $currentY, "middle", $colour, qq{id="$name" onclick="debugEvent(evt)"});
       
   971 		    }
       
   972 		} 
       
   973 	incrementY();
       
   974 	}
       
   975 
       
   976 #
       
   977 # Test whether a value is within a certain (start,end] range inclusive.
       
   978 #
       
   979 # Arguments: value, start, end
       
   980 #
       
   981 # If start and/or end are -1, then this is a "wildcard" match 
       
   982 #
       
   983 
       
   984 sub IsInRangeInclusive($$$)
       
   985 	{
       
   986 	my ($value, $start, $end) = @_;
       
   987 	return ($start == -1 || $value >= $start) && ($end == -1 || $value <= $end);
       
   988 	}
       
   989 
       
   990 #
       
   991 # Test whether one range crosses another.
       
   992 #
       
   993 # if $start and/or $end are $objectUnknown, this means 1 and infinity respectively.
       
   994 #
       
   995 
       
   996 sub IsCrossingRange($$$$)
       
   997 	{
       
   998 	my ($startRange, $endRange, $start, $end) = @_;
       
   999 	if ($start == $objectUnknown)
       
  1000 		{
       
  1001 		return ($end == $objectUnknown || $end >= $startRange);
       
  1002 		}
       
  1003 	elsif ($end == $objectUnknown)
       
  1004 		{
       
  1005 		return ($start == $objectUnknown || $start <= $endRange);
       
  1006 		}
       
  1007 	else
       
  1008 		{
       
  1009 		return ($start <= $endRange && $end >= $startRange);
       
  1010 		}
       
  1011 	}
       
  1012 
       
  1013 #
       
  1014 # Activity related routines
       
  1015 #
       
  1016 
       
  1017 # Create an activity hash associated with the object in question.  Parameters:
       
  1018 # <object reference>		Reference to the object
       
  1019 # <activity address>		Unique activity address
       
  1020 # Returns: reference to the new activity hash.
       
  1021 #
       
  1022 sub	createObjectActivity($$)
       
  1023 	{
       
  1024 	my ($objRef, $addr) = @_;
       
  1025 	my $actRef;
       
  1026 	if (($actRef = findObjectActivity($objRef, $addr)) != 0)
       
  1027 		{
       
  1028 		print "Duplicate activity creation - $addr\n";
       
  1029 		return 0;
       
  1030 		}
       
  1031 	else 
       
  1032 		{
       
  1033 		my $newActRef = { Addr => $addr, End => -1 };
       
  1034 		push @{$objRef->{Activities}}, $newActRef;
       
  1035 		return $newActRef; 
       
  1036 		}
       
  1037 	}
       
  1038 
       
  1039 # Find an existing activity hash associated with the object in question.  Parameters:
       
  1040 # <object reference>		Reference to the object
       
  1041 # <activity address>		Unique activity address (string)
       
  1042 # Returns: reference to the new activity hash.
       
  1043 #
       
  1044 
       
  1045 sub	findObjectActivity($$)
       
  1046 	{
       
  1047 	my ($objRef, $addr) = @_;
       
  1048 	
       
  1049 	if (defined($objRef->{Activities}))
       
  1050 		{
       
  1051 		my $actRef;
       
  1052 		foreach $actRef (@{$objRef->{Activities}})
       
  1053 			{
       
  1054 			if ($actRef->{Addr} eq $addr)
       
  1055 				{
       
  1056 				return $actRef;
       
  1057 				}
       
  1058 			}
       
  1059 		}
       
  1060 	return 0;
       
  1061 	}
       
  1062 
       
  1063 # Find the activity and object associated with an activity address.  Parameters:
       
  1064 # <activity address>				Unique activity address (string)
       
  1065 # <object reference reference>		Reference to a variable that will receive the object reference (out)
       
  1066 # Returns: reference to the activity.
       
  1067 
       
  1068 sub findActivity($$)
       
  1069 	{
       
  1070 	my ($addr, $returnObj) = @_;
       
  1071 	my $objRef;
       
  1072 	my $actRef;
       
  1073 	foreach $objRef (@objects)
       
  1074 		{
       
  1075 		if (defined($objRef->{Activities}))
       
  1076 			{
       
  1077 			$actRef = findObjectActivity($objRef, $addr);
       
  1078 			if ($actRef != 0)
       
  1079 				{
       
  1080 				${$returnObj} = $objRef;
       
  1081 				return $actRef;
       
  1082 				}
       
  1083 			}
       
  1084 		}
       
  1085 	return 0;
       
  1086 	}
       
  1087 
       
  1088 # Allocate a free vertical activity line associated with the object.
       
  1089 # Returns: vertical activity line (number starting from 1).
       
  1090 sub allocateObjectActivityLine($)
       
  1091 	{
       
  1092 	my ($objRef) = @_;
       
  1093 	if (! defined ($objRef->{LineMap}))
       
  1094 		{
       
  1095 		# LineMap is actually a bitmap.  Allocate first line.
       
  1096 		$objRef->{LineMap} = 1;
       
  1097 		return 1;
       
  1098 		}
       
  1099 	else {
       
  1100 		my $lineMap = $objRef->{LineMap};
       
  1101 		my $freeBit = ~($lineMap) & ($lineMap + 1);
       
  1102 		die "Zero allocated bit - $lineMap\n" if ($freeBit == 0);
       
  1103 		$objRef->{LineMap} |= $freeBit;
       
  1104 		my $bitNumber = 1;
       
  1105 		while ($freeBit >>= 1)
       
  1106 			{
       
  1107 			++$bitNumber;
       
  1108 			}
       
  1109 		return $bitNumber;
       
  1110 		}
       
  1111 	}
       
  1112 
       
  1113 sub freeObjectActivityLine($$)
       
  1114 	{
       
  1115 	my ($objRef,$line) = @_;
       
  1116 	die "Freeing undefined/empty LineMap\n" if (!defined($objRef->{LineMap}) || $objRef->{LineMap} == 0);
       
  1117 	$objRef->{LineMap} &= ~(1 << ($line-1));
       
  1118 	}
       
  1119 
       
  1120 sub closeActivity($$$)
       
  1121 	{
       
  1122 	my ($actRef, $objRef, $endRow) = @_;
       
  1123 	$actRef->{End} = $endRow;
       
  1124 	$actRef->{Addr} = -1;		# address can be re-used, so have to clear it
       
  1125 	freeObjectActivityLine($objRef, $actRef->{Line});
       
  1126 	}
       
  1127 
       
  1128 #
       
  1129 # Draw message sequences
       
  1130 #
       
  1131 # Args: drawFlag  1 = do draw, 0 = don't draw
       
  1132 # Return: 	number of rows in diagram for sequences
       
  1133 #
       
  1134 # ToDo:
       
  1135 #	Partitioning: how do popups work ?
       
  1136 #
       
  1137 
       
  1138 sub drawSequences($$$$$$)
       
  1139 	{
       
  1140 	my ($startRow, $endRow, $drawFlag, $labelsOnPageRef, $objectsCreatedRef, $objectsDestroyedRef) = @_;
       
  1141 	my $nextLabel = "";
       
  1142 
       
  1143 	# startRow/endRow = start/end row of displayed area (inclusive)
       
  1144 	# absoluteRow	  = increments through all rows that have an object to display, whether in displayed area or not.
       
  1145 	# rows			  = Increments for each row that contains an object to display, but only when $absoluteRow moves
       
  1146 	#					into the displayed area.
       
  1147 
       
  1148 	my $rows = 0;
       
  1149 	my $absoluteRow = 0;
       
  1150 	my $drawDone = 0;
       
  1151 	my $inRange = 0;
       
  1152 	my $lastPopupText = "";		# text of last "pn" action
       
  1153 
       
  1154 	foreach my $ref (@sequences) {
       
  1155 		my $action = $ref->{Action};
       
  1156 
       
  1157 		$inRange = IsInRangeInclusive($absoluteRow, $startRow, $endRow);
       
  1158 		if ($action eq "t") {
       
  1159 			# text
       
  1160 			if ($inRange) {
       
  1161 				++$rows;
       
  1162 				if ($drawFlag == 1) {
       
  1163 					my $objX = $ref->{Object}->{X};
       
  1164 					my $text = $ref->{Text};
       
  1165 					# Text matt no longer required because "t" primitives are not drawn centred on the node lifeline anymore.
       
  1166 					# my $colour = "!";
       
  1167 					my $colour = "";
       
  1168 					if ($text =~ /\s*\{(\d+,\d+,\d+)\}\s*(.+)/) {
       
  1169 						$colour .= "rgb($1)";
       
  1170 						$text = $2;
       
  1171 					}
       
  1172 					my $attrs = generatePopupAttrs(\$lastPopupText, qq{id="$ref->{Object}->{Name}"});
       
  1173 					outputText($objX + $lifelineTextLeftMargin, $text, $currentY, "begin", $colour, $attrs);
       
  1174 					$drawDone = 1;
       
  1175 					# support for putting threads into the view map
       
  1176 					$text = $ref->{Text};
       
  1177 					if ($text =~ m/^\(Thread "(.*)" (\w+)\)$/) {
       
  1178 						if ($2 eq "Created") {
       
  1179 							push @{$objectsCreatedRef}, $1;
       
  1180 						} elsif ($2 eq "Destroyed") {
       
  1181 							push @{$objectsDestroyedRef}, $1;
       
  1182 						}
       
  1183 					}
       
  1184 				} else {
       
  1185 					fakeUpCreationDeletionIfRequired($ref->{Object}, $absoluteRow);
       
  1186 					}
       
  1187 				}
       
  1188 			# displayable row, so increment absoluteRow.
       
  1189 			++$absoluteRow;
       
  1190 		} elsif ($action eq "ts") {
       
  1191 			# text
       
  1192 			if ($inRange) {
       
  1193 				++$rows;
       
  1194 				if ($drawFlag == 1) {
       
  1195 					my $text = $ref->{Text};
       
  1196 					outputTextSeperator($text, $currentY);
       
  1197 					$drawDone = 1;
       
  1198 					}
       
  1199 				}
       
  1200 			# displayable row, so increment absoluteRow.
       
  1201 			++$absoluteRow;
       
  1202 			}
       
  1203 		elsif ($action eq "oc") {
       
  1204 			# object created
       
  1205 
       
  1206 			# ensure that $objectCreation contains the very first creation point and
       
  1207 			# is not subsequently overwritten if we happen to come across another "oc".
       
  1208 			# This would only happen if an object was deleted and another object of
       
  1209 			# the same type was created with the same address, so it looks to us like the
       
  1210 			# same object being created/destroyed in several cycles.
       
  1211 
       
  1212 			if ($drawFlag == 0) {
       
  1213 				if ($ref->{Object}->{Creation} == $objectUnknown) {
       
  1214 					$ref->{Object}->{Creation} = $absoluteRow;
       
  1215 					}
       
  1216 				}
       
  1217 			else {
       
  1218 				if ($inRange) {
       
  1219 					push @{$objectsCreatedRef}, $ref->{Object};
       
  1220 					}
       
  1221 				}
       
  1222 			}
       
  1223 		elsif ($action eq "od")
       
  1224 			{
       
  1225 			# object destroyed
       
  1226 			#
       
  1227 			# Note: okay for objectDeletion to be overwritten by subsequent "od" actions such
       
  1228 			# that it is left containing the last "od" in the sequence for that object.  See comment
       
  1229 			# in "oc" about this circumstance.
       
  1230 
       
  1231 			if ($drawFlag == 0)
       
  1232 				{
       
  1233 				$ref->{Object}->{Deletion} = $absoluteRow;
       
  1234 				}
       
  1235 			else {
       
  1236 				if ($inRange) {
       
  1237 					push @{$objectsDestroyedRef}, $ref->{Object};
       
  1238 				}
       
  1239 			}
       
  1240 			}
       
  1241 		elsif ($action eq "l")
       
  1242 			{
       
  1243 			# left label
       
  1244 			if ($outputTimeDeltas && $nextLabel =~ m/^(\d\d):(\d\d):(\d\d)(,(\d+))?/)	# hh:mm:ss(dur)
       
  1245 				{
       
  1246 				use integer my $dur;
       
  1247 				my ($h1,$m1,$s1,$dur) = ($1,$2,$3,$5);
       
  1248 				if ($ref->{Text} =~ m/^(\d\d):(\d\d):(\d\d)/)
       
  1249 					{
       
  1250 					my ($h2,$m2,$s2) = ($1,$2,$3);
       
  1251 					$dur += ($h2 - $h1) * 3600 + ($m2 - $m1) * 60 + ($s2 - $s1);
       
  1252 					$nextLabel = $h2 . ":" . $m2 . ":" . $s2 . "," . $dur;
       
  1253 					}
       
  1254 				}
       
  1255 			else
       
  1256 				{
       
  1257 				$nextLabel = $ref->{Text};
       
  1258 				}
       
  1259 			if ($drawFlag == 1 && $inRange) {
       
  1260 				push @{$labelsOnPageRef}, $nextLabel;
       
  1261 				}
       
  1262 			}
       
  1263 		elsif ($action eq "pn")
       
  1264 			{
       
  1265 			# popup
       
  1266 			if ($drawFlag == 1 && $opt_p && $inRange)
       
  1267 				{
       
  1268 				$lastPopupText = $ref->{Text};
       
  1269 				# not setting $drawDone=1 because popups do not occupy a row
       
  1270 				}
       
  1271 			}
       
  1272 		elsif ($action eq "ac")
       
  1273 			{
       
  1274 			# Activity Created
       
  1275 			# (does not occupy a row, hence no need to set $drawDone)
       
  1276 			# - add activity to list of activities in object and set start row attribute
       
  1277 			# - determine free vertical position to draw activity line
       
  1278 			if ($drawFlag == 0)			# just do it once on first invocation of drawSequences()
       
  1279 				{
       
  1280 				my $actRef = findObjectActivity($ref->{Object}, $ref->{Addr});
       
  1281 				if ($actRef != 0)
       
  1282 					{
       
  1283 					# We have encountered a duplicate "ac" without an intervening "ad".
       
  1284 					# In other words, we've come across two "activity create" tags with the
       
  1285 					# same memory address <xxx> without an intervening "activity delete" tag
       
  1286 					# closing off the first <xxx> memory address.  The same <xxx> memory address
       
  1287 					# has been re-used.  Mark the first of the activities as having ended here
       
  1288 					# (at the point where the second activity starts) and mark its activity line
       
  1289 					# in red - it's the best we can do.
       
  1290 					$actRef->{EndUnknown} = 1;
       
  1291 					closeActivity($actRef, $ref->{Object}, $absoluteRow);
       
  1292 					}
       
  1293 
       
  1294 				my $newActRef = createObjectActivity($ref->{Object}, $ref->{Addr});
       
  1295 				$newActRef->{Start} = $absoluteRow;
       
  1296 				$newActRef->{Line} = allocateObjectActivityLine($ref->{Object});
       
  1297 				$newActRef->{Name} = $ref->{Name};
       
  1298 				}
       
  1299 			}
       
  1300 		elsif ($action eq "ad")
       
  1301 			{
       
  1302 			# Activity Destroyed
       
  1303 			# (does not occupy a row, hence no need to set $drawDone)
       
  1304 			# - find corresponding activity and set end row attribute
       
  1305 			# - update our list of free vertical activity lines associated with the object
       
  1306 			if ($drawFlag == 0)			# just do it once on first invocation of drawSequences()
       
  1307 				{
       
  1308 				my $objRef;
       
  1309 				my $actRef = findActivity($ref->{Addr}, \$objRef);
       
  1310 				if ($actRef != 0)
       
  1311 					{
       
  1312 					closeActivity($actRef, $objRef, $absoluteRow);
       
  1313 					}
       
  1314 				else
       
  1315 					{
       
  1316 #					print "Destroyed activity not found - $ref->{Addr}\n";
       
  1317 					}
       
  1318 				}
       
  1319 			}
       
  1320 		else
       
  1321 			{
       
  1322 			# post or receive
       
  1323 			if ($inRange)
       
  1324 				{
       
  1325 				++$rows;
       
  1326 				if ($drawFlag == 1)
       
  1327 					{
       
  1328 					my $msg = ${$ref->{Msg}};
       
  1329 					my $srcX = $ref->{SrcObj}->{X};
       
  1330 					my $destX = $ref->{DestObj}->{X};
       
  1331 					my $align;
       
  1332 					my $colour;
       
  1333 					my $endType;
       
  1334 					if ($action eq "p") {
       
  1335 						$align = "tail";
       
  1336 						$colour = $postColour;
       
  1337 						$endType = "postArr";
       
  1338 						}
       
  1339 					elsif ($action eq "r") {
       
  1340 						$align = "head";
       
  1341 						$colour = $receiveColour;
       
  1342 						$endType = "recvArr";
       
  1343 						}
       
  1344 					elsif ($action eq "sc") {
       
  1345 						$align = "head";
       
  1346 						$colour = $receiveColour;
       
  1347 						$endType = "callArr";
       
  1348 						}
       
  1349 					else {
       
  1350 						die("Unknown action");
       
  1351 						}
       
  1352 					my $lineName = $ref->{SrcObj}->{Name} . "_" . $ref->{DestObj}->{Name};
       
  1353 					my $attrs = generatePopupAttrs(\$lastPopupText, qq{id="$lineName"});
       
  1354 					outputLabelledLine($srcX, $destX, $msg, $colour, $endType, $align, $attrs);
       
  1355 
       
  1356 					#print RTTTL ($ref->{SrcObj}->{colno}.",".$ref->{DestObj}->{colno}."\n");
       
  1357 					$drawDone = 1;
       
  1358 					}
       
  1359 				else
       
  1360 					{
       
  1361 					# If the object has no creation point specified, just take the first message as the
       
  1362 					# creation point.  This is to ensure that such objects (generally with hex numbers as object names
       
  1363 					# due to fragmental logs) do not appear on every single SVG page.
       
  1364 					fakeUpCreationDeletionIfRequired($ref->{SrcObj}, $absoluteRow);
       
  1365 					fakeUpCreationDeletionIfRequired($ref->{DestObj}, $absoluteRow);
       
  1366 					}
       
  1367 				}
       
  1368 			++$absoluteRow;
       
  1369 			}
       
  1370 
       
  1371 		if ($drawDone == 1) {
       
  1372 			if ($nextLabel)
       
  1373 				{
       
  1374 				outputLeftLabel($nextLabel);
       
  1375 				$nextLabel = "";
       
  1376 				}
       
  1377 			incrementY();
       
  1378 			$drawDone = 0;
       
  1379 			}
       
  1380 
       
  1381 		if ($absoluteRow != 0 && (($absoluteRow) % $objectNameRepeatCount) == 0)
       
  1382 			{
       
  1383 			# Note: have (possibly) incremented absoluteRow above, so re-calculate $inRange
       
  1384 			$inRange = IsInRangeInclusive($absoluteRow, $startRow, $endRow);
       
  1385 			if ($inRange)
       
  1386 				{
       
  1387 				# drawObjectNames() increments Y once, and we increment Y once for
       
  1388 				# spacing, so we occupy two rows here.
       
  1389 				$rows += 2;
       
  1390 				if ($drawFlag == 1)
       
  1391 					{
       
  1392 					drawObjectNames(1);
       
  1393 					incrementY();
       
  1394 					}
       
  1395 				}
       
  1396 			$absoluteRow += 2;
       
  1397 			}
       
  1398 		}
       
  1399 	return $rows;
       
  1400 	}
       
  1401 
       
  1402 sub fakeUpCreationDeletionIfRequired($$)
       
  1403 	{
       
  1404 	my ($ref,$row) = @_;
       
  1405 	if ($ref->{Name} =~ /[0-9a-f]{8}/)
       
  1406 		{
       
  1407 		if ($ref->{Creation} == $objectUnknown)
       
  1408 			{
       
  1409 			$ref->{Creation} = $row;
       
  1410 			#print "Object $srcObjRef->{Name} fake creation $absoluteRow\n";
       
  1411 			}
       
  1412 		if ($ref->{Deletion} == $objectUnknown)
       
  1413 			{
       
  1414 			$ref->{FakeDeletion} = $row;
       
  1415 			#print "Object $ref->{SrcObj}->{Name} fake deletion $absoluteRow\n";
       
  1416 			}
       
  1417 		}
       
  1418 	}
       
  1419 	
       
  1420 sub fixObjectsWithUnknownCreationDeletion()
       
  1421 	{
       
  1422 	foreach my $ref (@objects) {
       
  1423 		if ($ref->{Hide} == 1) {
       
  1424 			next;
       
  1425 			}
       
  1426 		if ($ref->{Deletion} == $objectUnknown && $ref->{FakeDeletion}) {
       
  1427 			$ref->{Deletion} = $ref->{FakeDeletion};
       
  1428 			#print "Object $ref->{Name} fixing deletion to $ref->{FakeDeletion}\n";
       
  1429 			}
       
  1430 		}
       
  1431 	}
       
  1432 
       
  1433 #
       
  1434 # Generate SVG attributes suitable for an element with a popup.
       
  1435 #
       
  1436 # inputs:
       
  1437 # textRef	Reference to a string variable which, when non-null, indicates
       
  1438 #			that the attributes should additionally contain an onclick popup.
       
  1439 # attrs		Initial attribute values.
       
  1440 #
       
  1441 
       
  1442 sub generatePopupAttrs($$)
       
  1443 	{
       
  1444 	my ($textRef,$attrs) = @_;
       
  1445 	if ($opt_p && ${$textRef} ne "")
       
  1446 		{
       
  1447 		# non-null popup text, so append popup attribute
       
  1448 		$attrs .= qq{ onclick="popup(evt,'${$textRef}')"};
       
  1449 		# nullify the text - important not to leave behind stray popup text
       
  1450 		# that can inadvertantly be utilised in a later seq primitive that does
       
  1451 		# not specify a "pn" seq primitive.
       
  1452 		${$textRef} = "";
       
  1453 		}
       
  1454 	return $attrs;
       
  1455 	}
       
  1456 
       
  1457 sub relativeRowToY($)
       
  1458 	{
       
  1459 	return $topOffset + ($_[0] + 1) * $incrementY;
       
  1460 	}
       
  1461 
       
  1462 #
       
  1463 # Draw the vertical object lifelines between the object creation and destruction points
       
  1464 #
       
  1465 # main inputs: global $screenHeight
       
  1466 
       
  1467 # Partitioning: need to modify this to take into account the beginning and ending row of the partition being
       
  1468 # displayed.
       
  1469 
       
  1470 sub drawObjectLifelines($$$)
       
  1471 	{
       
  1472 	my ($startRow, $endRow, $screenHeight) = @_;
       
  1473 	my $x;
       
  1474 	my $lifelineBottomOfScreenY = $screenHeight - $topOffset + $lifelineBottomOverrun;
       
  1475 	my $afterLastRow = $endRow + 1;
       
  1476 	my $beforeFirstRow = $startRow - 1;
       
  1477 
       
  1478 #print "\ndrawObjectLifelines(startRow $startRow, endRow $endRow, screenHeight $screenHeight)\n";
       
  1479 
       
  1480 	foreach my $i (@objects)
       
  1481 		{
       
  1482 		if ($i->{Hide} == 1)
       
  1483 			{
       
  1484 			next;
       
  1485 			}
       
  1486 
       
  1487 		$x = $i->{X};
       
  1488 
       
  1489 		my $creationRow = $i->{Creation};
       
  1490 		my $deletionRow = $i->{Deletion};
       
  1491 			
       
  1492 #		print "Before: $i->{Name}, creationRow $creationRow, deletionRow $deletionRow\n";
       
  1493 
       
  1494 		if ($creationRow == $objectUnknown)
       
  1495 			{
       
  1496 			$creationRow = $beforeFirstRow;
       
  1497 			}
       
  1498 
       
  1499 		if ($deletionRow == $objectUnknown)
       
  1500 			{
       
  1501 			$deletionRow = $afterLastRow;
       
  1502 			}
       
  1503 
       
  1504 		my $deletionY;
       
  1505 		my $creationY;
       
  1506 
       
  1507 #		print "After: $i->{Name}, creationRow $creationRow, deletionRow $deletionRow\n";
       
  1508 
       
  1509 		my $idAttr = qq{id="$i->{Name}"};
       
  1510 		if ($deletionRow < $startRow || $creationRow > $endRow)
       
  1511 			{
       
  1512 			outputVerticalLine($x, $topOffset, $lifelineBottomOfScreenY, $deadColour, "");
       
  1513 			}
       
  1514 		elsif ($creationRow < $startRow && $deletionRow > $endRow)
       
  1515 			{
       
  1516 			outputVerticalLine($x, $topOffset, $lifelineBottomOfScreenY, $aliveColour, $idAttr);
       
  1517 			}
       
  1518 		elsif ($creationRow < $startRow && IsInRangeInclusive($deletionRow, $startRow, $endRow))
       
  1519 			{
       
  1520 #			$deletionY = $topOffset + ($deletionRow - $startRow + 1) * $incrementY;
       
  1521 			$deletionY = relativeRowToY($deletionRow - $startRow);
       
  1522 			outputVerticalLine($x, $topOffset, $deletionY, $aliveColour, $idAttr);
       
  1523 			outputVerticalLine($x, $deletionY, $lifelineBottomOfScreenY, $deadColour, "");
       
  1524 			}
       
  1525 		elsif ($deletionRow > $endRow && IsInRangeInclusive($creationRow, $startRow, $endRow))
       
  1526 			{
       
  1527 			$creationY = relativeRowToY($creationRow - $startRow);
       
  1528 #			$creationY = $topOffset + ($creationRow - $startRow + 1) * $incrementY;
       
  1529 			outputVerticalLine($x, $topOffset, $creationY, $deadColour, "");
       
  1530 			outputVerticalLine($x, $creationY, $lifelineBottomOfScreenY, $aliveColour, $idAttr);
       
  1531 			}
       
  1532 		else
       
  1533 			{
       
  1534 #			$creationY = $topOffset + ($creationRow - $startRow + 1) * $incrementY;
       
  1535 #			$deletionY = $topOffset + ($deletionRow - $startRow + 1) * $incrementY;
       
  1536 			$creationY = relativeRowToY($creationRow - $startRow);
       
  1537 			$deletionY = relativeRowToY($deletionRow - $startRow);
       
  1538 			outputVerticalLine($x, $topOffset, $creationY, $deadColour, "");
       
  1539 			outputVerticalLine($x, $creationY, $deletionY, $aliveColour, $idAttr);
       
  1540 			outputVerticalLine($x, $deletionY, $lifelineBottomOfScreenY, $deadColour, "");
       
  1541 			}
       
  1542 		}
       
  1543 	}
       
  1544 
       
  1545 sub drawActivities($$$$)
       
  1546 	{
       
  1547 	my ($startRow, $endRow, $screenHeight, $viewNumber) = @_;
       
  1548 #	print "drawActivities(startRow $startRow, endRow $endRow, screenHeight $screenHeight)\n";
       
  1549 	my $x;
       
  1550 	my $beforeFirstRow = $startRow - 1;
       
  1551 	my $afterLastRow = $endRow + 1;
       
  1552 	my $bottomOfScreenY = $screenHeight - $topOffset + $lifelineBottomOverrun;
       
  1553 	foreach my $obj (@objects)
       
  1554 		{
       
  1555 		if ($obj->{Hide} == 1)
       
  1556 			{
       
  1557 			next;
       
  1558 			}
       
  1559 
       
  1560 		if (defined ($obj->{Activities}))
       
  1561 			{
       
  1562 			foreach my $act (@{$obj->{Activities}})
       
  1563 				{
       
  1564 #				print "\t$obj->{Name} : ", $act->{Start}, " -> ", $act->{End}, ", $act->{Line}, $act->{Addr}\n";
       
  1565 				my $startAct = $act->{Start};
       
  1566 				my $endAct = $act->{End};
       
  1567 				my $startY;
       
  1568 				my $endY;
       
  1569 				my $startContinuation;
       
  1570 				my $endContinuation;
       
  1571 				
       
  1572 				# "EndUnknown" indicates that the activity endpoint is not known, so mark
       
  1573 				# it in a different colour to highlight this.  Note that {End} may still
       
  1574 				# be set in this case - it just means that is a hint as to the last possible place
       
  1575 				# where it could have been terminated, not necessarily that it actually terminated
       
  1576 				# at that point.  Also, {End} may be -1, which means we don't even have a hint
       
  1577 				# as to where it terminated, so just assume the node termination point.
       
  1578 				my $colour = $act->{EndUnknown} == 1 ? $activityEndUnknownColour : $activityColour;
       
  1579 				if ($endAct == -1)		# unknown end point
       
  1580 					{
       
  1581 					# don't know activity end (logging missing).  Trim it to the object destruction
       
  1582 					# point (if known).
       
  1583 					if ($obj->{Deletion} != $objectUnknown)
       
  1584 						{
       
  1585 						$endAct = $obj->{Deletion};
       
  1586 						}
       
  1587 					else
       
  1588 						{
       
  1589 						$endAct = $afterLastRow;
       
  1590 						}
       
  1591 					$colour = $activityEndUnknownColour;
       
  1592 					}
       
  1593 				if ($startAct > $endRow || $endAct < $startRow)
       
  1594 					{
       
  1595 					# not visible at all in this page
       
  1596 					next;
       
  1597 					}
       
  1598 				if ($startAct < $startRow)
       
  1599 					{
       
  1600 					$startContinuation = 1;
       
  1601 					$startY = $topOffset;
       
  1602 					}
       
  1603 				else
       
  1604 					{
       
  1605 					$startContinuation = 0;
       
  1606 					$startY = relativeRowToY($startAct - $startRow);
       
  1607 					}
       
  1608 					
       
  1609 				if ($endAct > $endRow) {
       
  1610 					$endContinuation = 1;
       
  1611 					$endY = $bottomOfScreenY;
       
  1612 					}
       
  1613 				else
       
  1614 					{
       
  1615 					$endContinuation = 0;
       
  1616 					$endY = relativeRowToY($endAct - $startRow);
       
  1617 					}
       
  1618 #				print "\t\tstartY = $startY, endY = $endY\n";
       
  1619 				$x = $obj->{X};
       
  1620 				my $activityX = $x + ($act->{Line} * $activitySpacing);
       
  1621 				my $topCornerSize = 5;
       
  1622 				my $endUpwardsOffset = 10;
       
  1623 				
       
  1624 				# For activities that span multiple pages, ensure the name of the activity pop up box (i.e. when you
       
  1625 				# click on the activity line) includes the page where the activity started (e.g. "PRStart (p11)").
       
  1626 				# Just do this for pages after the one where the activity starts.
       
  1627 				my $pages = "";
       
  1628 				if ($startContinuation == 0)			# note page where activity started
       
  1629 					{
       
  1630 					$act->{StartPage} = $viewNumber;
       
  1631 					}
       
  1632 				else
       
  1633 					{
       
  1634 					$pages = " (p$act->{StartPage})";
       
  1635 					}
       
  1636 		
       
  1637 				my $event = qq{ onclick="popup(evt, '$act->{Name}$pages')" };
       
  1638 				if ($startContinuation == 1 && $endContinuation == 1)
       
  1639 					{
       
  1640 					outputVerticalLine($activityX, $startY, $endY, $colour, $event);
       
  1641 					}
       
  1642 				elsif ($startContinuation == 1 && $endContinuation == 0)
       
  1643 					{
       
  1644 					$endY -= $endUpwardsOffset;
       
  1645 					outputVerticalLine($activityX, $startY, $endY - $topCornerSize, $colour, $event);
       
  1646 					outputLine($activityX, $endY - $topCornerSize, $x, $endY, $colour, "");
       
  1647 					}
       
  1648 				elsif ($startContinuation == 0 && $endContinuation == 1)
       
  1649 					{
       
  1650 					outputLine($x, $startY, $activityX, $startY + $topCornerSize, $colour, "");
       
  1651 					outputVerticalLine($activityX, $startY + $topCornerSize, $endY, $colour, $event);
       
  1652 					}
       
  1653 				else
       
  1654 					{
       
  1655 					# $startContinuation == 0 && $endContinuation == 0
       
  1656 					$endY -= $endUpwardsOffset;
       
  1657 					outputLine($x, $startY, $activityX, $startY + $topCornerSize, $colour, "");
       
  1658 					outputVerticalLine($activityX, $startY + $topCornerSize, $endY - $topCornerSize, $colour, $event);
       
  1659 					outputLine($activityX, $endY - $topCornerSize, $x, $endY, $colour, "");
       
  1660 					}
       
  1661 				}
       
  1662 			}
       
  1663 		}
       
  1664 	}
       
  1665 
       
  1666 sub readIniFile()
       
  1667 	{
       
  1668 	if ($opt_x) {
       
  1669 	        my $sectionName;
       
  1670 		open (INI, $opt_x) || die "Cannot open ini file $opt_x\n";
       
  1671 		while (<INI>) {
       
  1672 			chomp;
       
  1673 			# remove leading and trailing blanks and ignore blank lines
       
  1674 			s/^\s+//;
       
  1675 			s/\s+$//;
       
  1676 			if (! $_)
       
  1677 				{ next;	};
       
  1678 
       
  1679 			if (/^\[(.+)\]/)
       
  1680 				{
       
  1681 				# parse section name from "[<section>]" lines
       
  1682 				$sectionName = $1;
       
  1683 				}
       
  1684 			else
       
  1685 				{
       
  1686 				if ($sectionName eq "DontHideShortName")
       
  1687 					{
       
  1688 					$dontHideShortName{$_} = 1;
       
  1689 					}
       
  1690 				}
       
  1691 			}
       
  1692 		close INI;
       
  1693 		}
       
  1694 	}
       
  1695 
       
  1696 ##########################
       
  1697 # HTML Embedder routines #
       
  1698 ##########################
       
  1699 
       
  1700 sub createViewMap()
       
  1701 {
       
  1702 	open MAP, ">logmap.html" || die "Cannot open logmap.html for writing\n";
       
  1703 	print MAP "<html>\n<body>\n";
       
  1704 	
       
  1705 	# Begin page label/time table
       
  1706 	print MAP qq { <table border="1" style="text-align: center">\n };
       
  1707 	print MAP qq { <tr><th>Page</th><th colspan="2">Lines</th><th colspan="2">Times</th><th>Objects Created</th><th>Objects Destroyed</th></tr>\n };
       
  1708 }
       
  1709 
       
  1710 sub closeViewMap($)
       
  1711 {
       
  1712 	# End page label/time table
       
  1713 	print MAP "</table>\n<p>\n";
       
  1714 	
       
  1715 	# begin object page lifetime table
       
  1716 	my $pageCount = $_[0];
       
  1717 	print MAP qq{ <table border="1">\n };
       
  1718 	print MAP qq{ <tr><th colspan="4">Object Lifetimes per Page</th></tr><tr><th>Object</th><th colspan="2">Start/End</th><th>Lifetime</th></tr>\n };
       
  1719 	foreach my $obj (@objects) {
       
  1720 		print MAP qq { <tr style="font-family: courier new">\n };
       
  1721 		my $last;
       
  1722 		my $name = $obj->{Name};
       
  1723 		my $style = "";
       
  1724 		if ($name =~ s/^!//) {
       
  1725 			$style = "font-weight: bold";
       
  1726 		}
       
  1727 		if ($style) {
       
  1728 			print MAP qq {<td style="$style">$name</td>};
       
  1729 		} else {
       
  1730 			print MAP "<td>$name</td>";
       
  1731 		}
       
  1732 		if (!defined($obj->{LastPage})) {
       
  1733 			print MAP qq {<td colspan=\"2\">$obj->{FirstPage}</td>};
       
  1734 			$last = $obj->{FirstPage};
       
  1735 		} else {
       
  1736 			print MAP qq {<td>$obj->{FirstPage}</td><td>$obj->{LastPage}</td>};
       
  1737 			$last = $obj->{LastPage};
       
  1738 		}
       
  1739 		print MAP qq{ <td><table style="font-family: courier new" border="1" cellpadding="0" cellspacing="0"><tr>\n };
       
  1740 		for (my $i = 0 ;$i < $pageCount ; ++$i) {
       
  1741 			my $anchor = ($i != $pageCount - 1) ? "html/log$i.html" : "log.html";
       
  1742 			if ($i < $obj->{FirstPage} || $i > $last) {
       
  1743 				print MAP "<td>&nbsp;</td>";
       
  1744 			} else {
       
  1745 				print MAP qq{ <td style="background-color: blue"><a href=$anchor>&nbsp;</a></td> };
       
  1746 			}
       
  1747 		}
       
  1748 		print MAP "</tr></table></td>\n";
       
  1749 	}
       
  1750 	print MAP "</table>\n";
       
  1751 	print MAP "<body>\n<\html>\n";
       
  1752 	close MAP;
       
  1753 }
       
  1754 
       
  1755 sub outputViewMap($$$$$)
       
  1756 {
       
  1757 	my ($viewNumber, $lastViewNumber, $labelsOnPageRef, $objectsCreatedRef, $objectsDestroyedRef) = @_;
       
  1758 	my $firstLine;
       
  1759 	my $lastLine;
       
  1760 	my $firstTime;
       
  1761 	my $lastTime;
       
  1762 	foreach my $i (@{$labelsOnPageRef}) {
       
  1763 		if ($i =~ s/^#//) {
       
  1764 			if (! $firstLine) {
       
  1765 				$firstLine = $i;
       
  1766 			} else {
       
  1767 				$lastLine = $i;
       
  1768 			}
       
  1769 		} elsif ($i =~ m/^\d\d:\d\d:\d\d\.\d/) {
       
  1770 			if (! $firstTime) {
       
  1771 				$firstTime = $i;
       
  1772 			} else {
       
  1773 				$lastTime = $i;
       
  1774 			}
       
  1775 		}
       
  1776 	}
       
  1777 	$firstTime = "&nbsp;" if (!$firstTime);
       
  1778 	$firstLine = "&nbsp;" if (!$firstLine);
       
  1779 	$lastLine = "&nbsp;" if (!$lastLine);
       
  1780 	$lastTime = "&nbsp;" if (!$lastTime);
       
  1781 	my $anchor = $viewNumber != $lastViewNumber ? "html/log$viewNumber.html" : "log.html";
       
  1782 	print MAP qq { <tr><td><a href=\"$anchor\">$viewNumber</a></td><td>$firstLine</td><td>$lastLine</td><td>$firstTime</td><td>$lastTime</td>\n };
       
  1783 	print MAP qq { <td style="text-align: left ; font-family: courier new">\n };
       
  1784 	my $name = "";
       
  1785 	outputViewObjects($objectsCreatedRef);
       
  1786 	print MAP qq { </td><td style="text-align: left ; font-family: courier new"> \n };
       
  1787 	outputViewObjects($objectsDestroyedRef);
       
  1788 	print MAP "</td></tr>\n";
       
  1789 }
       
  1790 
       
  1791 sub outputViewObjects($)
       
  1792 {
       
  1793 	my $objectsRef = $_[0];
       
  1794 	my $name = "";
       
  1795 	foreach my $obj (@{$objectsRef}) {
       
  1796 		my $style = "";
       
  1797 		if (ref($obj) eq "HASH") {
       
  1798 			$name = $obj->{Name};		# obj = reference to (SVG) object
       
  1799 			if ($name =~ s/^!//) {
       
  1800 				$style = "font-weight: bold";
       
  1801 			}
       
  1802 		} else {
       
  1803 			$name = $obj;				# obj = thread name
       
  1804 			$style = "font-style: italic; font-weight: bold";
       
  1805 		}
       
  1806 		if ($style) {					# highlight processes/threads
       
  1807 			print MAP "<span style=\"$style\">$name</span> ";
       
  1808 		} else {
       
  1809 			print MAP "$name ";
       
  1810 		}
       
  1811 	}
       
  1812 	if (!$name) {
       
  1813 		print MAP "&nbsp;";
       
  1814 	}
       
  1815 }
       
  1816 
       
  1817 sub updateObjectViewList($)
       
  1818 {
       
  1819 	my ($viewNumber) = @_;
       
  1820 	foreach my $obj (@objects) {
       
  1821 		if ($obj->{Hide} == 1) {
       
  1822 			next;
       
  1823 		}
       
  1824 		if (! defined($obj->{FirstPage})) {
       
  1825 			$obj->{FirstPage} = $viewNumber;
       
  1826 		}
       
  1827 		elsif (! defined($obj->{LastPage}) || $viewNumber > $obj->{LastPage}) {
       
  1828 			$obj->{LastPage} = $viewNumber;
       
  1829 		}
       
  1830 	}
       
  1831 }
       
  1832 
       
  1833 
       
  1834 #
       
  1835 # Output the "0 1 2 3 4 ..." anchors representing the different views
       
  1836 #
       
  1837 sub outputHTMLPageLinks($$$$) {
       
  1838 	my ($viewNumber, $lastViewNumber, $htmlPathNotLastView, $htmlPathLastView) = @_;
       
  1839 	my $i;
       
  1840 	for ($i = 0 ; $i <= $lastViewNumber ; ++$i) {
       
  1841 		if ($i == $viewNumber) {
       
  1842 			print HTML qq{&nbsp; <b>$i</b>\n};
       
  1843 		} else {
       
  1844 			if ($i != $lastViewNumber) {
       
  1845 				print HTML qq{&nbsp;<a href="${htmlPathNotLastView}log$i.html">$i</a>\n};
       
  1846 			} else {
       
  1847 				print HTML qq{&nbsp;<a href="${htmlPathLastView}log.html">$i</a>\n};
       
  1848 			}
       
  1849 		}
       
  1850 	}
       
  1851 }
       
  1852 
       
  1853 sub outputHTMLEmbedder($$$$$)
       
  1854 {
       
  1855 	my ($width, $height, $viewNumber, $lastViewNumber, $fileName) = @_;
       
  1856 	#
       
  1857 	# leave log.html in the current directory, everything else goes into "html/" subdirectory
       
  1858 	#
       
  1859 	my $htmlPath;
       
  1860 	my $svgPathInHtmlFile;
       
  1861 	my $htmlLinkPathLastView;
       
  1862 	my $htmlLinkPathNotLastView;
       
  1863 	my $mainPath;
       
  1864 	if ($viewNumber != $lastViewNumber) {
       
  1865 		$htmlPath = "html/" . $fileName;
       
  1866 		$htmlLinkPathLastView = "../";
       
  1867 		$htmlLinkPathNotLastView = "";
       
  1868 		$svgPathInHtmlFile = $fileName;
       
  1869 		$mainPath = "../";
       
  1870 	} else {
       
  1871 		$htmlPath = $fileName;
       
  1872 		$htmlLinkPathLastView = "html/";
       
  1873 		$htmlLinkPathNotLastView = "html/";
       
  1874 		$svgPathInHtmlFile = "html/" . $fileName;
       
  1875 		$mainPath = "";
       
  1876 	}
       
  1877 	open HTML, ">${htmlPath}.html" || die "Cannot open ${htmlPath}.html for writing\n";
       
  1878 
       
  1879 	if (! $opt_f) {
       
  1880 		# DOCTYPE needed for "position: fixed" to work in IE 
       
  1881 		print HTML "<!--[if IE]>\n";
       
  1882 		print HTML qq{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n};
       
  1883 		print HTML "<![endif]-->\n";
       
  1884 		print HTML qq{<html>\n<head>\n<style type="text/css">\n};
       
  1885 		print HTML qq{#pagelinks { position: fixed; padding-right: 1px; top: 0px ; left: 0px; height: auto; background-color: lightyellow; border: 1px solid black }\n};
       
  1886 		# body "background-color: white": On machines with the system wide window background set to grey rather than white, using "...wmode="transparent"..." in the <embed>
       
  1887 		# shows up the (normally invisible) small white textual matt blocks that are written over the node lifeline to produce a break in the lifeline before the node
       
  1888 		# name is written over the top.  Set the background to white to hide these.
       
  1889 		print HTML qq{body { background-color: white }\n};
       
  1890 		print HTML qq{</style>\n</head>\n<body onLoad="loadPosition()" onUnload="savePosition()">\n};
       
  1891 		# Output a duplicate hidden set of pagelinks in the same position as the fixed ones.  The hidden pagelinks serve to
       
  1892 		# reserve vertical space at the top of the display for the fixed links to sit on top of and prevent them obscuring
       
  1893 		# the top of the SVG image.  Also, if the main window width is made smaller than the width of the fixed page links,
       
  1894 		# the latter will resize downward and start obscuring the top of the SVG image.  The hidden pagelinks prevent this
       
  1895 		# by resizing downward at the same time and so, not being fixed, will automatically reserve the right amount of space
       
  1896 		# at the top of the display for the fixed pagelinks to reside in.  A better way would be nice, but I don't know it.
       
  1897 		print HTML qq{<div style="visibility: hidden">};
       
  1898 		outputHTMLPageLinks($viewNumber, $lastViewNumber, $htmlLinkPathNotLastView, $htmlLinkPathLastView);
       
  1899 		print HTML qq{</div><div id="pagelinks">\n};
       
  1900 	} else {
       
  1901 		print HTML "<html>\n<body>\n";
       
  1902 	}
       
  1903 	outputHTMLPageLinks($viewNumber, $lastViewNumber, $htmlLinkPathNotLastView, $htmlLinkPathLastView);
       
  1904 	my $embedExtraArgs = "";
       
  1905 	if (! $opt_f) {
       
  1906 		print HTML "</div>\n";
       
  1907 		# wmode="transparent": In IE the "position: fixed" pagelinks will otherwise be hidden underneath the SVG image.
       
  1908 		$embedExtraArgs = qq{ wmode="transparent"};
       
  1909 	}
       
  1910 	print HTML qq{<embed src="$svgPathInHtmlFile.svg" width="$width" height="$height" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" name="embedder"$embedExtraArgs>\n};
       
  1911 	my $limitedText = limitedRowText();
       
  1912 	if ($limitedText)
       
  1913 		{
       
  1914 		print HTML "<small>$limitedText</small>&nbsp;\n";
       
  1915 		}
       
  1916 
       
  1917 	print HTML qq{<input type="button" value="Symbols" onclick="popupSymbols()">\n};
       
  1918 	print HTML qq{<input type="button" value="Relations" onclick="popupRelations()">\n};
       
  1919 	print HTML qq{<input type="button" value="Map" onclick="popupMap()">\n};
       
  1920 	print HTML qq{&nbsp;Zoom%\n<input type="text" size=3 maxlength=3 onkeypress="zoom(this,event)" />\n};
       
  1921 
       
  1922 	if ($opt_f) {
       
  1923 		# don't need the bottom page links if the fixed position ones are at the top
       
  1924 		outputHTMLPageLinks($viewNumber, $lastViewNumber, $htmlLinkPathNotLastView, $htmlLinkPathLastView);
       
  1925 	}
       
  1926 
       
  1927 	print HTML qq{<script type="text/javascript">\n};
       
  1928 	print HTML qq{var mainPath = "$mainPath";\n};
       
  1929 	print HTML qq{var width = $width;\nvar height = $height;\n};
       
  1930 	print HTML qq{var cookieName = "PagePos$viewNumber";\n};
       
  1931 
       
  1932 #################################
       
  1933 # Begin interpolated JavaScript #
       
  1934 #################################
       
  1935 	print HTML<<'EOT'
       
  1936 
       
  1937 var relationsDefined = 0;
       
  1938 var zoomPercent = 100;
       
  1939 
       
  1940 function zoom(obj,event) {
       
  1941 	var keycode;
       
  1942 	if (window.event) {
       
  1943 		keycode = window.event.keyCode;
       
  1944 	} else
       
  1945 	if (event) {
       
  1946 		keycode = event.which;
       
  1947 	} else
       
  1948 		return true;
       
  1949 	if (keycode == 13) {		// <return>
       
  1950 		var percent = obj.value;
       
  1951 		if (percent > 0 && percent <= 100) {
       
  1952 			if (percent != zoomPercent) {
       
  1953 				zoomPercent = percent;
       
  1954 				setViewBox();
       
  1955 			}
       
  1956 		} else {
       
  1957 			alert("Zoom value must be between 1 and 100");
       
  1958 		}
       
  1959 	}
       
  1960 }
       
  1961 
       
  1962 function setViewBox() {
       
  1963 	var viewBoxSize = "0 0 " + width*100/zoomPercent + " " + height*100/zoomPercent;
       
  1964 	document.embeds[0].getSVGDocument().documentElement.setAttribute("viewBox", viewBoxSize);
       
  1965 }
       
  1966 
       
  1967 // Functions for preserving original scroll position on each page
       
  1968 
       
  1969 function cookieToHash() {
       
  1970 	var cookie = document.cookie;
       
  1971 	var bits = cookie.split('&');
       
  1972 	var hash = new Array();
       
  1973 	for (b in bits) {
       
  1974 	    if (bits[b].length > 0)
       
  1975 	       {
       
  1976 	       var keypair = bits[b].split('=');
       
  1977 	       var key = unescape(keypair[0]);
       
  1978 	       var value = unescape(keypair[1]);
       
  1979 	       hash[key] = value;
       
  1980 	       }
       
  1981 	}
       
  1982 	return hash;
       
  1983 }
       
  1984 
       
  1985 function hashToCookie(hash) {
       
  1986 	var str = "";
       
  1987 	for (h in hash) {
       
  1988 	    str = str + escape(h) + "=" + escape(hash[h]) + "&";
       
  1989 	}
       
  1990 	document.cookie = str;
       
  1991 }
       
  1992 
       
  1993 function savePosition() {
       
  1994 	var hash = cookieToHash();
       
  1995 	var x = window.pageXOffset ? window.pageXOffset : document.documentElement.scrollLeft;
       
  1996 	var y = window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop;
       
  1997 	hash[cookieName] = x + "," + y;
       
  1998 	hashToCookie(hash);
       
  1999 }
       
  2000 
       
  2001 function loadPosition() {
       
  2002 	var hash = cookieToHash();
       
  2003 	var cookie = hash[cookieName];
       
  2004 	if (cookie) {
       
  2005 		var position = cookie.split(',');
       
  2006 		window.scrollTo(position[0], position[1]);
       
  2007 	}
       
  2008 }
       
  2009 
       
  2010 //
       
  2011 
       
  2012 function popupSymbols() {
       
  2013 	symbols = window.open(mainPath + "logsym.html", "_blank", "width=400,resizable=yes,scrollbars=yes");
       
  2014 }
       
  2015 
       
  2016 function popupRelations() {
       
  2017 	relations = window.open(mainPath + "relations.html", "_blank", "resizable=yes,scrollbars=yes,status=yes");
       
  2018 	relationsDefined = 1;
       
  2019 }
       
  2020 
       
  2021 function popupMap() {
       
  2022 	relations = window.open(mainPath + "logmap.html", "_blank", "resizable=yes,scrollbars=yes,status=yes");
       
  2023 }
       
  2024 
       
  2025 //
       
  2026 
       
  2027 function objectHighlightForwarder(id) {
       
  2028 	if (relationsDefined && !relations.closed) {
       
  2029 		relations.relationsObjectHighlightPtr(id);
       
  2030 	}
       
  2031 }
       
  2032 
       
  2033 function messageHighlightForwarder(id) {
       
  2034 	if (relationsDefined && !relations.closed) {
       
  2035 		relations.relationsMessageHighlightPtr(id);
       
  2036 	}
       
  2037 }
       
  2038 
       
  2039 function display(str, obj)
       
  2040 	{
       
  2041 	var s = "Browser::" + str + ": ";
       
  2042 	var t = "";
       
  2043 	for (i in obj)
       
  2044 		{
       
  2045 		t = typeof obj[i];
       
  2046 		s += i + " = ";
       
  2047 		if (t != "unknown") {
       
  2048 			var u = "";
       
  2049 			u += obj[i];
       
  2050 			if (u.indexOf("function") < 0)
       
  2051 				{
       
  2052 				s += u;
       
  2053 				}
       
  2054 			else
       
  2055 				{
       
  2056 				s += "(fn)";
       
  2057 				}
       
  2058 			}
       
  2059 		else
       
  2060 			{
       
  2061 			s += "(unk)";
       
  2062 			}
       
  2063 		s += ",   ";
       
  2064 		}
       
  2065 	alert(s);
       
  2066 	}
       
  2067 
       
  2068 </script>
       
  2069 </body>
       
  2070 </html>
       
  2071 EOT
       
  2072 ;
       
  2073 ###############################
       
  2074 # End interpolated JavaScript #
       
  2075 ###############################
       
  2076 	close HTML;
       
  2077 }
       
  2078 	
       
  2079 
       
  2080 
       
  2081 #######################
       
  2082 # SVG output routines
       
  2083 #######################
       
  2084 
       
  2085 sub outputDocHeader()
       
  2086 	{
       
  2087 	my ($width,$height) = @_;
       
  2088 	print SVG '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',"\n";
       
  2089 	print SVG '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',"\n";
       
  2090 	print SVG "<svg height=\"$height\" width=\"$width\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
       
  2091 	outputDefs();
       
  2092 	}
       
  2093 
       
  2094 sub outputDefs()
       
  2095 	{
       
  2096 	print SVG "<defs>\n";
       
  2097 	print SVG qq{<marker id="recvArr" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="10" refY="5" viewBox="0 0 10 10">\n};
       
  2098 	print SVG qq{\t<path d="M 0 0 L 10 5 L 0 10 " />\n};
       
  2099 	print SVG "</marker>\n";
       
  2100 	if ($opt_p) {
       
  2101 		print SVG qq{<marker id="postArr" stroke="$postColour" fill="white" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="10" refY="5" viewBox="0 0 10 10">\n};
       
  2102 		print SVG qq{\t<path d="M 0 0 L 10 5 L 0 10 Z" />\n};
       
  2103 		print SVG "</marker>\n";
       
  2104 	}
       
  2105 
       
  2106 	print SVG qq{<marker id="callArr" stroke="black" fill="white" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="10" refY="5" viewBox="0 0 10 10">\n};
       
  2107 	print SVG qq{\t<path d="M 0 5 L 10 5 L 0 0 L 10 5 L 0 10 L 10 5" />\n};
       
  2108 	print SVG "</marker>\n";
       
  2109 	
       
  2110 	print SVG "</defs>\n";
       
  2111 	}
       
  2112 
       
  2113 #
       
  2114 # Output label at lefthand side - used for timestamp
       
  2115 #
       
  2116 
       
  2117 sub outputLeftLabel($)
       
  2118 	{
       
  2119 	my ($text) = @_;
       
  2120 	outputText(0, $text, $currentY, "begin","black", "");
       
  2121 	}
       
  2122 
       
  2123 sub outputTextSeperator($$)
       
  2124 	{
       
  2125 	my ($text, $y) = @_;
       
  2126 	outputRect($leftSeperatorMargin, $y - $fontHeight, $screenWidth, $fontHeight, $seperatorColour, $seperatorColour);
       
  2127 	outputText($leftSeperatorMargin, $text, $y, "begin", "black", "");
       
  2128 	}
       
  2129 
       
  2130 
       
  2131 sub outputLabelledLine()
       
  2132 	{
       
  2133 	my ($x1,$x2,$text,$colour,$endType,$alignment, $otherAttrs) = @_;
       
  2134 	outputHorizontalLine($x1,$x2,$colour,$endType,$otherAttrs);
       
  2135 	my $textx;
       
  2136 	my $anchor;
       
  2137 
       
  2138 	if (! $colour) {
       
  2139 		$colour = "black";
       
  2140 	}
       
  2141 
       
  2142 	if (!$alignment || $alignment eq "mid") {
       
  2143 		$textx = (($x1 + $x2) / 2);
       
  2144 		$anchor = "middle";
       
  2145 		}
       
  2146 	else {
       
  2147 		if ($alignment eq "head") {
       
  2148 			if ($x1 < $x2) {
       
  2149 				$anchor = "end";
       
  2150 				$textx = $x2 - $arrowWidth - $textArrowSpacing;
       
  2151 				}
       
  2152 			else {
       
  2153 				$anchor = "start";
       
  2154 				$textx = $x2 + $arrowWidth + $textArrowSpacing;
       
  2155 				}
       
  2156 		} else {	# "tail"
       
  2157 			$textx = $x1;
       
  2158 			if ($x1 < $x2) {
       
  2159 				$anchor = "start";
       
  2160 			} else {
       
  2161 				$anchor = "end";
       
  2162 				}
       
  2163 			}
       
  2164 		}
       
  2165 
       
  2166 	outputText($textx,$text,$currentY - $messageSpacingAboveLine, $anchor, $colour, $otherAttrs);
       
  2167 	}
       
  2168 
       
  2169 sub outputText()
       
  2170 	{
       
  2171 	my ($x,$text,$y,$anchor,$colour, $otherAttrs) = @_;
       
  2172 	$text = encode_entities($text);
       
  2173 
       
  2174 	my $attrs = qq{ x="$x" y="$y" $otherAttrs };
       
  2175 
       
  2176 	if ($anchor)
       
  2177 		{
       
  2178 		$attrs .= qq { text-anchor="$anchor" };
       
  2179 		}
       
  2180 
       
  2181 	if ($colour)
       
  2182 		{
       
  2183 		if ($colour =~ s/^!//)
       
  2184 			{
       
  2185 			outputRect($x - 1, $y - $fontHeight, $textMattSize, $fontHeight, "white", "white");
       
  2186 			}
       
  2187 		if ($colour)
       
  2188 			{
       
  2189 			$attrs .= qq { fill="$colour" };
       
  2190 			}
       
  2191 		}
       
  2192 
       
  2193 	print SVG "<text $attrs>$text</text>\n";
       
  2194 	}
       
  2195 
       
  2196 sub outputLine()
       
  2197 	{
       
  2198 	my ($x1,$y1,$x2,$y2,$colour,$otherAttrs) = @_;
       
  2199 
       
  2200 	if (! $colour) {
       
  2201 		$colour = "black";
       
  2202 	}
       
  2203 	
       
  2204 	print SVG qq{<line stroke="$colour" x1="$x1" y1="$y1" x2="$x2" y2="$y2" $otherAttrs />\n};
       
  2205 	}
       
  2206 
       
  2207 sub outputHorizontalLine()
       
  2208 	{
       
  2209 	my ($x1,$x2,$colour,$endType,$otherAttrs) = @_;
       
  2210 
       
  2211 	if (! $colour) {
       
  2212 		$colour = "black";
       
  2213 	}
       
  2214 	
       
  2215 	print SVG qq{<line stroke="$colour" marker-end="url(#$endType)" x1="$x1" y1="$currentY" x2="$x2" y2="$currentY" $otherAttrs />\n};
       
  2216 	}
       
  2217 
       
  2218 sub outputVerticalLine()
       
  2219 	{
       
  2220 	my ($x,$y1,$y2,$colour,$otherAttrs) = @_;
       
  2221 	if (! $colour) { $colour = "black"; }
       
  2222 	print SVG qq{<line stroke="$colour" x1="$x" y1="$y1" x2="$x" y2="$y2" $otherAttrs />\n};
       
  2223 	}
       
  2224 
       
  2225 sub outputRect($$$$)
       
  2226 	{
       
  2227 	my ($x,$y,$width,$height,$fill,$stroke) = @_;
       
  2228 	print SVG qq {<rect fill="$fill" stroke="$stroke" x="$x" y="$y" width="$width" height="$height" />\n};
       
  2229 	}
       
  2230 
       
  2231 sub incrementY()
       
  2232 	{
       
  2233 	my $amount;
       
  2234 	if ($_[0]) {
       
  2235 		$amount = $_[0];
       
  2236 	} else {
       
  2237 		$amount = 1;
       
  2238 	}
       
  2239 	$currentY += $incrementY * $amount;
       
  2240 	}
       
  2241 
       
  2242 sub outputDocFooter()
       
  2243 	{
       
  2244 ###################################
       
  2245 #  Begin Interpolated JavaScript  #
       
  2246 ###################################
       
  2247 	print SVG <<'EOT'
       
  2248 <script type="text/ecmascript"> <![CDATA[
       
  2249 
       
  2250 var savedAttributes = new Object();
       
  2251 var nodeListDefined = 0;
       
  2252 var freePopups = new Array();
       
  2253 
       
  2254 createBindingToChild();
       
  2255 
       
  2256 function relationsObjectHighlight(nodeName)
       
  2257 	{
       
  2258 	parent.objectHighlightForwarder(nodeName);
       
  2259 	}
       
  2260 
       
  2261 function relationsMessageHighlight(nodeName)
       
  2262 	{
       
  2263 	parent.messageHighlightForwarder(nodeName);
       
  2264 	}
       
  2265 
       
  2266 function createBindingToChild()
       
  2267 	{
       
  2268 	parent.sequenceMessageHighlightPtr = messageHighlight;
       
  2269 	parent.sequenceObjectHighlightPtr = objectHighlight;
       
  2270 	}
       
  2271 
       
  2272 function popup(evt, popupText) {
       
  2273 	var documentElement = document.documentElement;
       
  2274 	var trans = documentElement.currentTranslate;	// in case canvas moved with "the hand"
       
  2275 
       
  2276 	var group = allocPopup(popupText);
       
  2277 	var margin = 2;
       
  2278 
       
  2279 	var rect = group.firstChild;
       
  2280 	var text = rect.nextSibling;
       
  2281 
       
  2282 	if (group.parentNode == null) {
       
  2283 		documentElement.appendChild(group);
       
  2284 	}
       
  2285 	var x = evt.clientX - trans.x;
       
  2286 	var y = evt.clientY - trans.y;
       
  2287 
       
  2288 	// Compensate for svg viewBox attribute being different size to svg width/height attributes - as a result of "zoom" functionality
       
  2289 	var viewBox = documentElement.getAttribute("viewBox");
       
  2290 	if (viewBox) {
       
  2291 		var viewBoxArray = viewBox.split(" ", 4);
       
  2292 		var viewBoxWidth = parseInt(viewBoxArray[2]);
       
  2293 		var viewBoxHeight = parseInt(viewBoxArray[3]);
       
  2294 		var svgWidth = parseInt(documentElement.getAttribute("width"));
       
  2295 		var svgHeight = parseInt(documentElement.getAttribute("height"));
       
  2296 		if (viewBoxWidth != svgWidth) {
       
  2297 			x = Math.floor(x * viewBoxWidth / svgWidth);
       
  2298 		}
       
  2299 		if (viewBoxHeight != svgHeight) {
       
  2300 			y = Math.floor(y * viewBoxHeight / svgHeight);
       
  2301 		}
       
  2302 	}
       
  2303 	// text
       
  2304 	text.setAttribute("x", x + margin);
       
  2305 	text.setAttribute("y", y);
       
  2306 	var textNode = text.firstChild;
       
  2307 	textNode.nodeValue = popupText;
       
  2308 
       
  2309 	// background rectangle
       
  2310 	var bbox = text.getBBox();
       
  2311 	var textYOffset = bbox.y - y;
       
  2312 	rect.setAttribute("width", bbox.width + margin * 2);
       
  2313 	rect.setAttribute("height", bbox.height + margin * 2);
       
  2314 	rect.setAttribute("x", x);
       
  2315 	rect.setAttribute("y", y + textYOffset - margin);
       
  2316 
       
  2317 	// make popup visible
       
  2318 	group.setAttribute("display", "inherit");
       
  2319 
       
  2320 	if (evt.shiftKey)
       
  2321 		{
       
  2322 		messageHighlight(evt.currentTarget.id);
       
  2323 		relationsMessageHighlight(evt.currentTarget.id);
       
  2324 		}
       
  2325 }
       
  2326 
       
  2327 function popdown(evt) {
       
  2328 	var element = evt.target.parentNode;		// "g" element
       
  2329 	element.setAttribute("display", "none");
       
  2330 	freePopup(element);
       
  2331 }
       
  2332 
       
  2333 function freePopup(element) {
       
  2334 	var i;
       
  2335 	// Popups that have been dismissed become invisible and placed into a free pool from which
       
  2336 	// they can be re-allocated and re-used when new popups are required.  The only reason to
       
  2337 	// do this rather than doing documentElement.removeChild(group), deleting the group element,
       
  2338 	// and re-creating the group element afresh next time a popup is required is that the
       
  2339 	// removeChild() seems to take a visible period of time to execute (i.e. about a second) before
       
  2340 	// the popup disappears, whereas just making the group element invisible and avoiding the
       
  2341 	// removeChild() looks instantaneous.
       
  2342 
       
  2343 	for (i = 0 ; i < freePopups.length ; ++i) {
       
  2344 		if (freePopups[i] == null) {
       
  2345 			freePopups[i] = element;	// add to existing slot in free list
       
  2346 			return;
       
  2347 		}
       
  2348 	}
       
  2349 	freePopups.push(element);			// add to end of free list
       
  2350 }
       
  2351 
       
  2352 function allocPopup(popupText) {
       
  2353 	var i;
       
  2354 	for (i = 0 ; i < freePopups.length ; ++i) {
       
  2355 		if (freePopups[i] != null) {
       
  2356 			var popup = freePopups[i];	// use existing popup
       
  2357 			freePopups[i] = null;
       
  2358 			// navigate from popup ("g") -> "rect" -> "text" -> textNode
       
  2359 			popup.firstChild.nextSibling.firstChild.nodeValue = popupText;
       
  2360 			return popup;
       
  2361 		}
       
  2362 	}
       
  2363 
       
  2364 	var group = document.createElementNS("http://www.w3.org/2000/svg", "g");			// create group
       
  2365 	group.setAttribute("onclick", "popdown(evt)");
       
  2366 
       
  2367 	var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");			// create rect
       
  2368 	rect.setAttribute("fill", "yellow");
       
  2369 
       
  2370 	// create <text> and text within it
       
  2371 	var text = document.createElementNS("http://www.w3.org/2000/svg", "text");			// create text
       
  2372 	var textNode = document.createTextNode(popupText);
       
  2373 	text.appendChild(textNode);
       
  2374 
       
  2375 	group.appendChild(rect);	// rect first...
       
  2376 	group.appendChild(text);	// ...text on top of it
       
  2377 
       
  2378 	return group;
       
  2379 }
       
  2380 
       
  2381 // save current attribute setting of a node and set new attribtue value.
       
  2382 //
       
  2383 // savedAttributes is used to store all the attributes and original values of elements
       
  2384 // whose attributes have been changed.  It is an object whose properties are attribute names.
       
  2385 // Each of these attribute names are, in turn, objects whose properties are attribute values.
       
  2386 // Each of these attribute values are, in turn, an array of elements.
       
  2387 // For example:
       
  2388 //	savedAttributes = { "stroke" : { "red"   : [element, ...], "blue" : [element, ...] },
       
  2389 //						"fill"   : { "green" : [element, ...], "pink" : [element, ...], "yellow" : [element, ...] }
       
  2390 
       
  2391 function saveAndSetAttribute(el, attrName, newVal)
       
  2392 {
       
  2393 	if (savedAttributes[attrName] == null) {
       
  2394 		savedAttributes[attrName] = new Object();
       
  2395 	}
       
  2396 
       
  2397 	var val = el.getAttribute(attrName);
       
  2398 	if (savedAttributes[attrName][val] == null) {
       
  2399 		savedAttributes[attrName][val] = new Array();
       
  2400 		}
       
  2401 	savedAttributes[attrName][val].push(el);
       
  2402 
       
  2403 	el.setAttribute(attrName, newVal);
       
  2404 }
       
  2405 
       
  2406 // restore all saved node attributes
       
  2407 function restoreAttributes()
       
  2408 {
       
  2409 	for (var attrName in savedAttributes) {
       
  2410 		for (var val in savedAttributes[attrName]) {
       
  2411 			while (savedAttributes[attrName][val].length > 0) {
       
  2412 				savedAttributes[attrName][val].pop().setAttribute(attrName, val);
       
  2413 			}
       
  2414 		}
       
  2415 	}
       
  2416 }
       
  2417 
       
  2418 // highlight a node, saving its changed attributes to restore later
       
  2419 function highlightNode(el)
       
  2420 	{
       
  2421 	if (el.tagName == "line")
       
  2422 		{
       
  2423 		saveAndSetAttribute(el, "stroke", "orange");
       
  2424 		}
       
  2425 	else
       
  2426 	if (el.tagName == "text")
       
  2427 		{
       
  2428 		saveAndSetAttribute(el, "fill", "orange");
       
  2429 		}
       
  2430 	}
       
  2431 
       
  2432 function messageHighlight(nodeNamesString)
       
  2433 	{
       
  2434 	restoreAttributes();
       
  2435 
       
  2436 	// given a string "x_y" highlight all nodes with tags "x", "y", "x_y" or "y_x"
       
  2437 
       
  2438 	var nodeNames = nodeNamesString.split("_", 2);
       
  2439 
       
  2440 	if (nodeNames.length == 2)
       
  2441 		{	
       
  2442 		var nodeList = getNodeList();
       
  2443 		var count = nodeList.length;
       
  2444 		var el;
       
  2445 		var reverseNodeNamesString = nodeNames[1] + "_" + nodeNames[0];
       
  2446 		for (i = 0 ; i < count ; i++)
       
  2447 			{
       
  2448 			el = nodeList.item(i);
       
  2449 			if (el.id == nodeNames[0] || el.id == nodeNames[1] || el.id == nodeNamesString || el.id == reverseNodeNamesString)
       
  2450 				{
       
  2451 				highlightNode(el);
       
  2452 				}
       
  2453 			}
       
  2454 		}
       
  2455 
       
  2456 	}
       
  2457 
       
  2458 // retrieve the node list, creating it (once) if necessary
       
  2459 function getNodeList()
       
  2460 {
       
  2461 	if (!nodeListDefined) {
       
  2462 		nodeList = document.getElementsByTagName("*");
       
  2463 		nodeListDefined = 1;
       
  2464 		}
       
  2465 	return nodeList;
       
  2466 }
       
  2467 
       
  2468 function objectHighlight(nodeName)
       
  2469 {
       
  2470 	restoreAttributes();
       
  2471 
       
  2472 	// given a string "x" highlight all nodes with tags "x", "x_<y>", "<y>_x", where <y> is any node name.  Then,
       
  2473 	// highlight all nodes with tag <y> for all values of <y> that were found.
       
  2474 
       
  2475 	var nodeNameLen = nodeName.length;
       
  2476 	var nodeList = getNodeList();
       
  2477 
       
  2478 	var count = nodeList.length;
       
  2479 	var el;
       
  2480 	var otherNodes = new Object();
       
  2481 
       
  2482 	for (i = 0 ; i < count ; i++)
       
  2483 		{
       
  2484 		el = nodeList.item(i);
       
  2485 		if (el.id == nodeName)						// nodes with tag "x"
       
  2486 			{
       
  2487 			highlightNode(el);
       
  2488 			}
       
  2489 		else {
       
  2490 			var underlinePos = el.id.indexOf("_");
       
  2491 			var nodePos;
       
  2492 			if (underlinePos != -1) {
       
  2493 				nodePos = el.id.indexOf(nodeName);
       
  2494 				if (nodePos != -1) {
       
  2495 					if (nodePos == 0 && underlinePos == nodeNameLen) {	// nodes with tag "x_<y>"
       
  2496 						highlightNode(el);
       
  2497 						otherNodes[el.id.substr(underlinePos+1)] = 1;
       
  2498 					} else
       
  2499 					if (nodePos == underlinePos + 1 && el.id.length == nodeNameLen + underlinePos + 1)	{	// nodes with tag "<y>_x"
       
  2500 						highlightNode(el);
       
  2501 						otherNodes[el.id.substr(0, underlinePos)] = 1;
       
  2502 					}
       
  2503 				}
       
  2504 			}
       
  2505 		}
       
  2506 	}
       
  2507 
       
  2508 	// nodes with tag "<y>"
       
  2509 	for (i = 0 ; i < count ; ++i) {
       
  2510 		el = nodeList.item(i);
       
  2511 		for (j in otherNodes) {
       
  2512 			if (el.id != nodeName && el.id == j) {
       
  2513 				highlightNode(el);
       
  2514 			}
       
  2515 		}
       
  2516 	}
       
  2517 }
       
  2518 
       
  2519 function debugEvent(evt)
       
  2520 	{
       
  2521 	objectHighlight(evt.currentTarget.id);
       
  2522 	relationsObjectHighlight(evt.currentTarget.id);
       
  2523 	}
       
  2524 
       
  2525 function display(str, obj)
       
  2526 	{
       
  2527 	var s = "SVG::" + str + ": ";
       
  2528 	var t = "";
       
  2529 	for (i in obj)
       
  2530 		{
       
  2531 		t = typeof obj[i];
       
  2532 		s += i + " = ";
       
  2533 		if (t != "unknown") {
       
  2534 			var u = "";
       
  2535 			u += obj[i];
       
  2536 			if (u.indexOf("function") < 0)
       
  2537 				{
       
  2538 				s += u;
       
  2539 				}
       
  2540 			else
       
  2541 				{
       
  2542 				s += "(function)";
       
  2543 				}
       
  2544 			}
       
  2545 		else
       
  2546 			{
       
  2547 			s += "(unknown)";
       
  2548 			}
       
  2549 		s += ",   ";
       
  2550 		}
       
  2551 	alert(s);
       
  2552 	}
       
  2553 
       
  2554 ]]> </script>
       
  2555 </svg>
       
  2556 EOT
       
  2557 #################################
       
  2558 #  End Interpolated JavaScript  #
       
  2559 #################################
       
  2560 	}