//
// "$Id: DiffWindow.cxx 407 2006-11-13 18:54:02Z mike $"
//
// DiffWindow 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:
//
//   DiffWindow::DiffWindow()          - Create a diff window.
//   DiffWindow::~DiffWindow()         - Destroy a diff window.
//   DiffWindow::about_cb()            - Show the about dialog.
//   DiffWindow::close_cb()            - Close the current window.
//   DiffWindow::compare_cb()          - Compare files.
//   DiffWindow::compare_selected_cb() - Compare selected files.
//   DiffWindow::copy_cb()             - Copy the diff view selection.
//   DiffWindow::draw_overlay()        - Draw the zoom overlay...
//   DiffWindow::find_cb()             - Find a string in the diff.
//   DiffWindow::find_next_cb()        - Find the next occurrence of the string.
//   DiffWindow::find_window()         - Find a window that already has a diff open.
//   DiffWindow::handle()              - Handle UI events in the window.
//   DiffWindow::load()                - Load a diff.
//   DiffWindow::load_prefs()          - Load preferences...
//   DiffWindow::next_change_cb()      - Show the next change.
//   DiffWindow::open()                - Open a diff.
//   DiffWindow::prefs_cb()            - Show the preferences.
//   DiffWindow::prev_change_cb()      - Show the previous change.
//   DiffWindow::quit_cb()             - Quit the fldiff application.
//   DiffWindow::rediff_cb()           - Redo the diff view.
//   DiffWindow::resize()              - Resize the window...
//   DiffWindow::save_prefs()          - Save preferences...
//   DiffWindow::select_cb()           - Handle selections in the diff view.
//   DiffWindow::select_left_cb()      - Select everything on the left side.
//   DiffWindow::select_none_cb()      - Select nothing.
//   DiffWindow::select_right_cb()     - Select everything on the right side.
//   DiffWindow::using_cb()            - Show help window.
//

#include "DiffWindow.h"
#include "DiffOpenWindow.h"
#include <FL/Fl_Box.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Help_View.H>
#include <FL/Fl_Spinner.H>
#include <FL/fl_ask.H>
#include <FL/fl_show_colormap.H>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#if defined(WIN32)
#include <io.h>
#define snprintf _snprintf
#define access _access
#else
#include <unistd.h>
#endif


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

#define SCROLLER	16


//
// DiffWindow globals...
//

Fl_Preferences	DiffWindow::prefs_(Fl_Preferences::USER, "fltk.org", "fldiff");
DiffWindow	*DiffWindow::first_ = (DiffWindow *)0;
const char	*DiffWindow::help_text_ =
"The bar in the center of the window shows the changes in the\n"
"entire file in yellow - the darkened box is the part of the diff\n"
"you are viewing. If the diff is larger than can be represented\n"
"in the change bar, a small zoom overlay will appear showing a\n"
"full window's worth of changes centered at the mouse\n"
"position.</p>\n"
"<p>Click in the change bar to immediately move to that position\n"
"in the file or the up/down arrow button to move up or down in\n"
"the diff. You can also use the <kbd>PageUp</kbd>,\n"
"<kbd>PageDown</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>,\n"
"<kbd>BackSpace</kbd>, <kbd>Space</kbd>, <kbd>Home</kbd>,\n"
"<kbd>End</kbd>, <kbd>N</kbd>, and <kbd>P</kbd> keys to move up\n"
"and down within the diff. The <kbd>N</kbd> and <kbd>P</kbd> keys\n"
"show the next and previous changes in the diff,\n"
"respectively.</p>\n"
"<p>Use the scrollbars to scroll within the diff; the scrollbars\n"
"are linked so scrolling one side also scrolls the other\n"
"side.</p>\n"
"<p>You can search both sides of the diff by pressing\n"
"<kbd>CTRL+f</kbd> or choosing <var>Find...</var> from the\n"
"<var>Search</var> menu. Repeat the search by pressing\n"
"<kbd>CTRL+g</kbd> or choosing <var>Find Next</var> from the\n"
"<var>Search</var> menu.</p>\n"
"<p>Drag the mouse to select the text on either side. Press\n"
"<kbd>CTRL+a</kbd> to select all of the text on the left or\n"
"<kbd>SHIFT+CTRL+a</kbd> to select all of the text on the right.\n"
"Press <kbd>CTRL+c</kbd> to copy the corresponding lines of text\n"
"to the clipboard.</p>\n"
"<p>The <var>File</var> menu allows you to open new diffs and\n"
"close the current diff. You can also drop files in the diff\n"
"window to open new diffs or double-click on filenames when\n"
"comparing directories.</p>\n"
"<p>Choose <var>Preferences</var> from the <var>Edit</var> menu\n"
"to show the preferences dialog which allows you to customize the\n"
"display. These preferences and the current window size are\n"
"remembered each time you run the program or open a new file.</p>\n";


