//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 2005, 2007
// All Rights Reserved.
//
// DESCRIPTION:
// CursorHairline - sample SWT LPEX extension (cursor hairline)
//----------------------------------------------------------------------------

package com.ibm.lpex.samples;

import java.util.HashMap;

import com.ibm.lpex.core.LpexView;
import com.ibm.lpex.core.LpexViewAdapter;
import com.ibm.lpex.core.LpexWindow;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Control;

/**
 * Sample class to display a vertical hairline.
 *
 * <p>Installing this class in a document view adds a vertical hairline that
 * either tracks the cursor, or is fixed at the cursor location in effect when
 * it is installed.</p>
 *
 * <p>Here is the CursorHairline <a href="doc-files/CursorHairline.java.html">source
 * code</a>.</p>
 *
 * <p>A user profile (such as {@link TestUserProfile}) can install this feature
 * in a document view by calling, for example:
 * <pre>  CursorHairline.install(lpexView, false);</pre></p>
 *
 * <p>The code fragment below sets the hairline at column 81 (assuming a fixed-pitch font):
 * <pre>  // import com.ibm.lpex.samples.CursorHairline;
 *  String currentPosition = lpexView.query("displayPosition");
 *  lpexView.doCommand("set displayPosition 81");
 *  CursorHairline.install(lpexView, false);
 *  lpexView.doCommand("screenShow view");
 *  lpexView.doCommand("set displayPosition " + currentPosition);</pre></p>
 *
 * <p>See also {@link HairlineCommand} as an example of an editor command that
 * controls the display of the cursor hairline.</p>
 *
 * @see com.ibm.lpex.samples All the samples
 */
