//
// "$Id: DiffView.cxx 407 2006-11-13 18:54:02Z mike $"
//
// DiffView widget code.
//
// Copyright 2005-2006 by Michael Sweet.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License v2 as published
// by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// Contents:
//
//   DiffView::DiffView()          - Create a diff viewing widget.
//   DiffView::~DiffView()         - Destroy a diff viewing widget.
//   DiffView::add_line()          - Add a line to the diff...
//   DiffView::clear()             - Remove all lines from the viewer.
//   DiffView::draw()              - Draw the diff viewer...
//   DiffView::expand_tabs()       - Expand tabs on a line.
//   DiffView::handle()            - Handle user events.
//   DiffView::load()              - Load a diff.
//   DiffView::resize()            - Resize a diff viewing widget.
//   DiffView::scroll_cb()         - Scroll the diff.
//   DiffView::select()            - Select specific lines.
//   DiffView::selection()         - Return the current text selection.
//   DiffView::selection_length()  - Return the length of the current selection.
//   DiffView::showline()          - Show a line in the diff...
//   DiffView::timeout_cb()        - Repeat scrollbar buttons...
//

#include "DiffView.h"
#include <FL/filename.H>
#include <FL/fl_ask.H>
#include <FL/fl_draw.H>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "PtProcess.h"

//
// Width/height of scrollbars...
//

#define SCROLLER	16


//
// 'DiffView::DiffView()' - Create a diff viewing widget.
//

DiffView::DiffView(int        X,	// I - X position
                   int        Y,	// I - Y position
		   int        W,	// I - Width
		   int        H,	// I - Height
		   const char *L)	// I - Label
  : Fl_Group(X, Y, W, H, L),
    hscroll1_(X, Y + H - SCROLLER, (W - SCROLLER) / 2, SCROLLER),
    hscroll2_(X + (W + SCROLLER) / 2, Y + H - SCROLLER, (W - SCROLLER) / 2, SCROLLER)
{
  // Don't add any more widgets to this one...
  end();

  // Initialize this instance with defaults...
  alloc_        = 0;
  count_        = 0;
  ignoreblanks_ = false;
  linefmt_[0]   = '\0';
  linenum_      = 0;
  lines_        = (DiffLine **)0;
  maxwidth_     = 0;
  select_end_   = -1;
  select_right_ = false;
  select_start_ = -1;
  showlinenum_  = true;
  state_        = IDLE;
  tabwidth_     = 8;
  textcolor_    = FL_BLACK;
  textfont_     = FL_COURIER;
  textsize_     = FL_NORMAL_SIZE;
  topline_      = 0;

  box(FL_FLAT_BOX);
  selection_color(FL_YELLOW);

  // Connect the scrollbars to the DiffView widget and initialize them...
  hscroll1_.callback((Fl_Callback *)scroll_cb, this);
  hscroll1_.deactivate();
  hscroll1_.type(FL_HORIZONTAL);
  hscroll1_.value(0, w() / 2 - SCROLLER, 0, 1);

  hscroll2_.callback((Fl_Callback *)scroll_cb, this);
  hscroll2_.deactivate();
  hscroll2_.type(FL_HORIZONTAL);
  hscroll2_.value(0, w() / 2 - SCROLLER, 0, 1);
}


//
// 'DiffView::~DiffView()' - Destroy a diff viewing widget.
//

DiffView::~DiffView()
{
  // Remove all lines, freeing memory...
  clear();
}


//
// 'DiffView::add_line()' - Add a line to the diff...
//

void
DiffView::add_line(const char *left,	// I - First line or NULL
                   const char *right,	// I - Second line or NULL
		   bool       changed,	// I - Line changed?
		   int        pos)	// I - Position to add to
{
  int		start,			// First changed column
		end,			// Last changed column
		leftlen,		// First line length
		rightlen;		// Second line length
  DiffLine	*line,			// Current line
		**temp;			// New array


  if (pos < 0 || pos >= count_)
  {
    // Add a new line...
    if (count_ >= alloc_)
    {
      temp = new DiffLine *[alloc_ + 50];

      if (count_)
      {
	memcpy(temp, lines_, count_ * sizeof(DiffLine *));
	delete[] lines_;
      }

      lines_ = temp;
      alloc_ += 50;
    }

    line         = new DiffLine;
    line->left   = (char *)0;
    line->dleft  = (char *)0;
    line->right  = (char *)0;
    line->dright = (char *)0;

    lines_[count_] = line;
    count_ ++;
  }
  else
  {
    // Update an existing line...
    line = lines_[pos];
  }

  // Copy left/right strings as needed...
  if (left)
  {
    line->left  = strdup(left);
    line->dleft = expand_tabs(left);
  }

  if (right)
  {
    line->right  = strdup(right);
    line->dright = expand_tabs(right);
  }

  // Save the "changed" state...
  line->changed = changed;

  // Update the maximum width of the text...
  if (line->dleft && (leftlen = strlen(line->dleft)) > maxwidth_)
    maxwidth_ = leftlen;
  else
    leftlen = 0;

  if (line->dright && (rightlen = strlen(line->dright)) > maxwidth_)
    maxwidth_ = rightlen;
  else
    rightlen = 0;

  if (changed)
  {
    // Update the start/end offsets so we can highlight the changed portion
    // of the line.
    if (!line->dleft)
    {
      line->start = 0;
      line->end   = rightlen;
    }
    else if (!line->dright)
    {
      line->start = 0;
      line->end   = leftlen;
    }
    else if (directories_)
    {
      line->start = 0;

      if (leftlen > rightlen)
        line->end = leftlen;
      else
        line->end = rightlen;
    }
    else
    {
      for (start = 0; line->dleft[start] == line->dright[start]; start ++);

      line->start = start;

      if (leftlen > rightlen)
        line->end = leftlen;
      else if (rightlen > leftlen)
        line->end = rightlen;
      else
      {
	for (end = start + 1; line->dleft[end] && line->dright[end]; end ++)
          if (line->dleft[end] != line->dright[end])
	    line->end = end + 1;
      }
    }
  }
}