//
// 'DiffWindow::DiffWindow()' - Create a diff window.
//

DiffWindow::DiffWindow(const char *file1,
					// I - First file
                       const char *file2)
					// I - Second file
  : Fl_Overlay_Window(640, 480),
    menubar_(0, 0, 640, 25),
    view_(0, 25, 640, 455)
{
  int	W,				// Width of window
	H;				// Height of window


  // Finalize the UI...
  end();

  resizable(&view_);

  menubar_.box(FL_THIN_UP_BOX);
  menubar_.add("&File/Open\\/Compare...", FL_COMMAND | 'o',
               (Fl_Callback *)compare_cb, this);
  menubar_.add("&File/Open\\/Compare &Selected", FL_COMMAND | 'y',
               (Fl_Callback *)compare_selected_cb, this);
  menubar_.add("&File/&Close", FL_COMMAND | 'w',
               (Fl_Callback *)close_cb, this, FL_MENU_DIVIDER);
  menubar_.add("&File/&Redo Diff", FL_COMMAND | 'r',
               (Fl_Callback *)rediff_cb, this, FL_MENU_DIVIDER);
  menubar_.add("&File/&Quit", FL_COMMAND | 'q',
               (Fl_Callback *)quit_cb, this);

  menubar_.add("&Edit/Copy", FL_COMMAND | 'c',
               (Fl_Callback *)copy_cb, this, FL_MENU_DIVIDER);
  menubar_.add("&Edit/Select Left", FL_COMMAND | 'a',
               (Fl_Callback *)select_left_cb, this);
  menubar_.add("&Edit/Select Right", FL_COMMAND | 'A',
               (Fl_Callback *)select_right_cb, this);
  menubar_.add("&Edit/Select None", FL_COMMAND | 'n',
               (Fl_Callback *)select_none_cb, this, FL_MENU_DIVIDER);
  menubar_.add("&Edit/Preferences...", 0,
               (Fl_Callback *)prefs_cb, this);

  menubar_.add("&Search/Find...", FL_COMMAND | 'f',
               (Fl_Callback *)find_cb, this);
  menubar_.add("&Search/Find Next", FL_COMMAND | 'g',
               (Fl_Callback *)find_next_cb, this, FL_MENU_DIVIDER);
  menubar_.add("&Search/Previous Change", 'p',
               (Fl_Callback *)prev_change_cb, this);
  menubar_.add("&Search/Next Change", 'n',
               (Fl_Callback *)next_change_cb, this);

  menubar_.add("&Help/Using fldiff...", FL_F + 1,
               (Fl_Callback *)using_cb, this);
  menubar_.add("&Help/About fldiff...", 0,
               (Fl_Callback *)about_cb, this);

  view_.callback((Fl_Callback *)select_cb, this);

  load_prefs();

  // Get the previous window size and resize...
  prefs_.get("window_width", W, 640);
  prefs_.get("window_height", H, 480);

  resize(x(), y(), W, H);

  // Clear the zoom data...
  zoom_     = true;
  zoom_pos_ = -1;

  // Clear the search data...
  search_[0]    = '\0';
  search_line_  = -1;
  search_right_ = true;

  // Load the diff...
  file1_ = NULL;
  file2_ = NULL;

  load(file1, file2);

  select_cb(&view_, this);

  // Add this window to the list...
  next_  = first_;
  first_ = this;
}


//
// 'DiffWindow::~DiffWindow()' - Destroy a diff window.
//

DiffWindow::~DiffWindow()
{
  DiffWindow	*current,		// Current diff window
		*prev;			// Previous diff window


  // Free the filenames...
  if (file1_)
    free((void *)file1_);
  if (file2_)
    free((void *)file2_);

  // Remove the window from the list...
  for (current = first_, prev = (DiffWindow *)0;
       current;
       prev = current, current = current->next_)
    if (current == this)
      break;

  if (current)
  {
    if (prev)
      prev->next_ = current->next_;
    else
      first_ = current->next_;
  }
}


//
// 'DiffWindow::about_cb()' - Show the about dialog.
//

void
DiffWindow::about_cb(Fl_Menu_Bar *m,	// I - Menubar
                     DiffWindow  *dw)	// I - Window
{
  fl_message(VERSION "\nCopyright 2005-2006 by Michael Sweet\n"
	     "This program is free software provided under the\n"
	     "terms of the GNU General Public License v2.");
}


//
// 'DiffWindow::close_cb()' - Close the current window.
//

