#/usr/bin/pic2plot -Tps
#
# Pic macros for drawing UML sequence diagrams
#
# (C) Copyright 2004-2005 Diomidis Spinellis.
#
# Permission to use, copy, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# $Id: sequence.pic 1.10 2005/10/19 18:36:08 dds Exp $
#


# Default parameters (can be redefined)

# Spacing between messages
spacing = 0.25;
# Active box width
awid = .1;
# Box height
boxht = 0.3;
# Commend folding
corner_fold=awid
# Comment distance
define comment_default_move {up 0.25 right 0.25};
# Comment height
comment_default_ht=0.5;
# Comment width
comment_default_wid=1;
#Default Object width
object_default_width = 0.75

# set 2 to the width of a string(text)
define getwidth {
  sh X 
    for i in $1; do 
      foo=$i; bar=0; 
      if (( ${#i} > $bar)); then 
        bar=${#i};
      fi; 
      echo "$i $bar" >> test; 
    done; 
    echo  " w= ($bar * 0.75) / 9;" > .sequence.temp 
    cat .sequence.temp >> log.sh
  X
  copy ".sequence.temp";
  if (w < object_default_width) then {
    w = object_default_width;
  }
  print "width of " $1"  is " w
}

# Create a new object with custom width (name, text)
define object {
  getwidth($2);
  $1: box $2 wid w; move;

  # Could also underline text with \mk\ul\ul\ul...\rt
  {
    line from $1.w + (.1, -.07) to $1.e + (-.1, -.07);
  }
  move to $1.e;
  move right;
  # Active is the level of activations of the object
  # 0 : inactive : draw thin line swimlane
  # 1 : active : draw thick swimlane
  # > 1: nested : draw nested swimlane
  active_$1 = 0;
  lifestart_$1 = $1.s.y;
}

# Create a new external actor(name,text)
define actor {
  getwidth($2)
  $1: [
    XSEQC: circle rad w;
    XSEQL: line from XSEQC.s down 2*w;
    line from XSEQL.start - (.15,.02) to XSEQL.start + (.15,-.02);
    XSEQL1: line from XSEQL.end left .02+w down .15;
    XSEQL2: line from XSEQL.end right .0+w down .15;
    line at XSEQC.n invis "" "" "" $2;
  ]
  move to $1.e;
  move right;
  active_$1 = 0;
  lifestart_$1 = $1.s.y - .05;
}

# Create a new placeholder object(name)
define placeholder_object {
  $1: box invisible;
  move;
  move to $1.e;
  move right;
  active_$1 = 0;
  lifestart_$1 = $1.s.y;
}

define pobject {
  placeholder_object($1);
}

define extend_lifeline {
  if (active_$1 > 0) then {
    # draw the left edges of the boxes
    move to ($1.x - awid/2, Here.y);
    for level = 1 to active_$1 do {
      line from (Here.x, lifestart_$1) to Here;
      move right awid/2
    }

    # draw the right edge of the innermost box
    move right awid/2;
    line from (Here.x, lifestart_$1) to Here;
  } else {
    line from ($1.x, lifestart_$1) to ($1.x, Here.y) dashed;
  }
  lifestart_$1 = Here.y;
}

# complete(name)
# Complete the lifeline of the object with the given name
define complete {
  extend_lifeline($1)
  if (active_$1) then {
    # draw bottom of all active boxes
    line right ((active_$1 + 1) * awid/2) from ($1.x - awid/2, Here.y);
  }
}

# Draw a message (from_object,to_object,label)
define message {
  down;
  move spacing;
  getwidth($3);
  # adjust object position so text fits
  # add buffer of awid on either side for clarity
  if (($1.x < $2.x) && (($2.x - $1.x) < w)) then {
    $2.x = $1.x + w + 2*awid; 
  } else {
    if (($2.x < $1.x) && (($1.x-$2.x) < w)) then {
      $2.x = $2.x + w + 2*awid; 
    }
  }
              
  # Adjust so that lines and arrows do not fall into the
  # active box.  Should be .5, but the arrow heads tend to
  # overshoot.
  if ($1.x <= $2.x) then {
    off_from = awid * .6;
    off_to = -awid * .6;
  } else {
    off_from = -awid * .6;
    off_to = awid * .6;
  }

  # add half a box width for each level of nesting
  if (active_$1 > 1) then {
    off_from = off_from + (active_$1 - 1) * awid/2;
  }

  # add half a box width for each level of nesting
  if (active_$2 > 1) then {
    off_to = off_to + (active_$2 - 1) * awid/2;
  }

  if ($1.x == $2.x) then {
    arrow from ($1.x + off_from, Here.y) right then down .25 then left $3 ljust " " " " " " ;
  } else {
    arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y);
    #draw the text seperatly to work around arrow bug
    #add a small offset of awid for clarity
    if ($1.x < $2.x) then {
      sh X 
        rm .sequence.temp; 
                          
        for i in $3; 
          do echo -n "\"$i\" above ljust " >> .sequence.temp;
        done; 
        echo ' at ($1.x + off_from + awid, Here.y)' >> .sequence.temp;
        cat .sequence.temp >> log.sh
      X
      copy ".sequence.temp"
    } else {
      sh X 
        rm .sequence.temp; 
        c=0
        for i in $3; do
          ((c++));
          if ((c>1)); then
            echo -n " \"$i\" below rjust " >> .sequence.temp;
          else
            echo -n " \"$i\" above rjust " >> .sequence.temp;
            echo " at ($1.x + off_from - awid, Here.y)" >> .sequence.temp;
          fi
        done; 
        if ((c>1)); then
          echo " at ($1.x + off_from - awid, Here.y - $((c>1?c-2:1))*8/72)" >> .sequence.temp;
        fi
        cat .sequence.temp >> log.sh
      X
      copy ".sequence.temp"
    }
  }
}

# Display a lifeline constraint(object,label)
define lifeline_constraint {
  off_from = awid;
  # add half a box width for each level of nesting
  if (active_$1 > 1) then {
    off_from = off_from + (active_$1 - 1) * awid/2;
  }

  box at ($1.x + off_from, Here.y) invis $2 ljust " " ;
}

define lconstraint {
  lifeline_constraint($1,$2);
}

# Display an object constraint(label)
# for the last object drawn
define object_constraint {
  { box invis with .s at last box .nw $1 ljust; }
}

define oconstraint {
  object_constraint($1);
}

# Draw a creation message(from_object,to_object,object_label)
define create_message {
  down;
  move spacing;
  if ($1.x <= $2.x) then {
    off_from = awid * .6;
    off_to = -boxwid * .51;
  } else {
    off_from = -awid * .6;
    off_to = boxwid * .51;
  }

  # add half a box width for each level of nesting
  if (active_$1 > 1) then {
    off_from = off_from + (active_$1 - 1) * awid/2;
  }
  
  #we shouldnt need to adjust object positioning for this message
  #as it fits within the minimal defaults  
  # See comment in destroy_message
  XSEQA: arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y)  " ";
  #draw the text seperatly to work around arrow bug
  #add a small offset of awid for clarity
  if ($1.x < $2.x) then {
    "«create»" above ljust at ($1.x + off_from + awid, Here.y) ;
  } else {
    "«create»" above rjust at ($1.x + off_from - awid, Here.y) ;
  }

  getwidth($3);
  if ($1.x <= $2.x) then {
    { XSEQB: box $3 wid w with .w at XSEQA.end; }
  } else {
    { XSEQB: box $3 wid w with .e at XSEQA.end; }
  }
  {
    line from XSEQB.w + (.1, -.07) to XSEQB.e + (-.1, -.07);
  }
  lifestart_$2 = XSEQB.s.y;
  move (spacing + boxht) / 2;
}

define cmessage {
  create_message($1,$2,$3);
}

# Draw an X for a given object
define drawx {
  {
  line from($1.x - awid, lifestart_$1 - awid) to ($1.x + awid, lifestart_$1 + awid);
  line from($1.x - awid, lifestart_$1 + awid) to ($1.x + awid, lifestart_$1 - awid);
  }
}

# Draw a destroy message(from_object,to_object)
define destroy_message {
  down;
  move spacing;
  # The troff code is \(Fo \(Fc
  # The groff code is also \[Fo] \[Fc]
  # The pic2plot code is \Fo \Fc
  # See http://www.delorie.com/gnu/docs/plotutils/plotutils_71.html
  # To stay compatible with all we have to hardcode the characters
  message($1,$2,"«destroy»");
  complete($2);
  drawx($2);
}

define dmessage {
  destroy_message($1,$2);
}

# An object deletes itself: delete(object)
define delete {
  complete($1);
  lifestart_$1 = lifestart_$1 - awid;
  drawx($1);
}

# Draw a message return(from_object,to_object,label)
define return_message {
  down;
  move spacing;
  # See comment in message
  if ($1.x <= $2.x) then {
    off_from = awid * .6;
    off_to = -awid * .6;
  } else {
    off_from = -awid * .6;
    off_to = awid * .6;
  }

  # add half a box width for each level of nesting
  if (active_$1 > 1) then {
    off_from = off_from + (active_$1 - 1) * awid/2;
  }

  # add half a box width for each level of nesting
  if (active_$2 > 1) then {
    off_to = off_to + (active_$2 - 1) * awid/2;
  }

  arrow from  ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) dashed $3 " ";
}

define rmessage {
  return_message($1,$2,$3);
}

# Object becomes active
# Can be nested to show recursion
define active {
  extend_lifeline($1);
  # draw top of new active box
  line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y);
  active_$1 = active_$1 + 1;
}