//
// 'DiffView::clear()' - Remove all lines from the viewer.
//

void
DiffView::clear()
{
  int	i;				// Looping var


  // Free the text on each line...
  for (i = 0; i < count_; i ++)
  {
    if (lines_[i]->left)
      free(lines_[i]->left);
    if (lines_[i]->dleft)
      free(lines_[i]->dleft);

    if (lines_[i]->right)
      free(lines_[i]->right);
    if (lines_[i]->dright)
      free(lines_[i]->dright);
  }

  // Free the line array...
  if (alloc_)
    delete[] lines_;

  // Reset array and display data...
  alloc_        = 0;
  count_        = 0;
  directories_  = false;
  lines_        = (DiffLine **)0;
  linefmt_[0]   = '\0';
  linenum_      = 0;
  maxwidth_     = 0;
  select_start_ = -1;
  select_end_   = -1;
  select_right_ = false;
  topline_      = 0;

  hscroll1_.deactivate();
  hscroll1_.value(0, w() / 2 - SCROLLER, 0, 1);

  hscroll2_.deactivate();
  hscroll2_.value(0, w() / 2 - SCROLLER, 0, 1);

  Fl::remove_timeout((void (*)(void *))timeout_cb, (void *)this);
}


//
// 'DiffView::draw()' - Draw the diff viewer...
//