void
DiffWindow::close_cb(Fl_Menu_Bar *m,	// I - Menubar
                     DiffWindow  *dw)	// I - Window
{
  if (dw->next_ || first_ != dw)
    Fl::delete_widget(dw);
  else
    dw->load(NULL, NULL);
}


//
// 'DiffWindow::compare_cb()' - Compare files.
//

void
DiffWindow::compare_cb(Fl_Menu_Bar *m,	// I - Menubar
                       DiffWindow  *dw)	// I - Window
{
  DiffOpenWindow	*dow;		// Open dialog window
  int			i, j;		// Looping vars
  int			c1,		// First count
			c2;		// Second count
  const char		*f1,		// First file
			*f2;		// Second file
  int			W,		// Width of window
			H;		// Height of window


  // Create and show the open window...
  dow = new DiffOpenWindow(dw->file1_ ? dw->file1_ : ".",
                           dw->file2_ ? dw->file2_ : ".");

  // Get the previous window size and resize...
  prefs_.get("open_width", W, 640);
  prefs_.get("open_height", H, 480);

  dow->resize(dow->x(), dow->y(), W, H);

  dow->show();

  while (dow->shown())
    Fl::wait();

  prefs_.set("open_width", dow->w());
  prefs_.set("open_height", dow->h());

  // See what is chosen in each window...
  c1 = dow->dc1()->count();
  c2 = dow->dc2()->count();

  if (c1 > 0 && c2 <= 1)
  {
    // Compare N files against a single file/directory.
    f2 = dow->dc2()->value();
    i  = strlen(f2);
    j  = strlen(dow->dc1()->directory());

    if (!strncmp(f2, dow->dc1()->directory(), i) &&
        (i == j || (i == (j - 1) && dow->dc1()->directory()[i] == '/')))
      f2 = NULL;

    if (c1 <= 1)
    {
      // Compare one file/directory against another file/directory...
      f1 = dow->dc1()->value();

      dw->open(f1, f2);
    }
    else
    {
      // Compare two or more files/directories against another file/directory...
      for (i = 1; i <= dow->dc1()->size(); i ++)
        if (dow->dc1()->selected(i))
	{
          f1 = dow->dc1()->value(i);

          dw->open(f1, f2);
	}
    }
  }
  else if (c1 && c2)
  {
    // Compare against multiple files/directories...
    if (c1 <= 1)
    {
      // Compare a single file/directory against N files/directories...
      f1 = dow->dc1()->value();
      i  = strlen(f1);
      j  = strlen(dow->dc2()->directory());

      if (!strncmp(f1, dow->dc2()->directory(), i) &&
          (i == j || (i == (j - 1) && dow->dc2()->directory()[i] == '/')))
	f1 = NULL;

      for (i = 1; i <= dow->dc2()->size(); i ++)
        if (dow->dc2()->selected(i))
	{
          f2 = dow->dc2()->value(i);

          dw->open(f2, f1);
	}
    }
    else if (c1 == c2)
    {
      // Compare N files/directories...
      for (i = 1, j = 0; i <= dow->dc1()->size(); i ++)
        if (dow->dc1()->selected(i))
	{
          f1 = dow->dc1()->value(i);

          for (j ++; j <= dow->dc1()->size(); j ++)
	    if (dow->dc2()->selected(j))
	      break;

          if (j <= dow->dc1()->size())
	  {
	    f2 = dow->dc2()->value(j);
	    dw->open(f1, f2);
	  }
	}
    }
    else
    {
      // We don't support comparing N to M...
      fl_alert("Can only compare N files/directories against 1 or N files/directories!");
    }
  }

  // Delete the file chooser...
  delete dow;
}


//
// 'DiffWindow::compare_selected_cb()' - Compare selected files.
//

void
DiffWindow::compare_selected_cb(Fl_Menu_Bar *m,
					// I - Menubar
                                DiffWindow  *dw)
					// I - Window
{
  int		i;			// Looping var
  char		nfile1[1024],		// New first file
		nfile2[1024];		// New second file
  const char	*nfptr2;		// Pointer to second file


  for (i = dw->view_.select_start(); i <= dw->view_.select_end(); i ++)
  {
    if (dw->view_.left(i) && dw->view_.right(i))
    {
      snprintf(nfile1, sizeof(nfile1), "%s/%s", dw->file1_, dw->view_.left(i));
      if (dw->file2_ && dw->file2_[0] != ':')
      {
	snprintf(nfile2, sizeof(nfile2), "%s/%s", dw->file2_,
        	 dw->view_.right(i));
	nfptr2 = nfile2;
      }
      else
	nfptr2 = dw->file2_;

      dw->open(nfile1, nfptr2);
    }
    else if (dw->view_.left(i))
      fl_alert("No diff available for \"%s\"", dw->view_.left(i));
    else
      fl_alert("No diff available for \"%s\"", dw->view_.right(i));
  }
}