# Object becomes inactive
# Can be nested to show recursion
define inactive {
  extend_lifeline($1);
  active_$1 = active_$1 - 1;
  # draw bottom of innermost active box
  line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y);
}

# Time step
# Useful at the beginning and the end
# to show object states
define step {
  down;
  move spacing;
}

# Switch to asynchronous messages
define async {
  arrowhead = 0;
  arrowwid = arrowwid * 2;
}

# Switch to synchronous messages
define sync {
  arrowhead = 1;
  arrowwid = arrowwid / 2;
}

# same as lifeline_constraint, but Text and empty string are exchanged.
define lconstraint_below{
  off_from = awid;
  # add half a box width for each level of nesting
  if (active_$1 > 1) then {
    off_from = off_from + (active_$1 - 1) * awid/2;
  }

  box at ($1.x + off_from, Here.y) invis "" $2 ljust;
}

# begin_frame(left_object,name,label_text);
define begin_frame {
  # The lifeline will be cut here
  extend_lifeline($1);
  # draw the frame-label
  $2: box $3 invis with .n at ($1.x, Here.y);
  d = $2.e.y - $2.se.y;
  line from $2.ne to $2.e then down d left d then to $2.sw;
  # continue the lifeline below the frame-label
  move to $2.s;
  lifestart_$1 = Here.y;
}