void
DiffView::draw()
{
  int		i,			// Looping var
		yy,			// Current Y position
		xoffset,		// X offset for line
		xwidth,			// Width of column
		ystart,			// Start line of change
		yend,			// End line of change
		pstart,			// Start line of page
		pend,			// End line of page
		firstline,		// First line number
		secondline;		// Second line number
  char		number[255];		// Line number
  DiffLine	**line,			// Current line
		**last;			// Last line


  if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD))
  {
    // Do a full redraw...
    Fl_Group::draw();

    fl_color(FL_GRAY);
    fl_rectf(x() + (w() - SCROLLER) / 2, y() + h() - SCROLLER,
             SCROLLER, SCROLLER);
  }
  else
  {
    // Do a partial redraw of the text (scrolled)...
    fl_color(color());
    fl_rectf(x(), y(), (w() - SCROLLER) / 2, h() - SCROLLER);
    fl_rectf(x() + (w() + SCROLLER) / 2, y(), (w() - SCROLLER) / 2, h() - SCROLLER);
  }

  // Clear the diff bar...
  xoffset = x() + (w() - SCROLLER) / 2;

  fl_color(FL_DARK2);
  fl_rectf(xoffset, y(), SCROLLER, h() - SCROLLER);

  // Draw the up/down arrows...
  if ((count_ * textsize_) <= (h() - SCROLLER))
    set_flag(INACTIVE);
  draw_box(state_ == UPARROW ? FL_DOWN_BOX : FL_UP_BOX,
           xoffset, y(), SCROLLER, SCROLLER, FL_GRAY);
  draw_box(state_ == DOWNARROW ? FL_DOWN_BOX : FL_UP_BOX,
           xoffset, y() + h() - 2 * SCROLLER, SCROLLER, SCROLLER, FL_GRAY);
  clear_flag(INACTIVE);

  // Stolen from Fl_Scrollbar.cxx... :)
  if ((count_ * textsize_) > (h() - SCROLLER))
    fl_color(labelcolor());
  else
    fl_color(fl_inactive(labelcolor()));
  int w1 = (SCROLLER-4)/3;
  int x1 = xoffset+(SCROLLER-2*w1-1)/2;
  int yy1 = y()+(SCROLLER-w1-1)/2;
  if (Fl::scheme_ && !strcmp(Fl::scheme_, "gtk+")) {
    fl_polygon(x1, yy1+w1, x1+w1, yy1+w1-1, x1+2*w1, yy1+w1, x1+w1, yy1);
    yy1 += h()-2*SCROLLER;
    fl_polygon(x1, yy1, x1+w1, yy1+1, x1+w1, yy1+w1);
    fl_polygon(x1+w1, yy1+1, x1+2*w1, yy1, x1+w1, yy1+w1);
  } else {
    fl_polygon(x1, yy1+w1, x1+2*w1, yy1+w1, x1+w1, yy1);
    yy1 += h()-2*SCROLLER;
    fl_polygon(x1, yy1, x1+w1, yy1+w1, x1+2*w1, yy1);
  }

  // Return early if we have no lines...
  if (!count_)
    return;

  // Prepare the text font and display vars...
  fl_font(textfont_, textsize_);

  xwidth = (int)fl_width(' ');
  last   = lines_ + count_;

  // Draw the left side (first file)...
  fl_push_clip(x() + linenum_, y(),
               (w() - SCROLLER) / 2 - linenum_, h() - SCROLLER);
  firstline = topline_ / textsize_;
  line      = lines_ + firstline;
  yy        = y() + textsize_ - topline_ + firstline * textsize_;
  for (i = (h() + textsize_ - 1) / textsize_,
           xoffset = x() + linenum_ - hscroll1_.value();
       i > 0 && line < last;
       i --, yy += textsize_, line ++, firstline ++)
  {
    if (!(*line)->dleft)
    {
      fl_color(fl_color_average(textcolor(), color(), 0.1f));
      fl_rectf(x() + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);
      continue;
    }

    if (firstline >= select_start_ && firstline <= select_end_ &&
        (!select_right_ || directories_))
    {
      // Show selected line...
      fl_color(textcolor_);
      fl_rectf(x() + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);
      fl_color(color());
    }
    else if ((*line)->changed)
    {
      // Highlight change...
      fl_color(fl_color_average(selection_color(), color(), 0.33f));
      fl_rectf(x() + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);

      fl_color(selection_color());
      fl_rectf(xoffset + (*line)->start * xwidth, yy - textsize_,
               ((*line)->end - (*line)->start) * xwidth, textsize_);

      fl_color(fl_contrast(textcolor_, selection_color()));
    }
    else
      fl_color(textcolor_);

    // Draw the left text...
    fl_draw((*line)->dleft, xoffset, yy - fl_descent());
  }
  fl_pop_clip();

  // Draw the right side (second file)...
  fl_push_clip(x() + (w() + SCROLLER) / 2 + linenum_, y(),
               (w() - SCROLLER) / 2 - linenum_, h() - SCROLLER);
  secondline = topline_ / textsize_;
  line       = lines_ + secondline;
  yy         = y() + textsize_ - topline_ + secondline * textsize_;
  for (i = (h() + textsize_ - 1) / textsize_,
	   xoffset = x() + (w() + SCROLLER) / 2 + linenum_ - hscroll1_.value();
       i > 0 && line < last;
       i --, yy += textsize_, line ++, secondline ++)
  {
    if (!(*line)->dright)
    {
      fl_color(fl_color_average(textcolor(), color(), 0.1f));
      fl_rectf(x() + (w() + SCROLLER) / 2 + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);
      continue;
    }

    if (secondline >= select_start_ && secondline <= select_end_ &&
        (select_right_ || directories_))
    {
      // Show selected line...
      fl_color(textcolor_);
      fl_rectf(x() + (w() + SCROLLER) / 2 + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);
      fl_color(color());
    }
    else if ((*line)->changed)
    {
      // Highlight change...
      fl_color(fl_color_average(selection_color(), color(), 0.33f));
      fl_rectf(x() + (w() + SCROLLER) / 2 + linenum_, yy - textsize_,
               (w() - SCROLLER) / 2 - linenum_, textsize_);

      fl_color(selection_color());
      fl_rectf(xoffset + (*line)->start * xwidth, yy - textsize_,
               ((*line)->end - (*line)->start) * xwidth, textsize_);

      fl_color(fl_contrast(textcolor_, selection_color()));
    }
    else
      fl_color(textcolor_);

    // Draw the right text...
    fl_draw((*line)->dright, xoffset, yy - fl_descent());
  }
  fl_pop_clip();

  // Draw a box for the current position in the diff...
  xoffset = x() + (w() - SCROLLER) / 2;
  if ((count_ * textsize_) > (h() - SCROLLER))
  {
    pstart  = topline_ * (h() - 3 * SCROLLER - 2) / textsize_ / count_;
    pend    = (topline_ + h() - SCROLLER) * (h() - 3 * SCROLLER - 2) /
              textsize_ / count_;
  }
  else
  {
    pstart = 0;
    pend   = h() - 3 * SCROLLER - 2;
  }

  fl_color(fl_color_average(FL_DARK2, textcolor_, 0.5f));
  fl_rectf(xoffset, y() + pstart + SCROLLER, SCROLLER, pend - pstart + 2);

  // Draw change markers...
  fl_color(selection_color());

  for (line = lines_, i = 0, ystart = -1, yend = -1; line < last; line ++, i ++)
  {
    if ((*line)->changed)
    {
      yend = i;

      if (ystart < 0)
        ystart = i;
    }
    else if (ystart >= 0)
    {
      ystart = ystart * (h() - 3 * SCROLLER - 2) / count_;
      yend   = (yend + 1) * (h() - 3 * SCROLLER - 2) / count_ - 1;

      if (ystart >= yend)
        fl_xyline(xoffset + 1, ystart + y() + SCROLLER + 1, xoffset + SCROLLER - 2);
      else
        fl_rectf(xoffset + 1, ystart + y() + SCROLLER + 1, SCROLLER - 2, yend - ystart);

      ystart = -1;
    }
  }

  if (ystart >= 0)
  {
    ystart = ystart * (h() - 3 * SCROLLER - 2) / count_;
    yend   = (yend + 1) * (h() - 3 * SCROLLER - 2) / count_ - 1;

    if (ystart >= yend)
      fl_xyline(xoffset + 1, ystart + y() + SCROLLER + 1, xoffset + SCROLLER - 2);
    else
      fl_rectf(xoffset + 1, ystart + y() + SCROLLER + 1, SCROLLER - 2, yend - ystart);

    ystart = -1;
  }

  // Outline the box...
  fl_color(textcolor_);
  fl_rect(xoffset, pstart + y() + SCROLLER, SCROLLER, pend - pstart + 2);

  // Draw line numbers...
  if (showlinenum_)
  {
    fl_push_clip(x(), y(), w(), h() - SCROLLER);

    fl_color(fl_color_average(color(), textcolor_, 0.75f));
    fl_yxline(x() + linenum_ - 2, y(), y() + h() - SCROLLER);
    fl_yxline(x() + (w() + SCROLLER) / 2 + linenum_ - 2, y(), y() + h() - SCROLLER);

    fl_color(textcolor_);

    line = lines_;
    yy   = y() + textsize_ - fl_descent() - topline_;
    for (firstline = 1, secondline = 1; line < last; yy += textsize_, line ++)
    {
      if (yy < y())
      {
        if ((*line)->left)
	  firstline ++;

        if ((*line)->right)
	  secondline ++;

        continue;
      }

      if (yy >= (y() + h() - SCROLLER))
        break;

      if ((*line)->left)
      {
        sprintf(number, linefmt_, firstline);
	fl_draw(number, x(), yy);
	firstline ++;
      }

      if ((*line)->right)
      {
        sprintf(number, linefmt_, secondline);
	fl_draw(number, x() + (w() + SCROLLER) / 2, yy);
	secondline ++;
      }
    }

    fl_pop_clip();
  }
}


