//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// (C) Copyright IBM Corporation 2005
// 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>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 _cursorHairlines = new HashMap();

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

 // track cursor / fixed hairline
 private boolean _trackCursor;
 // fixed hairline's pixel offset inside the text line
 private int _fixedPixelPosition;
 // latest pixel offset inside the text window
 private int _pixelPosition;

 // 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,
  *                    false = fixed hairline at the current position
  */
 public static void install(LpexView lpexView, boolean trackCursor)
 {
  if (lpexView != null)
   {
    CursorHairline ch = (CursorHairline)_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 = (CursorHairline)_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)
     {
      // ensure minimum cursor width, so it doesn't disappear under hairline
      if (_lpexView.queryInt("current.cursor.width") == 1)
       {
        _originalCursorWidth = _lpexView.query("cursor.width");
        _lpexView.doCommand("set cursor.width 2");
       }

      _hairlineColor = new Color(_lpexWindow.getDisplay(), _R, _G, _B);

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

  // record the cursor pixel position, repaint if needed
  if (_lpexWindow != null)
   {
    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");
     }

    // ask to redraw the areas covered by the old, new hairline
    if (pixelPosition != _pixelPosition)
     {
      Control textWindow = _lpexWindow.textWindow();
      int height = textWindow.getSize().y;
      // TODO: limit height to the actual display rows...

      textWindow.redraw(_pixelPosition, 0, _hairlineWidth, height, false);
      if (pixelPosition > 0)
       {
        textWindow.redraw(pixelPosition, 0, _hairlineWidth, height, false);
       }
     }

    _pixelPosition = pixelPosition;
   }
 }

 /**
  * 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 && _pixelPosition > 0)
   {
    int height = _lpexWindow.textWindow().getSize().y;
    e.gc.setForeground(_hairlineColor);
    e.gc.setLineStyle(_hairlineStyle);
    e.gc.setLineWidth(_hairlineWidth);
    e.gc.drawLine(_pixelPosition, 0, _pixelPosition, height);
   }
 }
}