# end_frame(right_object,name);
define end_frame {
  # dummy-box for the lower right corner:
  box invis "" with .s at ($1.x, Here.y);
  # draw the frame
  frame_wid = last box.se.x - $2.nw.x
  frame_ht = - last box.se.y + $2.nw.y
  box with .nw at $2.nw wid frame_wid ht frame_ht;
  # restore Here.y
  move to last box.s;
}

# comment(object,[name],[line_movement], [box_size] text);
define comment {
  old_y = Here.y
  # draw the first connecting line, at which's end the box wil be positioned
  move to ($1.x, Here.y)
  if "$3" == "" then {
    line comment_default_move() dashed;
  } else {
    line $3 dashed;
  }

  # draw the box, use comment_default_xx if no explicit
  # size is given together with the text in parameter 4
  old_boxht=boxht;
  old_boxwid=boxwid;
  boxht=comment_default_ht;
  boxwid=comment_default_wid;
  if "$2" == "" then {
    box invis $4;
  } else {
    $2: box invis $4;
  }
  boxht=old_boxht;
  boxwid=old_boxwid;

  # draw the frame of the comment
  line from last box.nw \
    to      last box.ne - (corner_fold, 0) \
    then to last box.ne - (0, corner_fold) \
    then to last box.se \
    then to last box.sw \
    then to last box.nw ;
  line from last box.ne - (corner_fold, 0) \
    to      last box.ne - (corner_fold, corner_fold) \
    then to last box.ne - (0, corner_fold) ;

  # restore Here.y
  move to ($1.x, old_y)
}

# connect_to_comment(object,name);
define connect_to_comment {
  old_y = Here.y
  # start at the object
  move to ($1.x, Here.y)
  # find the best connection-point of the comment to use as line-end
  if $1.x < $2.w.x then {
    line to $2.w dashed;
  } else {
    if $1.x > $2.e.x then {
      line to $2.e dashed;
    } else {
      if Here.y < $2.s.y then {
        line to $2.s dashed;
      } else {
        if Here.y > $2.n.y then {
          line to $2.n dashed;
        }
      }
    }
  }
  # restore Here.y
  move to ($1.x, old_y)
}