//
// 'DiffView::expand_tabs()' - Expand tabs on a line.
//

char *					// O - Copy of expanded string
DiffView::expand_tabs(const char *s)	// I - String to expand
{
  char		*ptr,			// Pointer into new line
		buf[2048];		// New string
  int		col;			// Current column


  if (!s)
    return (NULL);

  for (ptr = buf, col = 0; *s && *s != '\n' && *s != '\r'; s ++)
    if (*s == '\t')
    {
      do
      {
        if (ptr < (buf + sizeof(buf) - 1))
	  *ptr++ = ' ';

	col ++;
      }
      while (col % tabwidth_);
    }
    else if (ptr < (buf + sizeof(buf) - 1))
    {
      *ptr++ = *s;
      col ++;
    }
    else
      break;

  *ptr = '\0';

  return (strdup(buf));
}


//
// 'DiffView::handle()' - Handle user events.
//

int					// O - 1 if handled, 0 otherwise
DiffView::handle(int event)		// I - Event
{
  int	v;				// Temporary value
  bool	doscrolling;			// True if we should scroll vertically...


  doscrolling = (count_ * textsize_) > (h() - SCROLLER);

  switch (event)
  {
    case FL_PUSH :
        if (Fl::event_y() >= y() && Fl::event_y() < (y() + h() - SCROLLER))
	{
	  if (Fl::event_x() < (x() + (w() - SCROLLER) / 2))
	  {
	    state_        = SELECTING;
	    select_start_ = (Fl::event_y() - y() + topline_) / textsize_;
	    select_end_   = -1;
	    select_right_ = false;

            if (select_start_ >= count_)
	      select_start_ = count_ - 1;

            if (Fl::event_clicks() > 0)
	    {
	      select_end_ = select_start_;

	      if (lines_[select_start_]->left)
		Fl::copy(lines_[select_start_]->left, 1);
	    }

	    damage(FL_DAMAGE_SCROLL);
	    return (1);
	  }
	  else if (Fl::event_x() >= (x() + (w() + SCROLLER) / 2))
	  {
	    state_        = SELECTING;
	    select_start_ = (Fl::event_y() - y() + topline_) / textsize_;
	    select_end_   = -1;
	    select_right_ = true;

            if (select_start_ >= count_)
	      select_start_ = count_ - 1;

            if (Fl::event_clicks() > 0)
	    {
	      select_end_ = select_start_;

	      if (lines_[select_start_]->right)
		Fl::copy(lines_[select_start_]->right, 1);
	    }

	    damage(FL_DAMAGE_SCROLL);
	    return (1);
	  }
          else if (Fl::event_y() < (y() + SCROLLER))
	  {
	    state_ = UPARROW;

            if ((count_ * textsize_) > (h() - SCROLLER))
	    {
              topline_ -= textsize_;
	      if (topline_ < 0)
		topline_ = 0;
	      else
        	Fl::add_timeout(0.5f, (void (*)(void *))timeout_cb, (void *)this);

              damage(FL_DAMAGE_SCROLL);
            }

	    return (1);
	  }
          else if (Fl::event_y() >= (y() + h() - 2 * SCROLLER))
	  {
	    state_ = DOWNARROW;

            if ((count_ * textsize_) > (h() - SCROLLER))
	    {
              topline_ += textsize_;
	      if (topline_ > (count_ * textsize_ - h() + SCROLLER))
		topline_ = count_ * textsize_ - h() + SCROLLER;
	      else
        	Fl::add_timeout(0.5f, (void (*)(void *))timeout_cb, (void *)this);

              damage(FL_DAMAGE_SCROLL);
	    }

	    return (1);
	  }
	  else if (doscrolling)
	    state_ = SCROLLING;
	}

    case FL_DRAG :
        switch (state_)
	{
	  case IDLE :
	      break;

          case SCROLLING :
	      v = (Fl::event_y() - y() - SCROLLER - 1) * count_ * textsize_ /
	          (h() - 3 * SCROLLER - 2) - (h() - SCROLLER) / 2;

	      if (v > (count_ * textsize_ - h() + SCROLLER))
		v = count_ * textsize_ - h() + SCROLLER;
	      else if (v < 0)
		v = 0;

              topline_ = v;
	      damage(FL_DAMAGE_SCROLL);
	      return (1);

          case SELECTING :
	      // First determine the current line...
	      select_end_ = (Fl::event_y() - y() + topline_) / textsize_;

              if (select_end_ >= count_)
		select_end_ = count_ - 1;

              // Then scroll as needed...
	      if ((Fl::event_y() - y()) < textsize_)
	      {
	        // Scroll up...
	        v = topline_ - textsize_;

		if (v < 0)
		  v = 0;

		topline_ = v;
	      }
	      else if ((Fl::event_y() - y()) >= (h() - textsize_ - SCROLLER))
	      {
	        // Scroll down...
	        v = topline_ + textsize_;

		if (v > (count_ * textsize_ - h() + SCROLLER))
		  v = count_ * textsize_ - h() + SCROLLER;

		topline_ = v;
	      }

	      damage(FL_DAMAGE_SCROLL);
	      return (1);
        }
	break;

    case FL_RELEASE :
        switch (state_)
	{
	  case SELECTING :
	      if (select_end_ >= 0)
	      {
	        // Copy the selection to the selection buffer...
	        char	*buf = selection();


                Fl::copy(buf, strlen(buf), 0);
		free(buf);
	      }

	      do_callback();

	  case UPARROW :
	  case DOWNARROW :
	      damage(FL_DAMAGE_SCROLL);

	  case SCROLLING :
              state_ = IDLE;
	      return (1);
	}
	break;

    case FL_MOUSEWHEEL :
        if (Fl::event_dy() && doscrolling)
	{
	  // Scroll vertically...
          topline_ += (h() - SCROLLER) * Fl::event_dy() / 8;

	  if (topline_ < 0)
	    topline_ = 0;
	  else if (topline_ > (count_ * textsize_ - h() + SCROLLER))
	    topline_ = count_ * textsize_ - h() + SCROLLER;

          damage(FL_DAMAGE_SCROLL);

	  return (1);
	}
	break;

    case FL_SHORTCUT :
        if (doscrolling)
          switch (Fl::event_key())
	  {
	    case FL_Up :
		topline_ -= textsize_;

		if (topline_ < 0)
	          topline_ = 0;

		damage(FL_DAMAGE_SCROLL);
		return (1);

	    case FL_Down :
		topline_ += textsize_;

		if (topline_ > (count_ * textsize_ - h() + SCROLLER))
	          topline_ = count_ * textsize_ - h() + SCROLLER;

		damage(FL_DAMAGE_SCROLL);
		return (1);

	    case FL_Page_Up :
	    case FL_BackSpace :
		topline_ -= h() - SCROLLER;

		if (topline_ < 0)
	          topline_ = 0;

		damage(FL_DAMAGE_SCROLL);
		return (1);

	    case FL_Page_Down :
	    case ' ' :
		topline_ += h() - SCROLLER;

		if (topline_ > (count_ * textsize_ - h() + SCROLLER))
	          topline_ = count_ * textsize_ - h() + SCROLLER;

		damage(FL_DAMAGE_SCROLL);
		return (1);

	    case FL_Home :
		topline_ = 0;

		damage(FL_DAMAGE_SCROLL);
		return (1);

	    case FL_End :
		topline_ = count_ * textsize_ - h() + SCROLLER;

		damage(FL_DAMAGE_SCROLL);
		return (1);
	  }
	break;
  }

  return (Fl_Group::handle(event));
}


