

import java.awt.*;
import java.awt.event.*;
import java.lang.Math;
import java.util.*;
import javax.swing.*;
import java.lang.reflect.*;
import com.genlogic.*;

//////////////////////////////////////////////////////////////////////////
// A diagramming editor: example of using Extended API.
//
// This demo uses Glg as a bean and may be used in a browser or
// stand-alone.
//
// The type of the diagram is selected by the first command-line argument
// in the stand-alone mode: -diagram or -process_diagram.
// When used as an applet, the diagram type is defined by the settings of
// the ProcessDiagram applet parameter.
//
// The CustomHandler class provides callbacks for interfacing with
// application-specific functionality.
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
public class GlgDiagram extends GlgJBean 
    implements ActionListener    // For updating process diagram 
{
   static final long serialVersionUID = 0;

   // Constants

   // Selection sensitivity in pixels
   static final int SELECTION_RESOLUTION = 5;
   static final int POINT_SELECTION_RESOLUTION = 2;

   // Number of palette buttons to skip: the first button with the 
     // "select" icon is already in the palette.
   static final int PALETTE_START_INDEX = 1;

   // Default scale factor for icon buttons.
   static final double DEFAULT_ICON_ZOOM_FACTOR = 10.;

   // Percentage of the button area to use for the icon.
   static final double ICON_FIT_FACTOR = 0.6;

   // Scale factor when placed in the drawing
   static final double IconScale = 1.;

   // Editing Modes
   static final int SELECT_OBJECT = 0;
   static final int ADD_NODE = 4;
   static final int ADD_LINK1 = 5;
   static final int ADD_LINK2 = 6;
   static final int DRAGGING = 7;
   
   // Object types
   static final int NO_OBJ = 0;
   static final int NODE = 1;
   static final int LINK = 2;
   
   boolean StandAlone;
   GlgObject Viewport;
   GlgObject MainArea;
   GlgObject SelectedObject = null;
   GlgObject PointMarker = null;
   GlgObject AttachmentMarker = null;
   GlgObject AttachmentArray = null;
   GlgObject AttachmentNode = null;
   boolean StickyCreateMode = false;
   boolean AllowUnconnectedLinks = true;
   boolean PointFeedbackShown = false;
   int SelectedObjectType;
   int NodeType;
   int LinkType;
   GlgObject DragLink;
   int EdgeType;
   GlgObject Point1;
   GlgObject StoredColor;
   GlgObject CutBuffer;
   int CutBufferType;
   int Mode;
   int NumLinkPoints;
   boolean MiddlePointAdded = false;
   String LastButton;
   GlgObject Handler;
   GlgDiagramData CurrentDiagram = new GlgDiagramData();
   GlgDiagramData SavedDiagram;
   GlgObject NodeIconArray;
   GlgObject NodeObjectArray;
   GlgObject LinkIconArray;
   GlgObject LinkObjectArray;
   GlgObject PaletteTemplate;
   GlgObject ButtonTemplate;
   int NumRows;
   int NumColumns;

   GlgPoint cursor_pos = new GlgPoint();
   GlgPoint world_coord = new GlgPoint();
   GlgPoint screen_coord = new GlgPoint();
   GlgPoint start_point = new GlgPoint();
   GlgPoint end_point = new GlgPoint();
   GlgCube select_rect = new GlgCube();

   GlgObject last_color;   // Stores color during selection.

   boolean ProcessDiagram = false;   // Defines the type of the diagram.

   // Used by process diagram.
   int DataSourceCounter = 0;
   int NumDatasources = 20;
   static int UPDATE_INTERVAL = 1000;     // Update once per second.

   // If set to True, icons are automatically fit to fill the button.
     // If set to False, the default zoom factor will be used.
   boolean FitIcons = false;

   javax.swing.Timer timer = null;
   boolean IsReady = false;

   //////////////////////////////////////////////////////////////////////////
   // Utility class used to return several values.
   //////////////////////////////////////////////////////////////////////////
   class ObjectInfo
   {
      GlgObject object;
      int type;

      // Constructors
      ObjectInfo() 
      {}

      ObjectInfo( GlgObject object_p, int type_p )
      {
         object = object_p;
         type = type_p;
      }
   }

   //////////////////////////////////////////////////////////////////////////
   public GlgDiagram()
   {
      super();

      SelectedObjectType = NO_OBJ;
      CutBufferType = NO_OBJ;
      Mode = SELECT_OBJECT;
      LastButton = null;

      // The bean is a default listener for bean events except the trace
      // events (which are disabled by default), but add all listeners
        // explicitely as an example.
      AddListener( GlgObject.H_CB, this );
      AddListener( GlgObject.V_CB, this );
      AddListener( GlgObject.READY_CB, this );
      AddListener( GlgObject.INPUT_CB, this );
      AddListener( GlgObject.TRACE_CB, this );
   }

   //////////////////////////////////////////////////////////////////////////
   // For use as a stand-alone java demo
   // The type of the diagram is selected by the first command-line argument:
   //   -diagram or -process_diagram.
   //////////////////////////////////////////////////////////////////////////
   public static void main( final String arg[] )
   {
      SwingUtilities.
        invokeLater( new Runnable(){ public void run() { Main( arg ); } } );
   }

   //////////////////////////////////////////////////////////////////////////
   public static void Main( final String arg[] )
   {
      class DemoQuit extends WindowAdapter
      {
         public void windowClosing( WindowEvent e ) { System.exit( 0 ); }
      } 

      JFrame frame = new JFrame();

      frame.setResizable( true );
      frame.setSize( 900, 700 );
      frame.setLocation( 20, 20 );

      GlgDiagram diagram = new GlgDiagram();      
      diagram.StandAlone = true;

      int num_arg = Array.getLength( arg );
      if( num_arg != 0 )
      {
         if( arg[ 0 ].equals( "-process_diagram" ) )
           diagram.ProcessDiagram = true;
         else if( arg[ 0 ].equals( "-diagram" ) )
           diagram.ProcessDiagram = false;
         else
           DisplayUsage();
      }
      else
        DisplayUsage();
      
      // Loading the drawing triggers the H, V and Ready callbacks.
      // When used as an applet, the DrawingName parameter is set in HTML.
        //
      diagram.SetDrawingName( diagram.ProcessDiagram ? 
                             "process_diagram.g" : "diagram.g" );

      frame.getContentPane().add( diagram );

      frame.addWindowListener( new DemoQuit() );
      frame.setVisible( true );
   }

   //////////////////////////////////////////////////////////////////////////
   static void DisplayUsage()
   {
      System.out.println( "Use the -process_diagram or -diagram options" );
      System.out.println( "    to select the type of the diagram.\n" );
   }

   //////////////////////////////////////////////////////////////////////////
   // Invoked after the drawing has been loaded but before it's set up. 
   //////////////////////////////////////////////////////////////////////////
   public void HCallback( GlgObject viewport )
   {
      if( !StandAlone )
      {
         String prop_string = getParameter( "ProcessDiagram" );
         if( prop_string != null )
           if( prop_string.equalsIgnoreCase( "true" ) )
             ProcessDiagram = true;
           else if( prop_string.equalsIgnoreCase( "false" ) )
             ProcessDiagram = false;
      }

      if( ProcessDiagram )
        FitIcons = true;
      else
        FitIcons = false;

      // Fill out the palette and change the dialog type before the 
        // hierarchy setup

      Viewport = GetViewport();

      MainArea = Viewport.GetResourceObject( "MainArea" );
      if( MainArea == null )
      {
         PrintToJavaConsole( "Can't find MainArea viewport.\n" );
         return;
      }

      PaletteTemplate = Viewport.GetResourceObject( "PaletteTemplate" );
      if( PaletteTemplate == null )
      {
         PrintToJavaConsole( "Can't find PaletteTemplate viewport.\n" );
         return;
      }
      Viewport.DeleteObject( PaletteTemplate );

      // Create groups to hold nodes and links.
      NodeIconArray = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );
      NodeObjectArray = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );
      LinkIconArray = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );
      LinkObjectArray = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );

      // Scan palette template and extract icon and link objects, adding them
        // to the buttons in the object palette.
      GetPaletteIcons( PaletteTemplate, "Node", 
                      NodeIconArray, NodeObjectArray );
      GetPaletteIcons( PaletteTemplate, "Link", 
                      LinkIconArray, LinkObjectArray );

      FillObjectPalette( "ObjectPalette", "IconButton", PALETTE_START_INDEX );
   }

   //////////////////////////////////////////////////////////////////////////
   // Invoked after the drawing has been loaded and set up, but before it's 
   // displayed.
   //////////////////////////////////////////////////////////////////////////
   public void VCallback( GlgObject viewport )
   {
      // Do some initialization here before displaying the drawing.
      SetPrompt( "" );

      // Create a color object to store original node color during node 
      // selection.
      StoredColor = Viewport.GetResourceObject( "FillColor" ).CopyObject();

      // Make exit button visible for stand-alone.
      if( StandAlone )
        Viewport.SetDResource( "Exit/Visibility", 1. );
      else
        Viewport.SetDResource( "Exit/Visibility", 0. );

      // Set grid color (configuration parameter) to grey.
      Viewport.SetGResource( "$config/GlgGridPolygon/EdgeColor",
                             0.632441, 0.632441, 0.632441 );

      // Make the properties dialog a top-level window, set its title and make
      // it invisible on startup. 
        // You can replace the dialog with any custom Java-based dialog.
      Viewport.SetDResource( "Dialog/ShellType", 
                            (double) GlgObject.DIALOG_SHELL );
      Viewport.SetSResource( "Dialog/ScreenName", "Object Properties" );
      Viewport.SetDResource( "Dialog/Visibility", 0. );

      // Same for the datasource dialog.
      if( ProcessDiagram )
      {
         Viewport.SetDResource( "DataSourceDialog/ShellType", 
                               (double) GlgObject.DIALOG_SHELL );
         Viewport.SetSResource( "DataSourceDialog/ScreenName",
                               "DataSource List" );
         Viewport.SetDResource( "DataSourceDialog/Visibility", 0. );
      }

      // Create a separate group to hold objects.
      GlgObject group = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );
      group.SetSResource( "Name", "ObjectGroup" );
      MainArea.AddObjectToBottom( group );      

      Mode = SELECT_OBJECT;
      SetRadioBox( "IconButton0" );  // Highlight Select button

      SetCreateMode();    // Query initial create mode from the drawing.

      CurrentDiagram = new GlgDiagramData();

      // Position node icons inside the palette buttons.
      SetupObjectPalette( "IconButton", PALETTE_START_INDEX );
   }

   //////////////////////////////////////////////////////////////////////////
   // Invoked after the drawing has been drawn for the first time.
   //////////////////////////////////////////////////////////////////////////
   public void ReadyCallback( GlgObject viewport )
   {
      super.ReadyCallback();

      IsReady = true;
      if( ProcessDiagram )
        if( timer == null )
        {
           timer = new javax.swing.Timer( UPDATE_INTERVAL, this );
           timer.start();
        }
   }

   //////////////////////////////////////////////////////////////////////////
   // Stops updates when used as an applet.
   //////////////////////////////////////////////////////////////////////////
   public void stop()
   {
      if( timer != null )
      {
         timer.stop();
         timer = null;
      }
      IsReady = false;
      super.stop();
   }

   //////////////////////////////////////////////////////////////////////////
   // Sets create mode based on the state of the CreateMode button.
   //////////////////////////////////////////////////////////////////////////
   void SetCreateMode()
   {
      int create_mode = (int) GetDResource( "CreateMode/OnState" );
      StickyCreateMode = ( create_mode != 0 );
   }

   ////////////////////////////////////////////////////////////////////////
   // Handles mouse operations: selection, dragging, connection point 
   // highlight.
   ////////////////////////////////////////////////////////////////////////
   public void TraceCallback( GlgObject viewport, GlgTraceData trace_info )
   {
      int event_type;
        
      // Use the MainArea's events only.
      if( !IsReady || trace_info.viewport != MainArea )
        return;

      event_type = trace_info.event.getID();
      switch( event_type )
      {
       case MouseEvent.MOUSE_PRESSED:
       case MouseEvent.MOUSE_MOVED:
       case MouseEvent.MOUSE_DRAGGED:
       case MouseEvent.MOUSE_RELEASED:
         if( GetButton( trace_info.event ) != 1 )
           return;  // Use the left button clicks only.
            
         cursor_pos.x = (double) ((MouseEvent)trace_info.event).getX();
         cursor_pos.y = (double) ((MouseEvent)trace_info.event).getY();
         cursor_pos.z = 0.;
         break;
            
       default: return;
      }      

      if( MainArea.GetDResource( "ZoomToMode" ).intValue() != 0 )
        return;   // Don't handle mouse selection in ZoomTo mode.

      switch( Mode )
      {
       default:
         Bell();
         ResetModes();
         Update();
         return;

       case ADD_NODE: AddNodeHandler( event_type, cursor_pos ); return;

       case ADD_LINK1:
       case ADD_LINK2:
         AddLinkHandler( event_type, cursor_pos, viewport, trace_info );
         return;

       case DRAGGING:
         DraggingHandler( event_type, cursor_pos ); return;

       case SELECT_OBJECT:
         ObjectHandler( event_type, cursor_pos, viewport, trace_info ); return;
      }
   }

   ////////////////////////////////////////////////////////////////////////
   public void AddNodeHandler( int event_type, GlgPoint cursor_pos )
   {
      if( event_type != MouseEvent.MOUSE_PRESSED )
        return;

      // Use screen coords of the cursor to add graphics
      GlgObject new_node =
        AddNodeAt( NodeType, null, cursor_pos, GlgObject.SCREEN_COORD );

      CustomHandler.AddObjectCB( this, new_node, GetData( new_node ), true );
                  
      SelectGlgObject( new_node, NODE );

      if( !StickyCreateMode )
        ResetModes();

      Update();
   }

   ////////////////////////////////////////////////////////////////////////
   public void AddLinkHandler( int event_type, GlgPoint cursor_pos,
                              GlgObject viewport, GlgTraceData trace_info )
   {
      GlgObject point, sel_object = null, sel_node, pt_array;
      GlgLinkData link_data;
      int num_selected;

      if( event_type != MouseEvent.MOUSE_PRESSED && 
         event_type != MouseEvent.MOUSE_MOVED )
        return;

      // Drag the link's last point
      if( event_type == MouseEvent.MOUSE_MOVED && Mode == ADD_LINK2 )
      {
         link_data = (GlgLinkData) GetData( DragLink );

         // First time: set link direction depending of the 
           // direction of the first mouse move, then make the link visible.
         if( link_data.first_move )
         {
            SetEdgeDirection( DragLink, start_point, cursor_pos );
            DragLink.SetDResource( "Visibility", 1. );
            link_data.first_move = false;
         }

         SetLastPoint( DragLink, cursor_pos, false );

         if( !MiddlePointAdded )
           SetArcMiddlePoint( DragLink );
         Update();
      }

      GlgObject selection = 
        GetObjectsAtCursor( /** top viewport **/ viewport, 
                           /** event viewport **/ trace_info.viewport, 
                           cursor_pos );
      
      if( selection != null && ( num_selected = selection.GetSize() ) != 0 )
      {
         // Some objects were selected, process the selection to find the 
           // point to connect to.
         point = null;
         pt_array = null;
         sel_node = null;

         for( int i=0; i < num_selected; ++i )
         {
            sel_object = (GlgObject) selection.GetElement( i );

            // Find if the object itself is a link or a node,
            // or if it's a part of a node. If it's a part of a node,
            // get the node object ID.
            //
            ObjectInfo selection_info = GetSelectedObject( sel_object );
            sel_object = selection_info.object;
            int selection_type = selection_info.type;

            if( selection_type == NODE )
            {
               int type = sel_object.GetDResource( "Type" ).intValue();
               if( type == GlgObject.REFERENCE )
               {
                  // Use ref's point as a connector.
                  point = sel_object.GetResourceObject( "Point" );
               }
               else  // Node has multiple attachment point: get the points.
               {
                  pt_array = GetAttachmentPoints( sel_object, "CP" );
                  if( pt_array == null )
                    continue;

                  point = GetSelectedPoint( pt_array, cursor_pos );

                  // Use attachment points array to highlight all attachment 
                  // points only if no specific point is selected, and only
                  // on the mouse move. On the mouse press, the specific point
                    // is used to connect to.
                  if( point != null || event_type != MouseEvent.MOUSE_MOVED )
                    pt_array = null;
                  else
                    sel_node = sel_object;
               }

               // If found a point to connect to, stop search and use it.
               // If found a node with attachment points, stop search and
                 // highlight the points.
               if( point != null || pt_array != null )
               {
                  if( point != null )   // If found the point, reset pt_array.
                    pt_array = null;
                  break;
               }
            }

            // No point to connect to: continue searching all selected objects.
         }

         if( point != null )  // Use point if found.
         {
            switch( event_type )
            {
               // Show feedback at the connector's position.
             case MouseEvent.MOUSE_MOVED:
               ShowAttachmentPoints( point, null, null, 0 );
               Update();
               break;

             case MouseEvent.MOUSE_PRESSED:   // Connect
               switch( Mode )
               {
                case ADD_LINK1:
                  Point1 = point;

                  DragLink = AddLink( LinkType, null );
                  NumLinkPoints = 1;

                  // First point
                  ConstrainLinkPoint( DragLink, Point1, false );
                  AttachFramePoints( DragLink );
            
                  // Wire up the start node
                  link_data = (GlgLinkData) GetData( DragLink );
                  link_data.setStartNode( (GlgNodeData)GetData( sel_object ) );

                  // Store cursor position for setting direction based on the
                    // first mouse move.
                  start_point.CopyFrom( cursor_pos );
                  link_data.first_move = true;
                  DragLink.SetDResource( "Visibility", 0. );

                  Mode = ADD_LINK2;
                  SetPrompt( "Select the second node." );

                  Update();
                  break;

                case ADD_LINK2:
                  if( point == Point1 )
                  {
                     SetError( "The two nodes are the same, " +
                              "chose a different second node." );
                     break;
                  }

                  ++NumLinkPoints;

                  // Last point
                  ConstrainLinkPoint( DragLink, point, true );
                  AttachFramePoints( DragLink );
                  if( !MiddlePointAdded )
                    SetArcMiddlePoint( DragLink );

                  // Wire up the end node
                  link_data = (GlgLinkData) GetData( DragLink );
                  link_data.setEndNode( (GlgNodeData) GetData( sel_object ) );

                  FinalizeLink( DragLink );
                  DragLink = null;	       

                  if( StickyCreateMode )
                  {
                     // Start over to create more links
                     Mode = ADD_LINK1;
                     SetPrompt( "Select the first node." );   
                     MiddlePointAdded = false;
                  }
                  else
                    ResetModes();

                  Update();
                  break;
               }
            }    /* switch( event_type ) */

            return;  // We are done with the point.

         }   /* if( point != null )   */
         // Mouse is over a node: highlight all connection points.
         else if( pt_array != null )
         {
            ShowAttachmentPoints( null, pt_array, sel_node, 1 );
            Update();	    
            return;    // We are done with pt_array.
         }
      }

      // No point or no selection: handle erasing attachment point feedback 
        // and adding middle points for arc links.

      if( EraseAttachmentPoints() )
        Update();

      if( event_type == MouseEvent.MOUSE_PRESSED )    // Add midlle link point
      {	     
         if( Mode == ADD_LINK1 )
         {
            SetError( "Invalid connection point!" );
            return;  // No first point yet
         }

         ++NumLinkPoints;

         // Add middle link point.
         AddLinkPoints( DragLink, 1 );
         MiddlePointAdded = true;

         SetLastPoint( DragLink, cursor_pos, EdgeType == GlgObject.ARC );
         AttachFramePoints( DragLink );

         if( EdgeType == GlgObject.ARC )
         {
            // Offset the last point after setting the middle one.
            cursor_pos.x += 10;
            cursor_pos.y += 10;
            SetLastPoint( DragLink, cursor_pos, false );
         }
         Update();	    
      }
   }

   ////////////////////////////////////////////////////////////////////////
   void ShowAttachmentPoints( GlgObject point, GlgObject pt_array, 
                             GlgObject sel_node, int highlight_type )
   {
      GlgPoint screen_point;

      if( point != null )
      {
         if( AttachmentArray != null )
           EraseAttachmentPoints();   
         
         // Get screen coords of the connector point, not the cursor
           // position: may be a few pixels off.
         screen_point = point.GetGResource( "XfValue" );

         MainArea.ScreenToWorld( true, screen_point, world_coord );

         if( AttachmentMarker == null )
         {
            AttachmentMarker = PointMarker;
            MainArea.AddObjectToBottom( AttachmentMarker );
         }

         // Position the feedback marker over the connector.
         AttachmentMarker.SetGResource( "Point", world_coord );

         AttachmentMarker.SetDResource( "HighlightType", 
                                       (double) highlight_type );
      }
      else if( pt_array != null )
      {
         if( sel_node == AttachmentNode )
           return;    // Attachment points are already shown for this node.
             
         // Erase previous attachment feedback if shown.
         EraseAttachmentPoints();   

         int size = pt_array.GetSize();
         AttachmentArray = new GlgDynArray( GlgObject.GLG_OBJECT, size, 0 );
         AttachmentNode = sel_node;
         
         for( int i=0; i<size; ++i )
         {
            GlgObject marker = PointMarker.CopyObject();
            
            point = (GlgObject) pt_array.GetElement( i );
            
            // Get the screen coords of the connector point.
            screen_point = point.GetGResource( "XfValue" );
            
            MainArea.ScreenToWorld( true, screen_point, world_coord );
            
            // Position the feedback marker over the connector.
            marker.SetGResource( "Point", world_coord );
            
            marker.SetDResource( "HighlightType", (double)highlight_type );

            AttachmentArray.AddObjectToBottom( marker );
         }
         MainArea.AddObjectToBottom( AttachmentArray );
      }
   }

   ////////////////////////////////////////////////////////////////////////
   // Erases attachment points feedback if shown. Returns True if feedback
   // was erased, of False if there was nothing to erase.
   ////////////////////////////////////////////////////////////////////////
   boolean EraseAttachmentPoints()
   {
      if( AttachmentMarker != null )
      {
         MainArea.DeleteObject( AttachmentMarker );
         AttachmentMarker = null;
         return true;
      }

      if( AttachmentArray != null )
      {
         MainArea.DeleteObject( AttachmentArray );
         AttachmentArray = null;
         AttachmentNode = null;
         return true;
      }

      return false;    // Nothing to erase.
   }

   ////////////////////////////////////////////////////////////////////////
   void FinalizeLink( GlgObject link )
   {
      GlgObject arrow_type = link.GetResourceObject( "ArrowType" );
      if( arrow_type != null )
        arrow_type.SetDResource( null, (double) GlgObject.MIDDLE_FILL_ARROW );

      GlgLinkData link_data = (GlgLinkData) GetData( link );

      // Store points
      StorePointData( link_data, link );
   
      // Add link data to the link list
      Vector link_list = CurrentDiagram.getLinkList();
      link_list.addElement( link_data );
   
      // After storing color: changes color to select.
      SelectGlgObject( link, LINK );
   
      CustomHandler.AddObjectCB( this, link, GetData( link ), false );
   }

   ////////////////////////////////////////////////////////////////////////
   // Stores point coordinates in the link data structure as a vector.
   ////////////////////////////////////////////////////////////////////////
   void StorePointData( GlgLinkData link_data, GlgObject link )
   {
      ObjectInfo link_info = GetCPContainer( link );
      GlgObject point_container = link_info.object;
      
      int num_points = point_container.GetSize();

      // Always create a new vector and discard the old one for simplicity
        // (not much to gain any way).
      link_data.point_array = new Vector( num_points );
      for( int i=0; i<num_points; ++i )
      {
         GlgObject point = (GlgObject) point_container.GetElement( i );
         GlgPoint xyz = point.GetGResource( null );
         link_data.point_array.addElement( new GlgDiagramPoint( xyz ) );
      }
   }
      
   ////////////////////////////////////////////////////////////////////////
   // Restores link's middle points from the link data's stored vector.
   // The first and last point's values are not used: they are constrained 
   // to nodes and positioned/controlled by them.
   ////////////////////////////////////////////////////////////////////////
   void RestorePointData( GlgLinkData link_data, GlgObject link )
   {
      // Set middle point values
      if( link_data.point_array != null )
      {
         int num_points = link_data.point_array.size();

         ObjectInfo link_info = GetCPContainer( link );
         GlgObject point_container = link_info.object;

         // Skip the first and last point if they are constrained to nodes.
           // Set only the unconnected ends and middle points.
         int start = ( link_data.getStartNode() != null ? 1 : 0 );
         int end = 
           ( link_data.getEndNode() != null ? num_points - 1 : num_points );

         // Skip the first and last point: constrained to nodes.
           // Set only the middle points.
         for( int i=start; i<end; ++i )
         {
            GlgObject point = (GlgObject) point_container.GetElement( i );
            GlgDiagramPoint xyz = 
              (GlgDiagramPoint) link_data.point_array.elementAt( i );
            point.SetGResource( null, xyz );
         }
      }
   }

   ////////////////////////////////////////////////////////////////////////
   // Handles object selection and dragging start.
   ////////////////////////////////////////////////////////////////////////
   public void ObjectHandler( int event_type, GlgPoint cursor_pos,
                             GlgObject viewport, GlgTraceData trace_info )
   {
      int num_selected;

      if( event_type != MouseEvent.MOUSE_PRESSED )
        return;

      GlgObject selection = 
        GetObjectsAtCursor( /** top viewport **/ viewport, 
                           /** event viewport **/ trace_info.viewport, 
                           cursor_pos );

      if( selection != null && ( num_selected = selection.GetSize() ) != 0 )
      {
         // Some objects were selected, process the selection.
         for( int i=0; i < num_selected; ++i )
         {
            GlgObject sel_object = (GlgObject) selection.GetElement( i );

            // Find if the object itself is a link or a node,
            // of if it's a part of a node. If it's a part of a node,
            // get the node object ID.
            //
            ObjectInfo selection_info = GetSelectedObject( sel_object );
            sel_object = selection_info.object;
            int selection_type = selection_info.type;
         
            if( selection_type != 0 )
            {
               SelectGlgObject( sel_object, selection_type );

               Update();

               CustomHandler.SelectObjectCB( this, sel_object,
                                            GetData( sel_object ),
                                            selection_type == NODE );

               // Prepare for dragging
               Mode = DRAGGING;  

               // Store the start point
               start_point.CopyFrom( cursor_pos );
               return;
            }
         }
      }

      // No link or node was selected.
      SelectGlgObject( null, 0 );    // Unselect.
      Update();	    
   }

   ////////////////////////////////////////////////////////////////////////
   public void DraggingHandler( int event_type, GlgPoint cursor_pos )
   {
      switch( event_type )
      {
       case MouseEvent.MOUSE_RELEASED:
         ResetModes();
         return;

       case MouseEvent.MOUSE_DRAGGED:
         if( SelectedObjectType == NODE )
           SelectedObject.MoveObject( GlgObject.SCREEN_COORD, 
                                     start_point, cursor_pos );
         else
           MoveLink( SelectedObject, start_point, cursor_pos );

         Update();

         Object data = GetData( SelectedObject );
         if( data != null )
            if( data instanceof GlgNodeData )
            {
               // Update the X and Y in the product's data struct.
               UpdateNodePosition( SelectedObject, (GlgNodeData) data );
               
               // Don't need to update the attached links' points, since 
               // the stored positions of the first and last points are
               // not used: they are constrained to nodes and positioned 
                 // by them.
            }
            else   // LinkData
            {
               GlgLinkData link_data = (GlgLinkData) data;

               GlgNodeData start_node = link_data.getStartNode();
               if( start_node != null )
                 UpdateNodePosition( start_node.graphics, null );

               GlgNodeData end_node = link_data.getEndNode();
               if( end_node != null )
                 UpdateNodePosition( end_node.graphics, null );

               // Update stored point values
               StorePointData( link_data, SelectedObject );
            }

         // Update the start point for the next move
         start_point.CopyFrom( cursor_pos );
         break;
      }
   }

   ////////////////////////////////////////////////////////////////////////
   // If the link is attached to nodes that use reference objects, moving the
   // link moves the nodes, with no extra actions required. However, the link
   // can be connected to a node with multiple attachment points which doesn't
   // use reference object. Moving such a link would move just the attachment
   // points, but not the nodes. To avoid this, unsconstrain the end points
   // of the link not to spoil the attachment points' geometry, move the link,
   // move the nodes, then constrain the link points back.
   ////////////////////////////////////////////////////////////////////////
   void MoveLink( GlgObject link, GlgPoint start_point, GlgPoint end_point )
   {
      GlgObject 
        start_node = null,
        end_node = null;
      int type1 = GlgObject.REFERENCE, type2 = GlgObject.REFERENCE;

      GlgLinkData link_data = (GlgLinkData) GetData( link );

      if( link_data.getStartNode() != null )
      {         
         start_node = link_data.getStartNode().graphics;
         type1 = start_node.GetDResource( "Type" ).intValue();
      }
      
      if( link_data.getEndNode() != null )
      {
         end_node = link_data.getEndNode().graphics;
         type2 = end_node.GetDResource( "Type" ).intValue();
      }

      if( type1 == GlgObject.REFERENCE && type2 == GlgObject.REFERENCE )
      {
         // Nodes are reference objects (or null), moving the link moves nodes.
         link.MoveObject( GlgObject.SCREEN_COORD, start_point, end_point );
      }
      else   // Nodes with multiple attachment points.
      {
         // Unconstrain link points
         GlgObject point1 = null, point2 = null;

         if( start_node != null )
           point1 = UnConstrainLinkPoint( link, false );
         if( end_node != null )
           point2 = UnConstrainLinkPoint( link, true );

         DetachFramePoints( link );
      
         // Move the link
         link.MoveObject( GlgObject.SCREEN_COORD, start_point, end_point );
      
         // Move start node, then reattach the link.
         if( start_node != null )
         {
            start_node.MoveObject( GlgObject.SCREEN_COORD, 
                                  start_point, end_point );
            ConstrainLinkPoint( link, point1, false );
         }

         // Move end node, then reattach the link.
         if( end_node != null )
         {
            end_node.MoveObject( GlgObject.SCREEN_COORD, 
                                start_point, end_point );
            ConstrainLinkPoint( link, point2, true );
         }

         AttachFramePoints( link );
      }
   }

   ////////////////////////////////////////////////////////////////////////
   void UpdateNodePosition( GlgObject node, GlgNodeData node_data )
   {
      if( node_data == null )
        node_data = (GlgNodeData) GetData( node );

      GetPosition( node, world_coord );
      node_data.position.x = world_coord.x;
      node_data.position.y = world_coord.y;
   }

   ////////////////////////////////////////////////////////////////////////
   public GlgObject GetObjectsAtCursor( GlgObject viewport, GlgObject event_vp,
                                       GlgPoint cursor_pos )
   {
      // Select all objects in the vicinity of the +-SELECTION_RESOLUTION 
      // pixels from the actual mouse click position.
      select_rect.p1.x = cursor_pos.x - SELECTION_RESOLUTION;
      select_rect.p1.y = cursor_pos.y - SELECTION_RESOLUTION;
      select_rect.p2.x = cursor_pos.x + SELECTION_RESOLUTION;
      select_rect.p2.y = cursor_pos.y + SELECTION_RESOLUTION;

      return
        GlgObject.CreateSelection( /** top viewport **/ viewport, select_rect,
                                  /** event viewport **/ event_vp );
   }

   ////////////////////////////////////////////////////////////////////////
   public void InputCallback( GlgObject viewport, GlgObject message_obj )
   {
      if( !IsReady )
        return;
      
      String format      = message_obj.GetSResource( "Format" );
      String origin      = message_obj.GetSResource( "Origin" );
      String full_origin = message_obj.GetSResource( "FullOrigin" );
      String action      = message_obj.GetSResource( "Action" );
      // String subaction   = message_obj.GetSResource( "SubAction" );

      // Handle Dialog window closing.
      if( format.equals( "Window" ) && action.equals( "DeleteWindow" ) &&
         origin.equals( "Dialog" ) )
      {
         // Close the dialog window.
         Viewport.SetDResource( "Dialog/Visibility", 0. );
         Update();
      }
      else if( format.equals( "Button" ) )
      {
         if( !action.equals( "Activate" ) && !action.equals( "ValueChanged" ) )
           return;

         ResetModes();
            
         if( origin.equals( "Save" ) )
           Save( CurrentDiagram );
         else if( origin.equals( "Insert" ) )
           Load();
         else if( origin.equals( "Print" ) )
           Print();
         else if( origin.equals( "Cut" ) )
           Cut();
         else if( origin.equals( "Paste" ) )
           Paste();
         else if( origin.equals( "Exit" ) )
           System.exit( 0 );
         else if( origin.equals( "ZoomIn" ) )
         {
            ResetModes();
            MainArea.SetZoom( null, 'i', 0. );
         }
         else if( origin.equals( "ZoomOut" ) )
         {
            ResetModes();
            MainArea.SetZoom( null, 'o', 0. );
         }
         else if( origin.equals( "ZoomTo" ) )
         {
            ResetModes();
            MainArea.SetZoom( null, 't', 0. );
         }
         else if( origin.equals( "ZoomReset" ) )
         {
            ResetModes();
            MainArea.SetZoom( null, 'n', 0. );
         }
         else if( origin.startsWith( "IconButton" ) )
         {
            GlgObject button = viewport.GetResourceObject( full_origin );
            GlgObject icon = button.GetResourceObject( "Icon" );
            if( icon == null )
            {
               SetError( "Can't find icon." );
               return;
            }

            // Object to use in the drawing. In case of connectors, uses only a
            // part of the icon (the connector object) without the end markers.
              //
            GlgObject object = icon.GetResourceObject( "Object" );
            if( object == null )
              object = icon;

            String icon_type = object.GetSResource( "IconType" );
            if( icon_type == null )
            {
               SetError( "Can't find IconType resource." );
               return;
            }
            
            if( icon_type.equals( "Select" ) )
              ResetModes();

            else if( icon_type.equals( "Link" ) )
              AddLink( full_origin, object );
         
            else if( icon_type.equals( "Node" ) )
              AddNode( full_origin, object );
         }
         else if( origin.equals( "Properties" ) )
         {
            // You can use a custom Java-based dialog to display properties
              // of the selected mode or link.
            FillData();
            Viewport.SetDResource( "Dialog/Visibility", 1. );
         }
         else if( origin.equals( "CreateMode" ) )
         { 
            ResetModes();
            SetCreateMode();
         }
         else if( origin.equals( "DialogApply" ) )
           ApplyData();
         else if( origin.equals( "DialogClose" ) )
         {
            if( ApplyData() )
              // Close the dialog if successfully applied.
              Viewport.SetDResource( "Dialog/Visibility", 0. );
         }
         else if( origin.equals( "DialogCancel" ) )
         {
            // Close the dialog without applying the data.
            Viewport.SetDResource( "Dialog/Visibility", 0. );
         }
         // The rest of buttons exist in and are used for process diagrams 
           // only
         else if( origin.equals( "DataSourceSelect" ) )
           viewport.SetDResource( "DataSourceDialog/Visibility", 1. );
         else if( origin.equals( "DataSourceClose" ) )
           viewport.SetDResource( "DataSourceDialog/Visibility", 0. );
         else if( origin.equals( "DataSourceApply" ) )
         {
            String string = 
              viewport.GetSResource( "DataSourceList/SelectedItem" );
            viewport.SetSResource( "Dialog/DialogDataSource/TextString",
                                  string );
         }
         
         Update();
      }
   }

   ////////////////////////////////////////////////////////////////////////
   void SelectGlgObject( GlgObject object, int selected_type )
   {
      String name;

      if( object == SelectedObject )
        return;   // No change
      
      if( last_color != null ) // Restore the color of previously selected node
      {
         last_color.SetResourceFromObject( null, StoredColor );
         last_color = null;
      }

      SelectedObject = object;
      SelectedObjectType = selected_type;

      // Show object selection
      if( object != null )
      {
         // Change color to highlight selected node or link.
         if( object.HasResourceObject( "SelectColor" ) )
         {
            last_color = object.GetResourceObject( "SelectColor" );

            // Store original color
            StoredColor.SetResourceFromObject( null, last_color );

            // Set color to red to highlight selection.
            last_color.SetGResource( null, 1., 0., 0. );
         }
         name = GetObjectLabel( SelectedObject );
      }
      else
        name = "NONE";

      // Display selected object name at the bottom.
      Viewport.SetSResource( "SelectedObject", name );

      FillData();
   }

   ////////////////////////////////////////////////////////////////////////
   void AddNode( String button_name, GlgObject node )
   {
      Mode = ADD_NODE;
      SetPrompt( "Position the node." );
      
      SetRadioBox( button_name );

      // Store Node type
      NodeType = node.GetDResource( "Index" ).intValue();
   }

   ////////////////////////////////////////////////////////////////////////
   void AddLink( String button_name, GlgObject link )
   {
      Mode = ADD_LINK1;
      SetPrompt( "Select the first node." );
      
      SetRadioBox( button_name );

      // Store Link type
      LinkType = link.GetDResource( "Index" ).intValue();

      ObjectInfo link_info = GetCPContainer( link );
      EdgeType = link_info.type;
   }

   ////////////////////////////////////////////////////////////////////////
   void Cut()
   {
      if( NoSelection() )
        return;

      // Disallow deleting a node without deleting the link first.
      if( SelectedObjectType == NODE && NodeConnected( SelectedObject ) )
      {
         SetError( "Remove links connected to the node before removing the node!" );
         return;
      }

      GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );

      if( group.ContainsObject( SelectedObject ) )
      {
         // Store the node or link in the cut buffer.
         CutBuffer = SelectedObject;
         CutBufferType = SelectedObjectType;

         // Delete the node
         group.DeleteObject( SelectedObject );

         // Delete the data
         Object data = GetData( SelectedObject );
         Vector list = null;

         CustomHandler.CutObjectCB( this, SelectedObject, data,
                                   SelectedObjectType == NODE );

         if( SelectedObjectType == NODE )
           list = (Vector) CurrentDiagram.getNodeList();
         else  // Link
           list = (Vector) CurrentDiagram.getLinkList();

         if( !list.removeElement( data ) )
           System.out.println( "Deleting data failed!" );

         SelectGlgObject( null, 0 );
      }
      else
        SetError( "Cut failed." );    
   }

   ////////////////////////////////////////////////////////////////////////
   void Paste()
   {
      if( CutBuffer == null )
      {
         SetError( "Empty cut buffer, cut some object first." );
         return;
      }
      
      GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );

      Object data = GetData( CutBuffer );
      Vector list = null;

      CustomHandler.PasteObjectCB( this, CutBuffer, data,
                                  CutBufferType == NODE );

      if( CutBufferType == NODE )
      {
         group.AddObjectToBottom( CutBuffer );     // In front
         list = CurrentDiagram.getNodeList();
      }
      else // LINK
      {
         group.AddObjectToTop( CutBuffer );        // Behind
         list = CurrentDiagram.getLinkList();
      }
      list.addElement( data );

      SelectGlgObject( CutBuffer, CutBufferType );

      // Allow pasting just once to avoid handling the data copy
      CutBuffer = null;
   }

   ////////////////////////////////////////////////////////////////////////
   void Save( GlgDiagramData diagram )
   {
      // Print all nodes and edges as a test
      Vector node_list = diagram.getNodeList();
      Vector link_list = diagram.getLinkList();

      int i;
      for( i=0; i<node_list.size(); ++i )
        System.out.println( "Node " + i + ", type " + 
                          ((GlgNodeData) node_list.elementAt( i )).node_type );

      for( i=0; i<link_list.size(); ++i )
      {
         GlgLinkData link = (GlgLinkData) link_list.elementAt( i );
         System.out.print( "Link " + i + ", type " + link.link_type );
         PrintLinkInfo( ", from node ", node_list,
                       link.start_node, link.start_point_name );
         PrintLinkInfo( " to node ", node_list,
                       link.end_node, link.end_point_name );
         System.out.println();
      }

      // Save the DiagramData class using either Java serialization,
      // writing out individual records, or any other custom save method.

      // Save the current diagram to use it as test for loading.
      SavedDiagram = diagram;

      // Empty the drawing area
      UnsetDiagram( diagram );
   }

   ////////////////////////////////////////////////////////////////////////
   void PrintLinkInfo( String label, Vector node_list, GlgNodeData node, 
                      String point_name )
   { 
      if( node == null )
        System.out.print( label + "null " );
      else
      {
         System.out.print( label + node_list.indexOf( node ) );
         
         // Print connection point info if not the default point.
         if( !point_name.equals( "Point" ) )
           System.out.print( ":" + point_name );
      }
   }

   ////////////////////////////////////////////////////////////////////////
   void Load()
   {
      // Load the DiagramClass using Java de-serialization, reading individual
      // records or any other custom load method matching the save method
        // used above.

      // In the demo, load the saved diagram and use it as a Save/Load test.
      if( SavedDiagram == null )
        SetError( "Save the diagram first." );
      else
      {
         UnsetDiagram( CurrentDiagram );
      
         // Load saved diagram
         SetDiagram( SavedDiagram );
         SavedDiagram = null;
      }
   }

   ////////////////////////////////////////////////////////////////////////
   void Print()
   {
      ResetModes();

      // May be used to export postscript, generate an image to be saved, 
      // or invoke native Java printing (see Glg Java print examples).

      // In an applet printing is handled by the browser's print button.
   }

   ////////////////////////////////////////////////////////////////////////
   void ResetModes()
   {
      if( Mode != 0 )
      {
         if( DragLink != null )
         {
            if( AllowUnconnectedLinks && FinishLink( DragLink ) )
              ;   // Keep the link even if its second end is not connected.
            else
            {
               GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );
               group.DeleteObject( DragLink );
            }
            DragLink = null;
         }

         EraseAttachmentPoints();
      }
      
      Mode = SELECT_OBJECT;
      SetRadioBox( "IconButton0" );  // Highlight Select button

      SetPrompt( "" );
      Update();
   }

   ////////////////////////////////////////////////////////////////////////
   // If AllowUnconnectedLinks=true, keep the link if it has at least two 
   ////////////////////////////////////////////////////////////////////////
   boolean FinishLink( GlgObject link )
   {
      ObjectInfo link_info = GetCPContainer( link );

      GlgObject point_container = link_info.object;

      int edge_type = link_info.type;
      if( edge_type == GlgObject.ARC )
        return false;      // Disconnected arc links are not allowed.

      int size = point_container.GetSize();

      // The link must have at least two points already defined, and one extra
        // point that was added to drag the next point.
      if( size < 3 )
        return false;

      // Delete the unfinished, unconnected point.
      GlgObject suspend_info = link.SuspendObject();
      point_container.DeleteBottomObject();
      link.ReleaseObject( suspend_info );

      FinalizeLink( link );
      return true;
   }

   ////////////////////////////////////////////////////////////////////////
   void SetPrompt( String string )
   {
      Viewport.SetSResource( "Prompt/String", string );
      Update();
   }

   ////////////////////////////////////////////////////////////////////////
   void SetError( String string )
   {
      Bell();
      SetPrompt( string );
   }

   ////////////////////////////////////////////////////////////////////////
   boolean NoSelection()
   {
      if( SelectedObject != null )
        return false;
      else
      {
         SetError( "Select some object first." );
         return true;
      }
   }

   ////////////////////////////////////////////////////////////////////////
   GlgObject AddNodeAt( int node_type, GlgNodeData node_data, GlgPoint pos, 
                  int coord_system )
   {     
      boolean store_position;

      if( node_data == null )
      {
         node_data = new GlgNodeData();
         node_data.node_type = node_type;

         // Add node data to the node list
         Vector node_list = CurrentDiagram.getNodeList();
         node_list.addElement( node_data );

         if( ProcessDiagram )
         {
            // Assign an arbitrary datasource initially.
            node_data.datasource = "DataSource" + DataSourceCounter;
         
            ++DataSourceCounter;
            if( DataSourceCounter >= NumDatasources )
              DataSourceCounter = 0;
         }
      }

      // Create the node based on the node type
      GlgObject new_node = CreateNode( node_data );

      // Make label visible and set its string.
      if( new_node.HasResourceObject( "Label" ) )
      {
         new_node.SetDResource( "Label/Visibility", 1. );
         new_node.SetSResource( "Label/String", node_data.object_label );
      }

      // Store datasource as a tag of the node's Value resource, if it exists.
      if( ProcessDiagram )
      {
         GlgObject value_obj = new_node.GetResourceObject( "Value" );
         String datasource = node_data.datasource;
         
         if( value_obj != null && 
            datasource != null && datasource.length() != 0  )
         {
            GlgObject tag_obj = new GlgTag( "Value", datasource,  null );
            value_obj.SetResourceObject( "TagObject", tag_obj );
         }
      }

      // No cursor pos: get pos from the data struct.
      if( pos == null )
      {
         pos = new GlgPoint( node_data.position.x, node_data.position.y, 0. );
         store_position = false;
      }
      else
         store_position = true;

      node_data.graphics = new_node;  // Pointer from data struct to graphics

      // Add the object to the drawing first, so that it's hierarchy is setup
        // for positioning it.
      GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );
      group.AddObjectToBottom( new_node );

      // Transform the object to set its size and position.
      PlaceObject( new_node, pos, coord_system, world_coord );

      if( store_position )
      {
         node_data.position.x = world_coord.x;
         node_data.position.y = world_coord.y;
      }
      return new_node;
   }

   ////////////////////////////////////////////////////////////////////////
   GlgObject CreateNode( GlgNodeData node_data )
   {
      // Get node template from the palette
      GlgObject new_node = (GlgObject)
        NodeObjectArray.GetElement( node_data.node_type );
         
      // Create a new node instance 
      new_node = new_node.CloneObject( GlgObject.STRONG_CLONE );

      // Name node using an "object" prefix (used to distiguish
        // nodes from links on selection).
      new_node.SetSResource( "Name", "object" );

      AddCustomData( new_node, node_data );

      if( ProcessDiagram )
      {
         // Init label data using node's InitLabel if exists.
         if( new_node.HasResourceObject( "InitLabel" ) )
           node_data.object_label = new_node.GetSResource( "InitLabel" );
      }

      return new_node;
   }

   ////////////////////////////////////////////////////////////////////////
   GlgObject CreateLink( GlgLinkData link_data )
   {
      int num_points;

      // Get link template from the palette
      GlgObject new_link = (GlgObject)
        LinkObjectArray.GetElement( link_data.link_type );
         
      // Create a new link instance 
      new_link = new_link.CloneObject( GlgObject.STRONG_CLONE );

      // Name link using a "link" prefix (used to distiguish
        // links from nodes on selection).
      new_link.SetSResource( "Name", "link" );

      // If point_array exists, create/add middle link points
      // If not an arc, it's created with 2 points by default, 
      // add ( num_points - 2 ) more. If it's an arc, AddLinkPoints
        // will do nothing.
      if( link_data.point_array != null &&
         ( num_points = link_data.point_array.size() ) > 2 )
        AddLinkPoints( new_link, num_points - 2 );

      AddCustomData( new_link, link_data );

      return new_link;
   }

   /////////////////////////////////////////////////////////////////////////
   // Connects the first or last point of the link.
   /////////////////////////////////////////////////////////////////////////
   void ConstrainLinkPoint( GlgObject link, GlgObject point, 
                           boolean last_point )
   {
      ObjectInfo link_info = GetCPContainer( link );

      GlgObject point_container = link_info.object;

      GlgObject link_point = (GlgObject) 
        point_container.GetElement(  
                ( last_point ? point_container.GetSize() - 1 : 0 ) );
   
      GlgObject suspend_info = link.SuspendObject();
      link_point.ConstrainObject( point );
      link.ReleaseObject( suspend_info );

      // Store the point name for save/load
      String point_name = point.GetSResource( "Name" );
      if( point_name == null || point_name.length() == 0 )
        point_name = "Point";
   
      GlgLinkData link_data = (GlgLinkData) GetData( link );
      if( last_point )
        link_data.end_point_name = point_name;
      else
        link_data.start_point_name = point_name;
   }


   /////////////////////////////////////////////////////////////////////////
   // Positions the arc's middle point if it's not explicitly defined.
   /////////////////////////////////////////////////////////////////////////
   void SetArcMiddlePoint( GlgObject link )
   {
      ObjectInfo link_info = GetCPContainer( link );
      GlgObject point_container = link_info.object;
      int edge_type = link_info.type;

      if( edge_type != GlgObject.ARC )
        return;

      // Offset the arc's middle point if wasn't set.
      GlgObject start_point = (GlgObject) point_container.GetElement( 0 );
      GlgObject middle_point = (GlgObject) point_container.GetElement( 1 );
      GlgObject end_point = (GlgObject) point_container.GetElement( 2 );

      GlgPoint pt1 = start_point.GetGResource( null );
      GlgPoint pt2 = end_point.GetGResource( null );

      // Offset the middle point.
      middle_point.SetGResource( null,
              ( pt1.x + pt2.x ) / 2. + ( pt1.y - pt2.y != 0. ? 50. : 0. ),
              ( pt1.y + pt2.y ) / 2. + ( pt1.y - pt2.y != 0. ? 0. : 50. ),
              ( pt1.z + pt2.z ) / 2. );
   }

   /////////////////////////////////////////////////////////////////////////
   // Handles links with labels: constrains frame's points to the link's 
   // points.
   /////////////////////////////////////////////////////////////////////////
   void AttachFramePoints( GlgObject link )
   {
      GlgObject
        frame,
        link_point_container,
        frame_point_container,
        link_start_point,
        link_end_point,
        frame_start_point,
        frame_end_point,
        suspend_info;

      frame = link.GetResourceObject( "Frame" );
      if( frame == null ) // Link without label and frame
        return;
      
      ObjectInfo link_info = GetCPContainer( link );
      link_point_container = link_info.object;

      // Always use the first segment of the link to attach the frame.
      link_start_point = (GlgObject) link_point_container.GetElement( 0 );
      link_end_point = (GlgObject) link_point_container.GetElement( 1 );
      
      frame_point_container = frame.GetResourceObject( "CPArray" );
      int size = frame_point_container.GetSize();
      frame_start_point =(GlgObject)  frame_point_container.GetElement( 0 );
      frame_end_point =
        (GlgObject) frame_point_container.GetElement( size - 1 );
      
      suspend_info = link.SuspendObject();
      
      frame_start_point.ConstrainObject( link_start_point );
      frame_end_point.ConstrainObject( link_end_point );
      
      link.ReleaseObject( suspend_info );
   }

   /////////////////////////////////////////////////////////////////////////
   // Disconnects the first or last point of the link. Returns the object 
   // the link is connected to.
   /////////////////////////////////////////////////////////////////////////
   GlgObject UnConstrainLinkPoint( GlgObject link, boolean last_point )
   {
      ObjectInfo link_info = GetCPContainer( link );

      GlgObject point_container = link_info.object;

      GlgObject link_point = (GlgObject)
        point_container.GetElement(
                ( last_point ? point_container.GetSize() - 1 : 0 ) );
      GlgObject attachment_point = link_point.GetResourceObject( "Data" );

      GlgObject suspend_info = link.SuspendObject();
      link_point.UnconstrainObject();
      link.ReleaseObject( suspend_info );

      return attachment_point;
   }
   
   /////////////////////////////////////////////////////////////////////////
   // Detaches the first and last points of the frame.
   /////////////////////////////////////////////////////////////////////////
   void DetachFramePoints( GlgObject link )
   {
      GlgObject
        frame,
        frame_point_container,
        frame_start_point,
        frame_end_point,
        suspend_info;
      
      frame = link.GetResourceObject( "Frame" );
      if( frame == null ) // Link without label and frame
        return;
      
      frame_point_container = frame.GetResourceObject( "CPArray" );
      int size = frame_point_container.GetSize(); 
      frame_start_point = (GlgObject) frame_point_container.GetElement( 0 );
      frame_end_point =
        (GlgObject) frame_point_container.GetElement( size - 1 );
      
      suspend_info = link.SuspendObject();
      
      frame_start_point.UnconstrainObject();
      frame_end_point.UnconstrainObject();
      
      link.ReleaseObject( suspend_info );
   }

   /////////////////////////////////////////////////////////////////////////
   // Set last point of the link (dragging).
   /////////////////////////////////////////////////////////////////////////
   void SetLastPoint( GlgObject link, GlgPoint cursor_pos, 
                     boolean arc_middle_point )
   {
      GlgObject point;

      ObjectInfo link_info = GetCPContainer( link );

      GlgObject point_container = link_info.object;

      if( arc_middle_point )
        // Setting the middle point of an arc.
        point = (GlgObject) point_container.GetElement( 1 );
      else
        // Setting the last point.
        point = (GlgObject)
          point_container.GetElement( point_container.GetSize() - 1 );

      MainArea.ScreenToWorld( true, cursor_pos, world_coord );
      point.SetGResource( null, world_coord );
   }

   /////////////////////////////////////////////////////////////////////////
   void AddLinkPoints( GlgObject link, int num_points )
   {
      ObjectInfo link_info = GetCPContainer( link );
      if( link_info.type == GlgObject.ARC )
        return; // Arc connectors have fixed number of points: don't add.

      GlgObject point_container = link_info.object;

      GlgObject point = (GlgObject) point_container.GetElement( 0 );

      GlgObject suspend_info = link.SuspendObject();
      for( int i=0; i<num_points; ++i )
      {
         GlgObject add_point = point.CloneObject( GlgObject.FULL_CLONE );
         point_container.AddObjectToBottom( add_point );
      }
      link.ReleaseObject( suspend_info );
   }

   /////////////////////////////////////////////////////////////////////////
   // Set the direction of the recta-linera connector depending on the 
   // direction of the first mouse move.
   /////////////////////////////////////////////////////////////////////////
   void SetEdgeDirection( GlgObject link, 
                         GlgPoint start_pos, GlgPoint end_pos )
   {
      int direction;

      ObjectInfo link_info = GetCPContainer( link );
      int edge_type = link_info.type;

      if( edge_type == GlgObject.ARC || edge_type == 0 )   // Arc or polygon
        return;

      if( Math.abs( start_pos.x - end_pos.x ) > 
         Math.abs( start_pos.y - end_pos.y ) )
        direction = GlgObject.HORIZONTAL;
      else
        direction = GlgObject.VERTICAL;
      link.SetDResource( "EdgeDirection", (double) direction );

      GlgLinkData link_data = (GlgLinkData) GetData( link );
      link_data.link_direction = direction;
   }

   ////////////////////////////////////////////////////////////////////////
   GlgObject AddLink( int link_type, GlgLinkData link_data )
   {     
      GlgObject link;

      if( link_data == null )    // Creating a new link interactively
      {	 
         link_data = new GlgLinkData();
         link_data.link_type = link_type;

         link = CreateLink( link_data );

         // Store color
         link_data.link_color = 
           new GlgDiagramPoint( link.GetGResource( "EdgeColor" ) );
                  
         // Don't add link data to the link list or store points: 
         // will be done when finished creating the link.
      }
      else  // Creating a link from data on load.
      {
         link = CreateLink( link_data );

         // Set color
         link.SetGResource( "EdgeColor", link_data.link_color );

         // Enable arrow type if defined 
         GlgObject arrow_type = link.GetResourceObject( "ArrowType" );
         if( arrow_type != null )
           arrow_type.SetDResource( null,
                                   (double) GlgObject.MIDDLE_FILL_ARROW );

         // Restore connector direction if recta-linear
         GlgObject direction = link.GetResourceObject( "EdgeDirection" );
         if( direction != null )
           direction.SetDResource( null, (double) link_data.link_direction );

         // Constrain end points to start and end nodes 
         GlgNodeData start_node = link_data.getStartNode();
         if( start_node != null )
         {
            GlgObject node1 = start_node.graphics;
            GlgObject point1 = 
              node1.GetResourceObject( link_data.start_point_name  );
            ConstrainLinkPoint( link, point1, false ); // First point
         }
         
         GlgNodeData end_node = link_data.getEndNode();
         if( end_node != null )
         {
            GlgObject node2 = end_node.graphics;         
            GlgObject point2 = 
              node2.GetResourceObject( link_data.end_point_name );         
            ConstrainLinkPoint( link, point2, true ); // Last point
         }

         AttachFramePoints( link );

         RestorePointData( link_data, link );
      }

      // Display the label if it's a link with a label.
      if( link.HasResourceObject( "Label" ) )
        link.SetSResource( "Label/String", link_data.object_label );

      link_data.graphics = link;     // Pointer from data struct to graphics

      // Add to the top of the draw list to be behind other objects.
      GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );
      group.AddObjectToTop( link );

      return link;
   }

   ////////////////////////////////////////////////////////////////////////
   // Set the object size and position.
   ////////////////////////////////////////////////////////////////////////
   void PlaceObject( GlgObject node, GlgPoint pos, int coord_type, 
                    GlgPoint world_coord )
   {
      int type;

      // World coordinates of the node are returned to be stored in the node's
        // data structure.
      if( coord_type == GlgObject.SCREEN_COORD ) 
        MainArea.ScreenToWorld( true, pos, world_coord );
      else
        world_coord.CopyFrom( pos );

      type = node.GetDResource( "Type" ).intValue();
      if( type == GlgObject.REFERENCE )
      {
         // Reference: can use its point to position it.
         node.SetGResource( "Point", world_coord );

         if( IconScale != 1. )   // Change node size if required.
           // Scale object around the origin, which is now located at pos.
           node.ScaleObject( coord_type, pos, IconScale, IconScale, 1. );
      }
      else
      {
         // Arbitrary object: move its box's center to the cursor position.
         node.PositionObject( coord_type, 
                             GlgObject.HCENTER | GlgObject.VCENTER,
                             pos.x, pos.y, pos.z );

         if( IconScale != 1. )   // Change node size if required.
           // Scale object around the center of it's bounding box.
           node.ScaleObject( coord_type, null, IconScale, IconScale, 1. );
      }
   }
      
   ////////////////////////////////////////////////////////////////////////
   // Get the link's control points container based on the link type.   
   ////////////////////////////////////////////////////////////////////////
   ObjectInfo GetCPContainer( GlgObject link )
   {
      ObjectInfo rval;
      int link_type;

      link_type = link.GetDResource( "Type" ).intValue();
      switch( link_type )
      {
       case GlgObject.POLYGON:
         rval = new ObjectInfo();
         rval.object = link;
         rval.type = 0;
         break;

       case GlgObject.GROUP: // Group containing a polygon with a label
         return GetCPContainer( link.GetResourceObject( "Link" ) );

       case GlgObject.CONNECTOR:
         rval = new ObjectInfo();
         rval.object = link;
         rval.type = link.GetDResource( "EdgeType" ).intValue();
         break;

       default: SetError( "Invalid link type." ); return null;
      }
      return rval;
   }

   ////////////////////////////////////////////////////////////////////////
   // Determines what node or link the object belongs to and returns it. 
   // Also returns type of the object: NODE or LINK.
   ////////////////////////////////////////////////////////////////////////
   ObjectInfo GetSelectedObject( GlgObject object )
   {
      while( object != null )
      {
         // Check if the object has IconType.
         if( object.HasResourceObject( "IconType" ) )
         {
            String type_string = object.GetSResource( "IconType" );
            if( type_string.equals( "Link" ) )
              return new ObjectInfo( object, LINK );	   
            else if( type_string.equals( "Node" ) )
              return new ObjectInfo( object, NODE );	   
         }

         object = object.GetParent();
      }

      // No node/link parent found - no selection.
      return new ObjectInfo( null, NO_OBJ );
   }

   ////////////////////////////////////////////////////////////////////////
   // Returns an array of all attachment points, i.e. the points whose 
   // names start with the name_prefix.
   ////////////////////////////////////////////////////////////////////////
   GlgObject GetAttachmentPoints( GlgObject sel_object, String name_prefix )
   {
      GlgObject pt_array = sel_object.CreatePointArray( 0 );
      if( pt_array == null )
        return null;

      int size = pt_array.GetSize();
      GlgObject attachment_pt_array = 
        new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );

      // Add points that start with the name_prefix to attachment_pt_array.
      for( int i=0; i<size; ++i )
      {
         GlgObject point = (GlgObject) pt_array.GetElement( i );
         String name = point.GetSResource( "Name" );
         if( name != null && name.startsWith( name_prefix ) )
           attachment_pt_array.AddObjectToBottom( point );
      }

      if( attachment_pt_array.GetSize() == 0 )
        attachment_pt_array = null;

      return attachment_pt_array;
   }

   ////////////////////////////////////////////////////////////////////////
   // Checks if one of the point array's points is under the cursor.
   ////////////////////////////////////////////////////////////////////////
   GlgObject GetSelectedPoint( GlgObject point_array, GlgPoint cursor_pos )
   {
      if( point_array == null )
        return null;

      int size = point_array.GetSize();

      for( int i=0; i<size; ++i )
      {
         GlgObject point = (GlgObject) point_array.GetElement( i );

         // Get position in screen coords.
         GlgPoint screen_pos = point.GetGResource( "XfValue" );
         if( Math.abs( cursor_pos.x - screen_pos.x ) < 
                                                 POINT_SELECTION_RESOLUTION &&
             Math.abs( cursor_pos.y - screen_pos.y ) <
                                                 POINT_SELECTION_RESOLUTION )
           return point;	
      }
      return null;
   }

   ////////////////////////////////////////////////////////////////////////
   void SetDiagram( GlgDiagramData diagram )
   {
      int i;

      Vector node_list = diagram.getNodeList();
      Vector link_list = diagram.getLinkList();
      
      for( i=0; i<node_list.size(); ++i )
      {
         GlgNodeData node_data = (GlgNodeData) node_list.elementAt( i );
         AddNodeAt( 0, node_data, null, GlgObject.PARENT_COORD );
      }

      for( i=0; i<link_list.size(); ++i )
      {
         GlgLinkData link_data = (GlgLinkData) link_list.elementAt( i );
         AddLink( 0, link_data );
      }

      CurrentDiagram = diagram;
      Update();
   }

   ////////////////////////////////////////////////////////////////////////
   void UnsetDiagram( GlgDiagramData diagram )
   {
      int i;

      SelectGlgObject( null, 0 );
      
      Vector node_list = diagram.getNodeList();
      Vector link_list = diagram.getLinkList();
      
      GlgObject group = MainArea.GetResourceObject( "ObjectGroup" );

      for( i=0; i<node_list.size(); ++i )
      {
         GlgNodeData node_data = (GlgNodeData) node_list.elementAt( i );
         if( node_data.graphics != null )
           group.DeleteObject( node_data.graphics );
      }

      for( i=0; i<link_list.size(); ++i )
      {
         GlgLinkData link_data = (GlgLinkData) link_list.elementAt( i );
         if( link_data.graphics != null )
            group.DeleteObject( link_data.graphics );
      }

      CurrentDiagram = new GlgDiagramData();
      Update();
   }

   //////////////////////////////////////////////////////////////////////////
   // Fills the object palette with buttons containing node and link icons
   // from the palette template. Palette template is a convenient place to 
   // edit all icons instead of placing them into the object palette buttons. 
   //
   // Icons named "Node0", "Node1", etc. were extracted into the NodeIconArray.
   // The LinkIconArray contains icons named "Link0", "Link1", etc.
   // Here, we place all node and link icons inside the object palette buttons 
   // named IconButton<N>, staring with the start_index to skip the first 
   // button which already contains the select button. 
   // The palette buttons are created by copying an empty template button.
   // Parameters:
   //  palette_name       Name of the object palette to add buttons to.
   //  button_name        Base name of the object palette buttons.
   //  start_index        Number of buttons to skip (the first button
   //                     with the select icon is already in the palette).
   //////////////////////////////////////////////////////////////////////////
   void FillObjectPalette( String palette_name, String button_name, 
                          int start_index )
   {
      GlgObject palette = Viewport.GetResourceObject( "ObjectPalette" );

      // Find and store an empty palette button used as a template.
      // Search the button at the top viewport level, since palette's
        // HasResources=NO.
      ButtonTemplate = Viewport.GetResourceObject( button_name + start_index );
      
      if( ButtonTemplate == null )
      {
         SetError( "Can't find palette button to copy!" );
         return;
      }
      
      // Delete the template button from the palette but keep it around.
      palette.DeleteObject( ButtonTemplate );

      // Store NumRows and NumColumns info.
      NumRows = ButtonTemplate.GetDResource( "NumRows" ).intValue();
      NumColumns = ButtonTemplate.GetDResource( "NumColumns" ).intValue();

      // Add all icons from each array, increasing the start_index. */
      start_index = 
        FillObjectPaletteFromArray( palette, button_name, start_index,
                                   LinkIconArray, LinkObjectArray, "Link" );
      start_index = 
        FillObjectPaletteFromArray( palette, button_name, start_index,
                                   NodeIconArray, NodeObjectArray, "Node" );

      // Store the marker template for attachment points feedback.
      PointMarker = PaletteTemplate.GetResourceObject( "PointMarker" );
            
      // Cleanup
      ButtonTemplate = null;
      PaletteTemplate = null;
      NodeIconArray = null;
      LinkIconArray = null;
   }

   //////////////////////////////////////////////////////////////////////////
   // Adds object palette buttons containing all icons from an array.
   // icon_array is an array of icon objects to use in the palette button.
   // object_array is an array of objects to use in the drawing.
   //////////////////////////////////////////////////////////////////////////
   int FillObjectPaletteFromArray( GlgObject palette, String button_name, 
                                  int start_index, 
                                  GlgObject icon_array, GlgObject object_array,
                                  String default_tooltip )
   {
      // Add all icons from the icon array to the palette using a copy of 
        // the template button.
      int size = icon_array.GetSize();
      int button_index = start_index;
      for( int i=0; i<size; ++i )
      { 
         GlgObject icon = (GlgObject) icon_array.GetElement( i );
         GlgObject object = (GlgObject) object_array.GetElement( i );

         // Set uniform icon name to simplify selection.
         icon.SetSResource( "Name", "Icon" );

         // For nodes, set initial label.
         if( default_tooltip.equals( "Node" ) && 
            object.HasResourceObject( "Label" ) )
         {
            String string;
            if( object.HasResourceObject( "InitLabel" ) )
              string = object.GetSResource( "InitLabel" );
            else
              string = "";

            object.SetSResource( "Label/String", string );
         }

         // Create a button to hold the icon.
         GlgObject button = 
           ButtonTemplate.CloneObject( GlgObject.STRONG_CLONE ); 

         // Set button name by appending its index as a suffix (IconButtonN).
         button.SetSResource( "Name", button_name +button_index );

         // Set tooltip string.
         GlgObject tooltip = icon.GetResourceObject( "TooltipString" );
         if( tooltip != null )
           // Use a custom tooltip from the icon if defined.
           button.SetResourceFromObject( "TooltipString", tooltip );
         else   // Use the supplied default tooltip.
           button.SetSResource( "TooltipString", "Node" );

         // Position the button by setting row and column indices.
         button.SetDResource( "RowIndex",
                             (double) ( button_index / (int) NumColumns ) );
         button.SetDResource( "ColumnIndex",
                             (double) ( button_index % (int) NumColumns ) );

         // Zoom palette icon button to scale icons displayed in it. 
         // Preliminary zoom by 10 for better fitting, will be precisely 
           // adjusted later. 
         button.SetDResource( "Zoom", DEFAULT_ICON_ZOOM_FACTOR );

         button.AddObjectToBottom( icon );

         palette.AddObjectToBottom( button );
         ++button_index;
      }

      return button_index; /* Return the next start index. */
   }
      
   //////////////////////////////////////////////////////////////////////////
   // Positions node icons inside the palette buttons.
   // Invoked after the drawing has been setup, which is required by 
   // PositionObject().
   // Parameters:
   //  button_name        Base name of the palette buttons.
   //  start_index        Number of buttons to skip (the select and link
   //                     buttons are already in the palette).
   //////////////////////////////////////////////////////////////////////////
   void SetupObjectPalette( String button_name, int start_index )
   {
      // Find icons in the palette template and add them to the palette,
      // using a copy of the template button.
      for( int i = start_index; ; ++i )
      {      
         GlgObject button = Viewport.GetResourceObject( button_name + i );

         if( button == null )
           return;    // No more buttons

         GlgObject icon = button.GetResourceObject( "Icon" );
         int type = icon.GetDResource( "Type" ).intValue();      

         if( type == GlgObject.REFERENCE )
           icon.SetGResource( "Point", 0., 0., 0. );  // Center position
         else
           icon.PositionObject( GlgObject.PARENT_COORD,
                               GlgObject.HCENTER | GlgObject.VCENTER,
                               0., 0., 0. );    // Center position

         double zoom_factor = GetIconZoomFactor( button, icon );

         // Query an additional icon scale factor if defined in the icon.
         if( icon.HasResourceObject( "IconScale" ) )
           zoom_factor *= icon.GetDResource( "IconScale" ).doubleValue();

         // Zoom palette icon button to scale icons displayed in it.
         button.SetDResource( "Zoom", zoom_factor );
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Returns a proper zoom factor to precisely fit the icon in the button.
   // Used for automatic fitting if FitIcons = true.
   //////////////////////////////////////////////////////////////////////////
   double GetIconZoomFactor( GlgObject button, GlgObject icon )
   {
      if( !FitIcons )
        return DEFAULT_ICON_ZOOM_FACTOR;
      
      GlgPoint 
        point1 = new GlgPoint(), 
        point2 = new GlgPoint();
      double
        extent_x, extent_y, extent,
        zoom_factor;
      
      zoom_factor = button.GetDResource( "Zoom" ).doubleValue();
      
      GlgCube box = icon.GetBoxPtr();
      button.ScreenToWorld( true, box.p1, point1 );
      button.ScreenToWorld( true, box.p2, point2 );
      
      extent_x = Math.abs( point1.x - point2.x );
      extent_y = Math.abs( point1.y - point2.y );
      extent = Math.max( extent_x, extent_y );
        
      // Increase zoom so that the icon fills the percentage of the button
        // defined by the ICON_FIT_FACTOR. 
      zoom_factor = 2000. / extent * ICON_FIT_FACTOR;
      return zoom_factor;
   }

   //////////////////////////////////////////////////////////////////////////
   // Queries items in the palette and fills array of node or link icons.
   // For each palette item, an icon is added to the icon_array, and the 
   // object to be used in the drawing is added to the object_array.
   // In case of connectors, the object uses only a part of the icon 
   // (the connector object) without the end markers.
   //////////////////////////////////////////////////////////////////////////
   void GetPaletteIcons( GlgObject palette, String icon_name, 
                        GlgObject icon_array, GlgObject object_array )
   {      
      for( int i=0; ; ++i )
      {
         // Get icon[i]
         GlgObject icon = palette.GetResourceObject( icon_name + i );
         if( icon == null )
           break;

         // Object to use in the drawing. In case of connectors, uses only a
         // part of the icon (the connector object) without the end markers.
           //
         GlgObject object = icon.GetResourceObject( "Object" );
         if( object == null )
           object = icon;

         if( !object.HasResourceObject( "IconType" ) )
         {
            SetError( "Can't find IconType resource." );
            continue;
         }

         String type_string = object.GetSResource( "IconType" );

         // Using icon base name as icon type since they are the same,
         // i.e. "Node" and "Node", or "Link" and "Link".
           //
         if( type_string.equals( icon_name ) )
         {
            // Found an icon of requested type, add it to the array.
            icon_array.AddObjectToBottom( icon );
            object_array.AddObjectToBottom( object );

            // Set index to match the index in the icon name, i.e. 0 for Icon0.
            object.SetDResource( "Index", (double) i );
         }
      }

      int size = icon_array.GetSize();
      if( size == 0 )
        SetError( "Can't find any icons of this type." );
      else
        System.out.println( "Scanned " + size + " " + icon_name +  " icons" );
   }

   ////////////////////////////////////////////////////////////////////////
   // Adds custom data to the graphical object
   ////////////////////////////////////////////////////////////////////////
   void AddCustomData( GlgObject object, Object data )
   {
      // Add back-pointer from graphics to the link's data struct,
      // keeping the data already attached (if any).
        //
      GlgObject custom_data = object.GetResourceObject( "CustomData" );
      if( custom_data == null )
      {
         // No custom data attached: create an extra group and attach it 
           // to object as custom data.
         custom_data = new GlgDynArray( GlgObject.GLG_OBJECT, 0, 0 );
         object.SetResourceObject( "CustomData", custom_data );
      }

      // To allow using non-glg objects, use a group with element type
      // JAVA_OBJECT as a holder. The first element of the group will keep
        // the custom data pointer (pointer to the Link or Node structure).
      GlgObject holder_group = new GlgDynArray( GlgObject.JAVA_OBJECT, 0, 0 );
      holder_group.SetSResource( "Name", "PtrHolder" );

      holder_group.AddObjectToBottom( data );

      // Add it to custom data.
      custom_data.AddObjectToBottom( holder_group );
   }

   ////////////////////////////////////////////////////////////////////////
   // Get custom data attached to the graphical object
   ////////////////////////////////////////////////////////////////////////
   Object GetData( GlgObject object )
   {
      GlgObject holder_group = object.GetResourceObject( "PtrHolder" );
      return holder_group.GetElement( 0 );
   }

   ////////////////////////////////////////////////////////////////////////
   void SetRadioBox( String button_name )
   {
      // Always highlight the new button: the toggle would unhighlight if 
        // clicked on twice.
      GlgObject button = Viewport.GetResourceObject( button_name );
      if( button != null )
        button.SetDResource( "OnState", 1. );

      // Unhighlight the previous button.
      if( LastButton != null && !LastButton.equals( button_name ) )
      {
         button = Viewport.GetResourceObject( LastButton );
         if( button != null )
           button.SetDResource( "OnState", 0. );
      }
         
      LastButton = button_name;   // Store the last button.
   }

   ////////////////////////////////////////////////////////////////////////
   void GetPosition( GlgObject object, GlgPoint coord ) 
   {
      int type = object.GetDResource( "Type" ).intValue();
      if( type == GlgObject.REFERENCE )
      {
         // Reference: can use its point to position it.
         coord.CopyFrom( object.GetGResource( "Point" ) );
      }
      else
      {
         // Arbitrary object: convert the box's center to the world coords.

         // Get object center in screen coords.
         GlgCube box = object.GetBoxPtr();

         GlgPoint center = new GlgPoint( ( box.p1.x + box.p2.x ) / 2.,
                                        ( box.p1.y + box.p2.y ) / 2.,
                                        ( box.p1.z + box.p2.z ) / 2. );

         MainArea.ScreenToWorld( true, center, coord );
      }
   }

   ////////////////////////////////////////////////////////////////////////
   // Fills Properties dialog with the selected object data 
   ////////////////////////////////////////////////////////////////////////   
   void FillData()
   {
      String 
        label,
        object_data,
        datasource = null;

      switch( SelectedObjectType )
      {
       default:
         label = "NO_OBJECT";
         object_data = "";
         datasource = "";
         break;
      
       case NODE:	 
       case LINK:	 
         label = GetObjectLabel( SelectedObject );
         object_data = GetObjectData( SelectedObject );
         if( ProcessDiagram )
         {
            datasource = GetObjectDataSource( SelectedObject );

            // Substitute an empty string instead of null for display.
            if( datasource == null )
              datasource = "";
         }
         break;
      }   

      Viewport.SetSResource( "Dialog/DialogName/TextString", label );
      Viewport.SetSResource( "Dialog/DialogData/TextString", object_data );

      // For process diagram also set the datasource field.
      if( ProcessDiagram )
        Viewport.SetSResource( "Dialog/DialogDataSource/TextString", 
                              datasource );
   }

   ////////////////////////////////////////////////////////////////////////
   boolean ApplyData()
   {
      String
        label,
        object_data;

      label = Viewport.GetSResource( "Dialog/DialogName/TextString" );
      object_data = Viewport.GetSResource( "Dialog/DialogData/TextString" );

      switch( SelectedObjectType )
      {
       case NODE:
       case LINK:
         break;
       default: return true;
      }   

      // Store data
      SetObjectLabel( SelectedObject, label );
      SetObjectData( SelectedObject, object_data );

      if( ProcessDiagram )
      {
         String datasource = 
           Viewport.GetSResource( "Dialog/DialogDataSource/TextString" );
         SetObjectDataSource( SelectedObject, datasource );
      }

      Update();
      return true;
   }

   ////////////////////////////////////////////////////////////////////////
   String GetObjectLabel( GlgObject object )
   {
      Object data = GetData( object );
      if( data instanceof GlgNodeData )
        return ((GlgNodeData)data).object_label;
      else
        return ((GlgLinkData)data).object_label;
   }

   ////////////////////////////////////////////////////////////////////////
   void SetObjectLabel( GlgObject object, String label )
   {
      // Display label in the node or link object if it has a label.
      if( object.HasResourceObject( "Label" ) )
        object.SetSResource( "Label/String", label );

      Object data = GetData( object );
      if( data instanceof GlgNodeData )
        ((GlgNodeData)data).object_label = label;
      else if( data instanceof GlgLinkData )
        ((GlgLinkData)data).object_label = label;
   }
   
   ////////////////////////////////////////////////////////////////////////
   String GetObjectData( GlgObject object )
   {
      Object data = GetData( object );
      if( data instanceof GlgNodeData )
        return ((GlgNodeData)data).object_data;
      else
        return ((GlgLinkData)data).object_data;
   }

   ////////////////////////////////////////////////////////////////////////
   void SetObjectData( GlgObject object, String object_data )
   {
      Object data = GetData( object );
      if( data instanceof GlgNodeData )
        ((GlgNodeData)data).object_data = object_data;
      else
        ((GlgLinkData)data).object_data = object_data;
   }
   
   ////////////////////////////////////////////////////////////////////////
   String GetObjectDataSource( GlgObject object )
   {
      Object data = GetData( object );
      if( data instanceof GlgNodeData )
        return ((GlgNodeData)data).datasource;
      else
        return ((GlgLinkData)data).datasource;
   }

   ////////////////////////////////////////////////////////////////////////
   void SetObjectDataSource( GlgObject object, String datasource )
   {
      if( datasource != null && datasource.length() == 0 )
        datasource = null;  // Substitute null for empty datasource strings.
      
      Object data = GetData( object );
      if( data instanceof GlgNodeData )
      {
         if( object.HasResourceObject( "Value" ) )
           object.SetSResource( "Value/Tag", datasource );

         ((GlgNodeData)data).datasource = datasource;
      }
      else
        ((GlgLinkData)data).datasource = datasource;
   }
   
   ////////////////////////////////////////////////////////////////////////
   boolean NodeConnected( GlgObject node )
   {
      for( int i=0; i< CurrentDiagram.getLinkList().size(); ++ i )
      {
         GlgLinkData link_data =
           (GlgLinkData) CurrentDiagram.getLinkList().elementAt( i );

         GlgNodeData start_node = link_data.getStartNode();
         GlgNodeData end_node = link_data.getEndNode();
        if( start_node != null && start_node.graphics == node ||
           end_node != null && end_node.graphics == node )
          return true;      
      }
      return false;
   }

   ////////////////////////////////////////////////////////////////////////
   int GetButton( AWTEvent event )
   {
      if( ! ( event instanceof InputEvent ) )
        return 0;
      
      InputEvent input_event = (InputEvent) event;
      int modifiers = input_event.getModifiers();
      
      if( ( modifiers & InputEvent.BUTTON3_MASK ) != 0 )
        return 3;
      else if( ( modifiers & InputEvent.BUTTON2_MASK ) != 0 )
        return 2;
      else
        return 1;
   }

   //////////////////////////////////////////////////////////////////////////
   // ActionListener method to use the bean as update timer's ActionListener.
   //////////////////////////////////////////////////////////////////////////
   public void actionPerformed( ActionEvent e )
   {
      if( ProcessDiagram && timer != null )
      {
         UpdateProcessDiagram();
         timer.start();
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Updates all tags defined in the drawing for a process diagram.
   //////////////////////////////////////////////////////////////////////////
   void UpdateProcessDiagram()
   {
      if( !ProcessDiagram )
        return;

      GlgObject drawing = MainArea;

      // Since new nodes may be added or removed in the process of the diagram
      // editing, get the current list of tags every time. In an application 
      // that just displays the diagram without editing, the tag list may be
      // obtained just once (initially) and used to subscribe to data.
        // Query only unique tags.
      GlgObject tag_list = drawing.CreateTagList( true );
      if( tag_list != null )
      {
         int size = tag_list.GetSize();
         for( int i=0; i<size; ++i )
         {
            GlgObject data_object = (GlgObject) tag_list.GetElement( i );
            String tag_name = data_object.GetSResource( "Tag" );

            double new_value = GetTagValue( tag_name, data_object );
            drawing.SetDTag( tag_name, new_value, true );
         }

         Update( drawing );
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Get new value based on a tag name. In a real application, the value
   // is obtained from a process database. PLC or another live datasource.
   // In the demo, use random data.
   //////////////////////////////////////////////////////////////////////////
   double GetTagValue( String tag_name, GlgObject data_object )
   {
      // Get the current value
      double value = data_object.GetDResource( null ).doubleValue();
   
      // Increase it.
      double increment = GlgObject.Rand( 0., 0.1 );

      double direction;
      if( value == 0. )
        direction = 1.;
      else if( value == 1. )
        direction = -1.;
      else
        direction = GlgObject.Rand( -1., 1. );

      if( direction > 0. )
        value += increment;
      else
        value -= increment;
      
      if( value > 1. )
        value = 1.;
      else if( value < 0. )
        value = 0.;
      
      return value;
   }
}

class GlgNodeData
{   
   int node_type;
   GlgDiagramPoint position;
   String object_label;
   String object_data;
   String datasource;
   transient GlgObject graphics;

   GlgNodeData()
   {
      object_label = "";
      object_data = "";
      datasource = "";
      position = new GlgDiagramPoint();
   }
}

class GlgLinkData
{   
   int link_type;
   int link_direction;
   GlgDiagramPoint link_color;
   transient GlgObject graphics;
   GlgNodeData start_node;
   GlgNodeData end_node;
   String start_point_name;
   String end_point_name;
   String object_label;
   String object_data;
   Vector point_array;
   boolean first_move;
   String datasource;

   GlgLinkData()
   {
      object_label = "A1";
      object_data = "";
      datasource = "";
      point_array = new Vector();
   }

   GlgNodeData getStartNode()
   {
      return start_node;
   }

   GlgNodeData getEndNode()
   {
      return end_node;
   }

   void setStartNode( GlgNodeData node )
   {
      start_node = node;
   }

   void setEndNode( GlgNodeData node )
   {
      end_node = node;
   }
}

class GlgDiagramData
{
   Vector node_list;
   Vector link_list;

   GlgDiagramData()
   {
      node_list = new Vector();
      link_list = new Vector();
   }

   Vector getNodeList()
   {
      return node_list;
   }

   Vector getLinkList()
   {
      return link_list;
   }
}
class GlgDiagramPoint extends GlgPoint
{
   // Allowes for custom serialization extensions for saving and loading

   GlgDiagramPoint()
   {
   }

   GlgDiagramPoint( GlgPoint point )
   {
      if( point != null )
      {
         x = point.x;
         y = point.y;
         z = point.z;
      }      
   }
}

// Custom handler, place application-specific code into these callbacks.
class CustomHandler
{
   static void AddObjectCB( GlgDiagram diagram, GlgObject icon, Object data,
                           boolean is_node )
   {
   }

   static void SelectObjectCB( GlgDiagram diagram, GlgObject icon, Object data,
                              boolean is_node )
   {
   }

   static void CutObjectCB( GlgDiagram diagram, GlgObject icon, Object data,
                           boolean is_node )
   {
   }

   static void PasteObjectCB( GlgDiagram diagram, GlgObject icon, Object data,
                             boolean is_node )
   {
   }
}