//
// 'DiffWindow::copy_cb()' - Copy the diff view selection.
//

void
DiffWindow::copy_cb(Fl_Menu_Bar *m,	// I - Menubar
                    DiffWindow  *dw)	// I - Window
{
  char *buf = dw->view_.selection();	// Selection text


  if (buf)
  {
    Fl::copy(buf, strlen(buf), 1);
    free(buf);
  }
}


//
// 'DiffWindow::draw_overlay()' - Draw the zoom overlay...
//

void
DiffWindow::draw_overlay()
{
  int	xoffset,			// X offset
	ystart,				// Start Y pos
	yend,				// End Y pos
	pstart,				// Page start Y pos
	pend;				// Page end Y pos
  int	line,				// Current line
	startline,			// Start line
        endline,			// End line
	lines;				// Lines per page


  if (zoom_pos_ >= 0 && view_.count())
  {
    lines     = (h() - 25 - SCROLLER) / textsize();
    line      = (zoom_pos_ - 26 - SCROLLER) * view_.count() /
                (h() - 3 * SCROLLER - 27);
    startline = line - lines / 2;
    endline   = line + lines / 2;

    if (startline < 0)
    {
      endline   -= startline;
      startline = 0;
    }

    if (endline >= view_.count())
    {
      startline -= endline - view_.count() + 1; 
      endline   = view_.count() - 1;
    }

    if (startline < 0)
      startline = 0;

    xoffset = (w() + SCROLLER) / 2;
    ystart  = zoom_pos_ - line + startline - 1;
    yend    = zoom_pos_ - line + endline + 1;

    if (ystart < 25)
    {
      yend   -= ystart - 25;
      ystart = 25;
    }

    if (yend >= (h() - SCROLLER))
    {
      ystart -= yend - h() + SCROLLER + 1;
      yend   = h() - SCROLLER - 1;
    }

    pstart = startline * (h() - 27 - 3 * SCROLLER) / view_.count() +
             25 + SCROLLER;
    pend   = (endline + 1) * (h() - 27 - 3 * SCROLLER) / view_.count() +
             26 + SCROLLER;

    fl_color(fl_color_average(FL_DARK2, textcolor(), 0.5f));
    fl_rectf(xoffset + 30, ystart, SCROLLER, yend - ystart);

    fl_color(textcolor());
    fl_rect(xoffset + 30, ystart, SCROLLER, yend - ystart);
    fl_rect(xoffset - SCROLLER, pstart, SCROLLER, pend - pstart);
    fl_line(xoffset, pstart, xoffset + 30, ystart);
    fl_line(xoffset, pend, xoffset + 30, yend);

    fl_color(selection_color());
    for (line = startline; line <= endline; line ++)
      if (view_.line_changed(line))
      {
        if (view_.left(line) && view_.right(line))
          fl_xyline(xoffset + 31, ystart + line - startline + 1,
	            xoffset + 30 + SCROLLER);
        else if (view_.left(line))
	  fl_xyline(xoffset + 31, ystart + line - startline + 1,
	            xoffset + 30 + SCROLLER / 2);
        else
          fl_xyline(xoffset + 31 + SCROLLER / 2, ystart + line - startline + 1,
	            xoffset + 30 + SCROLLER);
      }
  }
}


//
// 'DiffWindow::find_cb()' - Find a string in the diff.
//

void
DiffWindow::find_cb(Fl_Menu_Bar *m,	// I - Menubar
                    DiffWindow  *dw)	// I - Window
{
  const char	*s;			// String


  if ((s = fl_input("Search For?", dw->search_)) != NULL)
  {
    strncpy(dw->search_, s, sizeof(dw->search_) - 1);
    dw->search_[sizeof(dw->search_) - 1] = '\0';
    dw->search_line_  = -1;
    dw->search_right_ = true;

    find_next_cb(m, dw);
  }
}


//
// 'DiffWindow::find_next_cb()' - Find the next occurrence of the string.
//

void
DiffWindow::find_next_cb(Fl_Menu_Bar *m,// I - Menubar
                         DiffWindow  *dw)
					// I - Window
{
  const char	*line;			// Text from line


  if (!dw->search_[0])
  {
    fl_beep();
    return;
  }

  dw->view_.select(-1, -1, false);

  while (dw->search_line_ < dw->view_.count())
  {
    // Advance to the next 
    if (dw->search_right_)
    {
      dw->search_right_ = false;
      dw->search_line_ ++;

      if (dw->search_line_ >= dw->view_.count())
        break;
    }
    else
      dw->search_right_ = true;

    if (dw->search_right_)
      line = dw->view_.right(dw->search_line_);
    else
      line = dw->view_.left(dw->search_line_);

    if (line && strstr(line, dw->search_) != NULL)
    {
      dw->view_.select(dw->search_line_, dw->search_line_, dw->search_right_);
      dw->view_.showline(dw->search_line_);
      return;
    }

  }

  fl_alert("Search string not found.");
}