//
// 'DiffView::load()' - Load a diff.
//

bool					// O - True on success, false otherwise
DiffView::load(const char *file1,	// I - First file
               const char *file2)	// I - Second file
{
  char		command[1024];		// Diff command
  CPtProcess	pdiff,			// Diff process
		pfp;			// Original file/directory listing
  const char	*cptr;			// Const pointer into line
  char		*ptr,			// Pointer into line
		line[10240],		// Line from file/diff
		*dirname,		// Directory name from directory diff
		*filename,		// Filename from directory diff
		*left,			// Left file
		*right;			// Right file
  bool		changed;		// Changed?
  int		fplinenum,		// Line number in first file
		start,			// Current start line
		count,			// Count of lines
		newstart,		// New file start
		newcount,		// New file count
		xwidth,			// Column pixel width
		pos;			// Insert position

    
  // Clear the current view...
  clear();

  // Validate input...
  if (!file1)
    return (false);

  // Do the diff...
  if (file2 && file2[0] != ':')
  {
    const char *diffopt = ignoreblanks() ? "-bBwE" : "";

    if (fl_filename_isdir(file1))
    {
      if (fl_filename_isdir(file2))
      {
	snprintf(command, sizeof(command), "diff %s -q \"%s\" \"%s\"",
	         diffopt, file1, file2);
	directories_ = true;
      }
      else
      {
	if ((cptr = strrchr(file2, '/')) != NULL)
          cptr ++;
	else
          cptr = file2;

	snprintf(command, sizeof(command), "diff %s -u \"%s/%s\" \"%s\"",
	         diffopt, file1, cptr, file2);
      }
    }
    else if (fl_filename_isdir(file2))
    {
      if ((cptr = strrchr(file1, '/')) != NULL)
        cptr ++;
      else
        cptr = file1;

      snprintf(command, sizeof(command), "diff %s -u \"%s\" \"%s/%s\"",
               diffopt, file1, file2, cptr);
    }
    else
      snprintf(command, sizeof(command), "diff %s -u \"%s\" \"%s\"", diffopt,
	       file1, file2);
  }
  else if (fl_filename_isdir(file1))
  {
    directories_ = true;

    snprintf(line, sizeof(line), "%s/CVS", file1);

    if (!access(line, 0))
    {
      if (file2)
	snprintf(command, sizeof(command), "cvs diff -r \"%s\" -u \"%s\"",
	         file2 + 1, file1);
      else
	snprintf(command, sizeof(command), "cvs diff -u \"%s\"",
	         file1);
    }
    else
    {
      snprintf(line, sizeof(line), "%s/.svn", file1);

      if (!access(line, 0))
      {
        if (file2)
          snprintf(command, sizeof(command), "svn diff -N -r \"%s\" \"%s\"",
		   file2 + 1, file1);
	else
          snprintf(command, sizeof(command), "svn diff -N \"%s\"", file1);
      }
      else
        return (false);
    }
  }
  else
  {
    strncpy(line, file1, sizeof(line) - 1);
    line[sizeof(line) - 1] = '\0';

    if ((ptr = strrchr(line, '/')) != NULL)
      ptr ++;
    else
      ptr = line;

    strncpy(ptr, "CVS", sizeof(line) - 1 - (ptr - line));

    if (!access(line, 0))
    {
      if (file2)
	snprintf(command, sizeof(command), "cvs -q diff -r \"%s\" -u \"%s\"",
	         file2 + 1, file1);
      else
	snprintf(command, sizeof(command), "cvs -q diff -u \"%s\"", file1);
    }
    else
    {
      strncpy(ptr, ".svn", sizeof(line) - 1 - (ptr - line));

      if (!access(line, 0))
      {
        const char *diffopt = ignoreblanks() ?
	                          "--diff-cmd diff --extensions '-bBwEu'" : "";
        if (file2)
          snprintf(command, sizeof(command), "svn diff %s -r \"%s\" \"%s\"", 
	           diffopt, file2 + 1, file1);
	else
          snprintf(command, sizeof(command), "svn diff %s \"%s\"",
	           diffopt, file1);
      }
      else
        return (false);
    }
  }

  if (!pdiff.popen(command))
  {
    fl_alert("Diff command failed!\n\n%s", command);
    return (false);
  }

  // Open the original file...
  if (file2 && file2[0] != ':')
  {
    if (directories_)
    {
      snprintf(command, sizeof(command), "ls -a1 \"%s\"", file2);
      pfp.popen(command, "r");
    }
    else
      pfp.fopen(file2, "r");
  }
  else if (directories_)
  {
    snprintf(command, sizeof(command), "ls -a1 \"%s\"", file1);
    pfp.popen(command, "r");
  }
  else
    pfp.fopen(file1, "r");

  if (!pfp.is_open())
  {
    fl_alert("Unable to open \"%s\":\n\n%s", file2 ? file2 : file1,
             strerror(errno));
    pdiff.close();
    return (false);
  }

  // Loop until we run out of diffs...
  fplinenum = 1;

  if (directories_)
  {
    while (pfp.get_line(line, sizeof(line)))
    {
      ptr = line + strlen(line) - 1;
      if (ptr >= line && *ptr == '\n')
	*ptr = '\0';

      add_line(line, line, false);
    }
  }

  while (pdiff.get_line(line, sizeof(line)))
  {
    if (directories_)
    {
      changed = true;

      if (!strncmp(line, "Only in ", 8) &&
          (ptr = strstr(line + 8, ": ")) != NULL)
      {
        // Extract directory and path...
        *ptr     = '\0';
	dirname  = line + 8;
	filename = ptr + 2;

	if (filename[0])
	  filename[strlen(filename) - 1] = '\0';

        if (!strcmp(file1, dirname))
	{
	  left  = filename;
	  right = NULL;
	}
	else
	{
	  left  = NULL;
	  right = filename;
	}
      }
      else if (!strncmp(line, "Files ", 6) &&
               (ptr = strstr(line + 6, " and ")) != NULL)
      {
        *ptr = '\0';

	if ((filename = strrchr(line + 6, '/')) != NULL)
	  filename ++;
	else
	  filename = line + 6;

        left  = filename;
	right = filename;
      }
      else if (!strncmp(line, "Common subdirectories: ", 23) &&
               (ptr = strstr(line + 23, " and ")) != NULL)
      {
        changed = false;

        *ptr = '\0';

	if ((filename = strrchr(line + 23, '/')) != NULL)
	  filename ++;
	else
	  filename = line + 23;

        left  = filename;
	right = filename;
      }
      else if (!strncmp(line, "Index: ", 7) ||
               !strncmp(line, "M      ", 7))
      {
        if ((filename = strrchr(line + 7, '/')) != NULL)
	  filename ++;
	else
          filename = line + 7;

	ptr = filename + strlen(filename) - 1;
	if (ptr >= filename && *ptr == '\n')
	  *ptr = '\0';

        left  = filename;
	right = filename;
      }
      else if (!strncmp(line, "?      ", 7) ||
               !strncmp(line, "A      ", 7))
      {
        if ((filename = strrchr(line + 7, '/')) != NULL)
	  filename ++;
	else
          filename = line + 7;

	ptr = filename + strlen(filename) - 1;
	if (ptr >= filename && *ptr == '\n')
	  *ptr = '\0';

        left  = NULL;
	right = filename;
      }
      else if (!strncmp(line, "? ", 2))
      {
        if ((filename = strrchr(line + 2, '/')) != NULL)
	  filename ++;
	else
          filename = line + 2;

	ptr = filename + strlen(filename) - 1;
	if (ptr >= filename && *ptr == '\n')
	  *ptr = '\0';

        left  = NULL;
	right = filename;
      }
      else
        continue;

      if (!changed)
        continue;

      if (left && !right)
      {
        // Insert line
	for (pos = 0; pos < count_; pos ++)
	  if (lines_[pos]->right && strcmp(lines_[pos]->right, filename) > 0)
	    break;

        if (pos >= count_)
	  add_line(left, right, changed);
	else
	{
           DiffLine	*line,		// Current line
			**temp;		// New array

	  if (count_ >= alloc_)
	  {
	    temp = new DiffLine *[alloc_ + 50];

	    if (count_)
	    {
	      memcpy(temp, lines_, count_ * sizeof(DiffLine *));
	      delete[] lines_;
	    }

	    lines_ = temp;
	    alloc_ += 50;
	  }

	  line          = new DiffLine;
	  line->left    = strdup(left);
	  line->dleft   = expand_tabs(left);
	  line->right   = (char *)0;
	  line->dright  = (char *)0;
	  line->changed = true;
	  line->start   = 0;
	  line->end     = strlen(line->dleft);

          memmove(lines_ + pos + 1, lines_ + pos,
	          (count_ - pos) * sizeof(DiffLine *));
	  lines_[pos] = line;
	  count_ ++;
	}
      }
      else
      {
        // Update existing line
	for (pos = 0; pos < count_; pos ++)
	  if (lines_[pos]->right && !strcmp(lines_[pos]->right, filename))
	  {
	    if (!left)
	    {
	      free(lines_[pos]->left);
	      lines_[pos]->left = (char *)0;
	      free(lines_[pos]->dleft);
	      lines_[pos]->dleft = (char *)0;
	    }
	    else if (!right)
	    {
	      free(lines_[pos]->right);
	      lines_[pos]->right = (char *)0;
	      free(lines_[pos]->dright);
	      lines_[pos]->dright = (char *)0;
	    }

	    lines_[pos]->changed = true;
	    break;
	  }
      }
    }
    else
    {
      // Skip until we see "@@ -start,count +start,count @@"...
      if (sscanf(line, "@@ -%d,%d +%d,%d @@", &start, &count, &newstart,
        	 &newcount) != 4)
	continue;

      while (fplinenum < newstart)
      {
	if (!pfp.get_line(line, sizeof(line)))
          break;

	add_line(line, line, false);
	fplinenum ++;
      }

      pos = -1;

      while (count > 0 || newcount > 0)
      {
	if (!pdiff.get_line(line, sizeof(line)))
          break;

	if (line[0] == '+')
	{
          add_line(NULL, line + 1, true, pos);
	  newcount --;

	  if (pos >= 0)
	    pos ++;
	}
	else if (line[0] == '-')
	{
          if (pos < 0)
	    pos = count_;

          add_line(line + 1, NULL, true);
	  count --;
	}
	else
	{
          add_line(line + 1, line + 1, false);
	  count --;
	  newcount --;
	  pos = -1;
	}

	if (line[0] != '-')
	{
          pfp.get_line(line, sizeof(line));
          fplinenum ++;
	}
      }
    }
  }

  pdiff.close();

  if (directories_)
    pfp.close();
  else
  {
    while (pfp.get_line(line, sizeof(line)) != NULL)
      add_line(line, line, false);

    pfp.close();
  }

  fl_font(textfont_, textsize_);

  xwidth    = (int)fl_width(' ');
  maxwidth_ *= xwidth;

  if (showlinenum_)
  {
    sprintf(line, "%d", count_);
    linenum_ = (int)strlen(line);
    sprintf(linefmt_, "%%%dd ", linenum_);
    linenum_ = (linenum_ + 1) * xwidth;
  }

  hscroll1_.value(0, w() / 2 - SCROLLER - linenum_, 0, maxwidth_);
  hscroll2_.value(0, w() / 2 - SCROLLER - linenum_, 0, maxwidth_);
  topline_ = 0;

  if (maxwidth_ > (w() / 2 - SCROLLER - linenum_))
  {
    hscroll1_.activate();
    hscroll2_.activate();
  }

  redraw();

  return (true);
}