public class CursorHairline extends LpexViewAdapter
             implements PaintListener, DisposeListener
{
 private static HashMap<LpexView,CursorHairline> _cursorHairlines =
  new HashMap<LpexView,CursorHairline>();

 private LpexView   _lpexView;
 private LpexWindow _lpexWindow;
 private String     _originalCursorWidth;

 // track cursor / fixed hairline
 private boolean _trackCursor;
 // fixed hairline's x offset inside the text line
 private int _fixedPixelPosition;
 // latest x offset, y offset, and height inside the text window
 private int _pixelPosition, _height, _y;

 // hairline style, width, and color (TODO: make some customizable...)
 private static int _hairlineWidth = 1;
 private int _hairlineStyle = SWT.LINE_SOLID;
 private Color _hairlineColor;
 private int _R = 220, _G = 220, _B = 220; // light gray


 /**
  * Constructs a new cursor hairline for the specified view.
  */
 private CursorHairline(LpexView lpexView)
 {
  _lpexView = lpexView;
  _lpexView.addLpexViewListener(this);
  _cursorHairlines.put(lpexView, this);
 }

 /**
  * Installs the hairline in the given document view.
  *
  * @param trackCursor true = hairline follows the cursor, or
  *                    false = fixed hairline at the current position
  */
 public static void install(LpexView lpexView, boolean trackCursor)
 {
  if (lpexView != null)
   {
    CursorHairline ch = _cursorHairlines.get(lpexView);
    if (ch == null)
     {
      ch = new CursorHairline(lpexView);
     }

    ch._trackCursor = trackCursor;
    ch._fixedPixelPosition = -1;
   }
 }

 /**
  * Uninstalls the cursor hairline from the given view.
  */
 public static void uninstall(LpexView lpexView)
 {
  CursorHairline ch = _cursorHairlines.get(lpexView);
  if (ch != null)
   {
    ch.uninstall();
   }
 }

 // Removes this cursor hairline.
 private void uninstall()
 {
  if (_lpexView != null)
   {
    _lpexView.removeLpexViewListener(this);
    if (_lpexWindow != null)
     {
      Control textWindow = _lpexWindow.textWindow();
      if (!textWindow.isDisposed())
       {
        textWindow.removePaintListener(this);
        textWindow.removeDisposeListener(this);

        // if user is uninstalling us restore cursor width, erase hairline
        if (_originalCursorWidth != null)
         {
          _lpexView.doCommand("set cursor.width " + _originalCursorWidth);
         }
        textWindow.redraw();
       }

      _lpexWindow = null;
      _hairlineColor.dispose();
     }

    _cursorHairlines.remove(_lpexView);
    _lpexView = null;
   }
 }

 /**
  * View listener - the view has been refreshed.  Installs our listeners as
  * soon as an LPEX window has been associated with the view.  Assumes that
  * the specified document view will only ever be shown in this window.
  */
 public void shown(LpexView lpexView)
 {
  // install our listeners, adjust cursor width
  if (_lpexWindow == null)
   {
    _lpexWindow = _lpexView.window();
    if (_lpexWindow != null)
     {
      _hairlineColor = new Color(_lpexWindow.getDisplay(), _R, _G, _B);

      _lpexWindow.textWindow().addDisposeListener(this);
      _lpexWindow.textWindow().addPaintListener(this);
     }
   }

  if (_lpexWindow != null)
   {
    // always ensure minimum cursor width, so it doesn't disappear under hairline
    if (_lpexView.queryInt("current.cursor.width") == 1)
     {
      // record latest user setting
      _originalCursorWidth = _lpexView.query("cursor.width");
      _lpexView.doCommand("set cursor.width 2");
     }

    // determine the cursor pixel position
    int pixelPosition = (_trackCursor || _fixedPixelPosition == -1)?
                         _lpexView.queryInt("pixelPosition") : _fixedPixelPosition;
    if (!_trackCursor && _fixedPixelPosition == -1)
     {
      _fixedPixelPosition = pixelPosition;
     }

    // adjust for the prefix area, expand/hide area, horizontal scroll
    if (pixelPosition >= 0)
     {
      pixelPosition += _lpexView.queryInt("prefixAreaWidth") +
                       _lpexView.queryInt("expandHideAreaWidth") -
                       _lpexView.queryInt("scroll");
     }

    // determine the y offset and height
    int rowHeight = _lpexView.queryInt("rowHeight");
    int rows = _lpexView.queryInt("rows");
    int y = 0;
    int height = 0;

    if (rows > 0)
     {
      // adjust y offset for the top expand header
      if (_lpexView.elementOfRow(1) == 0)
       {
        y = rowHeight;
       }
      else
       {
        height = rowHeight;
       }

      // use only the element rows shown in the text area
      for (int i = 2; i <= rows && _lpexView.elementOfRow(i) != 0; i++)
       {
        height += rowHeight;
       }
     }

    // on changes, invalidate the areas covered by the old, new hairline
    if (pixelPosition != _pixelPosition || height != _height || y != _y)
     {
      Control textWindow = _lpexWindow.textWindow();
      if (_height > 0)
       {
        textWindow.redraw(_pixelPosition, _y, _hairlineWidth, _height, false);
       }
      if (height > 0 && pixelPosition > 0)
       {
        textWindow.redraw(pixelPosition, y, _hairlineWidth, height, false);
       }

      _pixelPosition = pixelPosition;
      _height = height;
      _y = y;
     }
   }
 }

 /**
  * View listener - the view is being disposed.
  * Uninstalls the cursor hairline from this view.
  */
 public void disposed(LpexView lpexView)
 { uninstall(); }

 /**
  * Text window dispose listener - the window is being disposed.
  * Uninstalls the cursor hairline.
  */
 public void widgetDisposed(DisposeEvent e)
 { uninstall(); }

 /**
  * Text window paint listener - paint event notification.
  * Draws the cursor hairline.
  */
 public void paintControl(PaintEvent e)
 {
  if (_lpexWindow != null && _height > 0 && _pixelPosition > 0)
   {
    e.gc.setForeground(_hairlineColor);
    e.gc.setLineStyle(_hairlineStyle);
    e.gc.setLineWidth(_hairlineWidth);
    e.gc.drawLine(_pixelPosition, _y, _pixelPosition, _y + _height);
   }
 }
}