//
// 'DiffWindow::find_window()' - Find a window that already has a diff open.
//

DiffWindow *				// O - Window or NULL
DiffWindow::find_window(const char *f1,	// I - First file
                        const char *f2)	// I - Second file
{
  DiffWindow	*dw;			// Current window
  char		filename[1024];		// Real second file


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

  if (f1 && f2)
  {
    if (!fl_filename_isdir(f1) && fl_filename_isdir(f2))
    {
      if (f2[strlen(f2) - 1] == '/')
	snprintf(filename, sizeof(filename), "%s%s", f2,
	         fl_filename_name(f1));
      else
	snprintf(filename, sizeof(filename), "%s/%s", f2,
	         fl_filename_name(f1));
    }
    else
      strncpy(filename, f2, sizeof(filename) - 1);
  }
  else if (f2)
    strncpy(filename, f2, sizeof(filename) - 1);

  for (dw = first_; dw; dw = dw->next_)
    if (((dw->file1_ == f1 || (dw->file1_ && f1 && !strcmp(dw->file1_, f1))) &&
         (dw->file2_ == f2 || (dw->file2_ && f2 &&
	  !strcmp(dw->file2_, filename))) ||
        ((dw->file1_ && f2 && !strcmp(dw->file1_, filename)) &&
	 (dw->file2_ && f1 && !strcmp(dw->file2_, f1)))))
      return (dw);

  return ((DiffWindow *)0);
}


//
// 'DiffWindow::handle()' - Handle UI events in the window.
//