//
// 'DiffView::resize()' - Resize a diff viewing widget.
//

void
DiffView::resize(int X,			// I - X position
                 int Y,			// I - Y position
		 int W,			// I - Width
		 int H)			// I - Height
{
  int	v;				// Scroll value


  Fl_Widget::resize(X, Y, W, H);

  hscroll1_.resize(X, Y + H - SCROLLER, (W - SCROLLER) / 2, SCROLLER);
  hscroll2_.resize(X + (W + SCROLLER) / 2, Y + H - SCROLLER,
                   (W - SCROLLER) / 2, SCROLLER);

  // Update scrollbars...
  if (maxwidth_ > ((W - SCROLLER) / 2 - linenum_))
  {
    hscroll1_.activate();
    hscroll2_.activate();

    if ((hscroll1_.value() + (W - SCROLLER) / 2 - linenum_) > maxwidth_)
      v = maxwidth_ - (W - SCROLLER) / 2 + linenum_;
    else
      v = hscroll1_.value();
  }
  else
  {
    hscroll1_.deactivate();
    hscroll2_.deactivate();
    v = 0;
  }

  hscroll1_.value(v, (W - SCROLLER) / 2 - linenum_, 0, maxwidth_);
  hscroll2_.value(v, (W - SCROLLER) / 2 - linenum_, 0, maxwidth_);

  if ((count_ * textsize_) > (h() - SCROLLER))
  {
    if ((topline_ + h() - SCROLLER) > (count_ * textsize_))
      topline_ = count_ * textsize_ - h() + SCROLLER;
  }
  else
    topline_ = 0;
}


//
// 'DiffView::scroll_cb()' - Scroll the diff.
//

void
DiffView::scroll_cb(Fl_Scrollbar *s,	// I - Scrollbar widget
                    DiffView     *d)	// I - DiffView widget
{
  if (s == &(d->hscroll1_))
    d->hscroll2_.value(d->hscroll1_.value(),
                       (d->w() - SCROLLER) / 2 - d->linenum_, 0,
                       d->maxwidth_);
  else if (s == &(d->hscroll2_))
    d->hscroll1_.value(d->hscroll2_.value(),
                       (d->w() - SCROLLER) / 2 - d->linenum_, 0,
                       d->maxwidth_);

  d->damage(FL_DAMAGE_SCROLL);
}


//
// 'DiffView::select()' - Select specific lines.
//

void
DiffView::select(int  s,		// I - Start line
                 int  e,		// I - End line
		 bool r)		// I - Righthand side?
{
  char	*buf;				// Selection buffer


  select_start_ = s;
  select_end_   = e;
  select_right_ = r;

  damage(FL_DAMAGE_SCROLL);

  if ((buf = selection()) != NULL)
  {
    Fl::copy(buf, strlen(buf), 0);
    free(buf);
  }
}


//
// 'DiffView::selection()' - Return the current text selection.
//