int					// O - 1 if handled, 0 otherwise
DiffWindow::handle(int event)		// I - Event
{
  char		*ptr;			// Pointer into DND data
  int		num_files;		// Number of files in URL list
  char		*files[100];		// Files


  switch (event)
  {
    case FL_PUSH :
	if (zoom_pos_ >= 0)
	{
	  zoom_pos_ = -1;
	  redraw_overlay();
	}
	break;

    case FL_RELEASE :
    case FL_MOVE :
    case FL_DRAG :
        if (Fl::event_y() >= (25 + SCROLLER) &&
	    Fl::event_y() < (h() - 2 * SCROLLER) &&
	    Fl::event_x() >= ((w() - SCROLLER) / 2) &&
	    Fl::event_x() < ((w() + SCROLLER) / 2) &&
	    zoom_ &&
	    view_.count() > (h() - 27 - SCROLLER))
	{
	  zoom_pos_ = Fl::event_y();
	  redraw_overlay();
	}
	else if (zoom_pos_ >= 0)
	{
	  zoom_pos_ = -1;
	  redraw_overlay();
	}
	break;

    case FL_DND_ENTER :
        Fl::focus(this);
    case FL_ENTER :
    case FL_LEAVE :
    case FL_FOCUS :
    case FL_UNFOCUS :
    case FL_DND_LEAVE :
    case FL_DND_DRAG :
        return (1);

    case FL_DND_RELEASE :
        take_focus();
        return (1);

    case FL_PASTE :
        printf("PASTE: \"%s\"\n", Fl::event_text());

        for (ptr = (char *)Fl::event_text(), num_files = 0; ptr && *ptr;)
	{
	  if (!strncmp(ptr, "file:", 5))
	  {
	    files[num_files] = ptr + 5;
	    num_files ++;
	  }
	  else if (ptr[0] == '/')
	  {
	    files[num_files] = ptr;
	    num_files ++;
	  }

          if ((ptr = strstr(ptr, "\r\n")) != NULL)
	  {
	    *ptr = '\0';
	    ptr += 2;
	  }

	  if (num_files >= (int)(sizeof(files) / sizeof(files[0])))
	    break;
	}

        if (num_files < 1 || num_files > 2)
	  fl_alert("Sorry, fldiff only supports comparing 1 file to a\n"
	           "repository or 2 files to each other...");
        else if (file1_)
	{
	  DiffWindow *dw;		// New diff window


          if (num_files == 1)
	    dw = new DiffWindow(files[0], NULL);
          else
	    dw = new DiffWindow(files[0], files[1]);

          dw->show();
	}
	else if (num_files == 1)
	  load(files[0], NULL);
        else
	  load(files[0], files[1]);

        return (1);
  }

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


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

bool					// O - True on success, false on failure
DiffWindow::load(const char *file1,	// I - First file
                 const char *file2)	// I - Second file
{
  Fl_Menu_Item	*item;			// Compare selected menu item...
  char		filename[1024];		// Real second file


  item = (Fl_Menu_Item *)menubar_.menu() + 2;
  item->deactivate();

  redraw();

  if (file1_)
    free((void *)file1_);
  if (file2_)
    free((void *)file2_);

  if (file1 && !*file1)
    file1 = NULL;
  if (file2 && !*file2)
    file2 = NULL;

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

  if (file1 && file2)
  {
    if (!fl_filename_isdir(file1) && fl_filename_isdir(file2))
    {
      if (file2[strlen(file2) - 1] == '/')
	snprintf(filename, sizeof(filename), "%s%s", file2,
	         fl_filename_name(file1));
      else
	snprintf(filename, sizeof(filename), "%s/%s", file2,
	         fl_filename_name(file1));
    }
    else
      strncpy(filename, file2, sizeof(filename) - 1);
  }
  else if (file2)
    strncpy(filename, file2, sizeof(filename) - 1);

  file1_ = file1 ? strdup(file1) : NULL;
  file2_ = file2 ? strdup(filename) : NULL;

  if (!view_.load(file1_, file2_))
  {
    if (file1_ || file2_)
      fl_alert("Sorry, no diff available!");

    label(VERSION);

    return (false);
  }

  // Format a title...
  if (file2_ && file2_[0] != ':')
    snprintf(title_, sizeof(title_), "%s <-> %s - " VERSION, file1_, file2_);
  else if (file2_)
    snprintf(title_, sizeof(title_), "r%s <-> %s - " VERSION, file2_ + 1, file1_);
  else
    snprintf(title_, sizeof(title_), "repos <-> %s - " VERSION, file1_);

  label(title_);

  select_cb(&view_, this);

  return (true);
}


//
// 'DiffWindow::load_prefs()' - Load preferences...
//

void
DiffWindow::load_prefs()
{
  int		v;			// Value


  prefs_.get("color", v, FL_WHITE);
  color((Fl_Color)v);

  prefs_.get("selection_color", v, FL_YELLOW);
  selection_color((Fl_Color)v);

  prefs_.get("showlinenum", v, true);
  showlinenum(v ? true : false);

  prefs_.get("tabwidth", v, 8);
  tabwidth(v);

  prefs_.get("textcolor", v, FL_BLACK);
  textcolor((Fl_Color)v);

  prefs_.get("textsize", v, FL_NORMAL_SIZE);
  textsize(v);

  prefs_.get("ignoreblanks", v, false);
  ignoreblanks(v ? true : false);
}


//
// 'DiffWindow::next_change_cb()' - Show the next change.
//

void
DiffWindow::next_change_cb(Fl_Menu_Bar *m,
					// I - Menubar
                           DiffWindow  *dw)
					// I - Window
{
  int	i;				// Looping var


  // Skip the rest of the current change at the bottom of the screen...
  i = dw->view_.topline() + (dw->view_.h() - SCROLLER) /
                            dw->view_.textsize() - 1;

  while (i < dw->view_.count() && dw->view_.line_changed(i))
    i ++;

  // Then find the next change...
  for (; i < dw->view_.count(); i ++)
    if (dw->view_.line_changed(i))
    {
      dw->view_.showline(i);
      return;
    }

  fl_alert("No more changes follow the current position.");
}


//
// 'DiffWindow::open()' - Open a diff.
//

void
DiffWindow::open(const char *f1,	// I - First file
                 const char *f2)	// I - Second file
{
  DiffWindow	*dw;			// New diff window


  if ((dw = find_window(f1, f2)) == NULL)
  {
    // Diff not already open...
    if (file1_)
    {
      // Create a new window
      dw = new DiffWindow(f1, f2);
    }
    else
    {
      // Load in the current window
      dw = this;
      load(f1, f2);
    }
  }

  // Show/raise the window...
  dw->show();
}


//
// 'DiffWindow::prefs_cb()' - Show the preferences.
//

void
DiffWindow::prefs_cb(Fl_Menu_Bar *m,	// I - Menubar
                     DiffWindow  *dw)	// I - Window
{
  Fl_Double_Window	*win;		// Preferences window
  Fl_Group		*group;		// Control group
  Fl_Spinner		*tabwidth,	// Tab size
			*textsize;	// Text size
  Fl_Button		*textfg,	// Foreground color
			*textbg,	// Background color
			*hicolor;	// Highlight color
  Fl_Check_Button	*linenum;	// Show line numbers?
  Fl_Check_Button	*ignoreb;	// ignore blanks during diff compare ?
  Fl_Button		*ok,		// OK button
			*cancel;	// Cancel button
  Fl_Widget		*current;	// Current widget


  win = new Fl_Double_Window(220, 310, "Preferences - " VERSION);
  win->modal();

    group = new Fl_Group(10, 10, 200, 255);
    group->box(FL_THIN_DOWN_BOX);
    group->color(FL_GRAY - 1);

      tabwidth = new Fl_Spinner(160, 20, 40, 25, "Tab Width:");
      tabwidth->align(FL_ALIGN_LEFT);
      tabwidth->range(1.0, 20.0);
      tabwidth->step(1.0);
      tabwidth->value(dw->view_.tabwidth());

      textsize = new Fl_Spinner(160, 55, 40, 25, "Text Size:");
      textsize->align(FL_ALIGN_LEFT);
      textsize->range(6.0, 20.0);
      textsize->step(1.0);
      textsize->value(dw->view_.textsize());

      textfg = new Fl_Button(160, 90, 25, 25, "Text Color:");
      textfg->align(FL_ALIGN_LEFT);
      textfg->color(dw->view_.textcolor());
      textfg->box(FL_BORDER_BOX);

      textbg = new Fl_Button(160, 125, 25, 25, "Background Color:");
      textbg->align(FL_ALIGN_LEFT);
      textbg->color(dw->view_.color());
      textbg->box(FL_BORDER_BOX);

      hicolor = new Fl_Button(160, 160, 25, 25, "Highlight Color:");
      hicolor->align(FL_ALIGN_LEFT);
      hicolor->color(dw->view_.selection_color());
      hicolor->box(FL_BORDER_BOX);

      linenum = new Fl_Check_Button(160, 195, 25, 25, "Show Line Numbers:");
      linenum->align(FL_ALIGN_LEFT);
      linenum->value(dw->view_.showlinenum());

      ignoreb = new Fl_Check_Button(160, 230, 25, 25, "Ignore Whitespace:");
      ignoreb->align(FL_ALIGN_LEFT);
      ignoreb->value(dw->view_.ignoreblanks());

    group->end();

    ok = new Fl_Button(100, 275, 40, 25, "OK");
    ok->shortcut(FL_Enter);
    cancel = new Fl_Button(150, 275, 60, 25, "Cancel");

  win->end();
  win->hotspot(win);
  win->show();

  while (win->shown())
  {
    if ((current = Fl::readqueue()) == cancel)
      break;
    else if (current == ok)
    {
      // Apply prefs
      dw->view_.color(textbg->color());
      dw->view_.textcolor(textfg->color());
      dw->view_.tabwidth((int)tabwidth->value());
      dw->view_.textsize((int)textsize->value());
      dw->view_.selection_color(hicolor->color());
      dw->view_.showlinenum(linenum->value() ? true : false);
      dw->view_.ignoreblanks(ignoreb->value() ? true : false);

      // Reload the diff...
      dw->view_.load(dw->file1_, dw->file2_);

      // Save prefs...
      dw->save_prefs();
      break;
    }
    else if (current == textfg || current == textbg || current == hicolor)
      current->color(fl_show_colormap(current->color()));

    Fl::wait();
  }

  win->hide();
  delete win;
}


//
// 'DiffWindow::prev_change_cb()' - Show the previous change.
//

void
DiffWindow::prev_change_cb(Fl_Menu_Bar *m,
					// I - Menubar
                           DiffWindow  *dw)
					// I - Window
{
  int	i;				// Looping var


  for (i = dw->view_.topline() - 1; i >= 0; i --)
    if (dw->view_.line_changed(i))
    {
      while (i >= 0 && dw->view_.line_changed(i))
        i --;

      dw->view_.showline(i + 1);
      return;
    }

  fl_alert("No more changes precede the current position.");
}


//
// 'DiffWindow::quit_cb()' - Quit the fldiff application.
//

void
DiffWindow::quit_cb(Fl_Menu_Bar *m,	// I - Menubar
                    DiffWindow  *dw)	// I - Window
{
  for (dw = first_; dw; dw = dw->next_)
    Fl::delete_widget(dw);
}


//
// 'DiffWindow::rediff_cb()' - Redo the diff view.
//

void
DiffWindow::rediff_cb(Fl_Menu_Bar *m,	// I - Menubar
                      DiffWindow  *dw)	// I - Window
{
  dw->view_.load(dw->file1_, dw->file2_);
}


//
// 'DiffWindow::resize()' - Resize the window...
//

void
DiffWindow::resize(int X,		// I - X position
                   int Y,		// I - Y position
		   int W,		// I - Width
		   int H)		// I - Height
{
  prefs_.set("window_width", W);
  prefs_.set("window_height", H);

  Fl_Overlay_Window::resize(X, Y, W, H);
}


//
// 'DiffWindow::save_prefs()' - Save preferences...
//

void
DiffWindow::save_prefs()
{
  // Save the window prefs for the next run...
  prefs_.set("color", color());
  prefs_.set("selection_color", selection_color());
  prefs_.set("showlinenum", showlinenum());
  prefs_.set("tabwidth", tabwidth());
  prefs_.set("textcolor", textcolor());
  prefs_.set("textsize", textsize());
  prefs_.set("ignoreblanks", ignoreblanks());
}


//
// 'DiffWindow::select_cb()' - Handle selections in the diff view.
//

void
DiffWindow::select_cb(DiffView   *dv,	// I - View
                      DiffWindow *dw)	// I - Window
{
  Fl_Menu_Item	*item;			// Copy menu item...


  item = (Fl_Menu_Item *)dw->menubar_.menu() + 8;

  if (dw->view_.selection_length())
    item->activate();
  else
    item->deactivate();

  item = (Fl_Menu_Item *)dw->menubar_.menu() + 2;

  if (dw->view_.directories() && dw->view_.select_start() >= 0)
  {
    item->activate();

    if (Fl::event_clicks())
      compare_selected_cb(&(dw->menubar_), dw);
  }
  else
    item->deactivate();
}


//
// 'DiffWindow::select_left_cb()' - Select everything on the left side.
//

void
DiffWindow::select_left_cb(Fl_Menu_Bar *m,
					// I - Menubar
                           DiffWindow  *dw)
					// I - Window
{
  dw->view_.select(0, dw->view_.count() - 1, false);
}


//
// 'DiffWindow::select_none_cb()' - Select nothing.
//

void
DiffWindow::select_none_cb(Fl_Menu_Bar *m,
					// I - Menubar
                           DiffWindow  *dw)
					// I - Window
{
  dw->view_.select(-1, -1, false);
}


//
// 'DiffWindow::select_right_cb()' - Select everything on the right side.
//

void
DiffWindow::select_right_cb(Fl_Menu_Bar *m,
					// I - Menubar
                            DiffWindow  *dw)
					// I - Window
{
  dw->view_.select(0, dw->view_.count() - 1, true);
}


//
// 'DiffWindow::using_cb()' - Show help window.
//

void
DiffWindow::using_cb(Fl_Menu_Bar *m,	// I - Menubar
                     DiffWindow  *dw)	// I - Window
{
  Fl_Double_Window	*win;		// Help window
  Fl_Help_View		*help;		// Help widget
  Fl_Button		*smaller,	// Smaller text
			*bigger,	// Bigger text
			*close;		// Close button
  Fl_Box		*box;		// Box
  Fl_Group		*group;		// Resize group
  Fl_Widget		*current;	// Currently pressed widget
  int			v;		// Text size
  int			W, H;		// Width and height


  win = new Fl_Double_Window(400, 300, "Help - " VERSION);

  dw->prefs_.get("help_textsize", v, FL_NORMAL_SIZE);

  help = new Fl_Help_View(10, 10, 380, 245);
  help->textsize(v);
  help->value(help_text_);

  group = new Fl_Group(10, 265, 380, 25);
  box   = new Fl_Box(10, 265, 25, 25);
  
  smaller = new Fl_Button(270, 265, 25, 25, "F");
  smaller->labelsize(8);
  smaller->tooltip("Make text smaller...");
  if (help->textsize() == 8)
    smaller->deactivate();

  bigger = new Fl_Button(305, 265, 25, 25, "F");
  bigger->labelsize(20);
  bigger->tooltip("Make text larger...");
  if (help->textsize() == 32)
    bigger->deactivate();

  close = new Fl_Button(340, 265, 50, 25, "Close");

  group->resizable(box);
  group->end();

  win->end();
  win->resizable(help);

  dw->prefs_.get("help_window_width", W, 400);
  dw->prefs_.get("help_window_height", H, 300);

  win->resize(win->x(), win->y(), W, H);
  win->show();

  while ((current = Fl::readqueue()) != close && win->shown() && dw->shown())
  {
    if (current == smaller)
    {
      help->textsize(help->textsize() - 1);

      if (help->textsize() == 8)
        smaller->deactivate();

      bigger->activate();
    }
    else if (current == bigger)
    {
      help->textsize(help->textsize() + 1);

      if (help->textsize() == 32)
        bigger->deactivate();

      smaller->activate();
    }

    Fl::wait();
  }

  dw->prefs_.set("help_textsize", help->textsize());
  dw->prefs_.set("help_window_width", win->w());
  dw->prefs_.set("help_window_height", win->h());

  win->hide();
  delete win;
}


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