char *					// O - Selected text or NULL
DiffView::selection()
{
  int	i;				// Looping var
  char	*buf,				// Selection buffer
	*ptr;				// Pointer into line


  if (select_end_ < 0)
    return (NULL);

  buf = (char *)calloc(1, selection_length() + 1);
  
  for (ptr = buf, i = select_start_; i <= select_end_; i ++, ptr += strlen(ptr))
    if (select_right_ && lines_[i]->right)
      strcpy(ptr, lines_[i]->right);
    else if (!select_right_ && lines_[i]->left)
      strcpy(ptr, lines_[i]->left);

  return (buf);
}


//
// 'DiffView::selection_length()' - Return the length of the current selection.
//

int					// Length of selection text
DiffView::selection_length()
{
  int	i;				// Looping var
  int	len;				// Length of selection text


  if (select_end_ < 0)
    return (0);

  for (len = 0, i = select_start_; i <= select_end_; i ++)
    if (select_right_ && lines_[i]->right)
      len += strlen(lines_[i]->right);
    else if (!select_right_ && lines_[i]->left)
      len += strlen(lines_[i]->left);

  return (len);
}


//
// 'DiffView::showline()' - Show a line in the diff...
//

void
DiffView::showline(int l)		// I - Line to show
{
  l *= textsize_;

  if (l < topline_ || l >= (topline_ + h() - SCROLLER))
  {
    l -= (h() - SCROLLER) / 2;

    if (l < 0)
      l = 0;
    else if (l > (count_ * textsize_ - h() + SCROLLER))
      l = count_ * textsize_ - h() + SCROLLER;

    topline_ = l;

    damage(FL_DAMAGE_SCROLL);
  }
}


//
// 'DiffView::timeout_cb()' - Repeat scrollbar buttons...
//

void
DiffView::timeout_cb(DiffView *d)	// I - DiffView widget
{
  if (d->state_ == UPARROW && d->topline_ > 0)
  {
    d->topline_ -= 2 * d->textsize_;

    if (d->topline_ <= 0)
      d->topline_ = 0;
    else
      Fl::add_timeout(0.1f, (void (*)(void *))timeout_cb, (void *)d);

    d->damage(FL_DAMAGE_SCROLL);
  }
  else if (d->state_ == DOWNARROW &&
           d->topline_ < (d->count_ * d->textsize_ - d->h() + SCROLLER))
  {
    d->topline_ += 2 * d->textsize_;

    if (d->topline_ >= (d->count_ * d->textsize_ - d->h() + SCROLLER))
      d->topline_ = d->count_ * d->textsize_ - d->h() + SCROLLER;
    else
      Fl::add_timeout(0.1f, (void (*)(void *))timeout_cb, (void *)d);

    d->damage(FL_DAMAGE_SCROLL);
  }
}


//
// End of "$Id: DiffView.cxx 407 2006-11-13 18:54:02Z mike $".
//
