/**********************************************************************
 *     EPSCAN, an EPSON ES-1200 scanner driver     
 *     Adam P. Jenkins <ajenkins@cs.umass.edu>
 *     
 *     This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License
 *     as published by the Free Software Foundation; either version 2
 *     of the License, or (at your option) any later version.
 *     
 *     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.
 *     
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream.h>

#include <qapp.h>
#include <qcombo.h>
#include <qscrbar.h>
#include <qlabel.h>
#include <qpushbt.h>
#include <qrect.h>
#include <qlayout.h>
#include <qfiledlg.h>
#include <qfileinf.h>
#include <qpopmenu.h>
#include <qmenubar.h>

#include "EPScan.h"
#include "Separator.h"
#include "ProgressReport.h"
#include "ValSlider.h"
#include "EPGetRegion.h"
#include "HelpWindow.h"
#include "QLabeledBoxLayout.h"

EPScan::EPScan(QWidget *parent, const char *name) : QWidget(parent, name)
{
  Separator *separator;
  
  initScanner();
  initMenus();

  // enable tiff routines so QPixmap can load tif files.
  void initTiffHandler();
  initTiffHandler();
  
  QBoxLayout *layout = new QBoxLayout(this, QBoxLayout::LeftToRight, 15);
  CHECK_PTR(layout);

  helpWin = new HelpWindow(EPSCAN_HELP_FILE);
  CHECK_PTR(helpWin);
  
  QPopupMenu *file = new QPopupMenu;
  CHECK_PTR( file );
  file->insertItem("&Exit", qApp, SLOT(quit()));

  QPopupMenu *help = new QPopupMenu;
  CHECK_PTR(help);
  help->insertItem("&Description", helpWin, SLOT(showHelp()));
  
  QMenuBar *menub = new QMenuBar(this);
  CHECK_PTR(menub);
  menub->insertItem("&File", file);
  menub->insertItem("&Help", help);

  layout->setMenuBar(menub);
  
  QLabeledBoxLayout *controls = new QLabeledBoxLayout(QBoxLayout::TopToBottom,
						      10);
  CHECK_PTR(controls);

  layout->addLayout(controls);

  // Image Type Menu
  imageTypeMenu = new QComboBox(false, this, "Image Type");
  CHECK_PTR(imageTypeMenu);
  makeMenu(imageTypeMenu, &imageTypeData);
  connect(imageTypeMenu, SIGNAL(activated(int)), SLOT(setImageType(int)) );
  controls->addLabeledWidget(imageTypeMenu);

  // Destination Menu
  destinationMenu = new QComboBox(false, this, "Destination");
  CHECK_PTR(destinationMenu);
  makeMenu(destinationMenu, &destinationData);
  connect(destinationMenu, SIGNAL(activated(int)), SLOT(setDestination(int)) );
  controls->addLabeledWidget(destinationMenu);

  // Resolution menu
  resolutionMenu = new QComboBox(false, this, "Resolution");
  CHECK_PTR(resolutionMenu);  
  makeMenu(resolutionMenu, &resolutionData);
  connect(resolutionMenu, SIGNAL(activated(int)), SLOT(setResolution(int)) );
  controls->addLabeledWidget(resolutionMenu);

  // brightness slider
  brightnessCtrl = new ValSlider(-3, 3, 1, 1, 0, this, "Brightness");
  CHECK_PTR(brightnessCtrl);
  connect(brightnessCtrl, SIGNAL(valueChanged(int)), SLOT(setBrightness(int)) );
  controls->addLabeledWidget(brightnessCtrl);

  separator = new Separator(this, Separator::Horizontal);
  CHECK_PTR(separator);
  controls->addWidget(separator);

  // width and height display
  QLabeledBoxLayout *dimensions = new QLabeledBoxLayout(QBoxLayout::LeftToRight);
  CHECK_PTR(dimensions);
  controls->addLayout(dimensions);


  // width
  widthLabel = new QLabel("00.00", this, "Width");
  CHECK_PTR(widthLabel);
  widthLabel->adjustSize();
  widthLabel->setMinimumSize(widthLabel->size());
  widthLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
  dimensions->addLabeledWidget(widthLabel);

  // height
  heightLabel = new QLabel("00.00", this, "Height");
  CHECK_PTR(heightLabel);
  heightLabel->adjustSize();
  heightLabel->setMinimumSize(heightLabel->size());
  heightLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
  dimensions->addLabeledWidget(heightLabel);

  // units menu
  unitsMenu = new QComboBox(this, "Units");
  CHECK_PTR(unitsMenu);
  makeMenu(unitsMenu, &unitsData);
  connect(unitsMenu, SIGNAL(activated(int)), SLOT(displayDim(int)) );
  dimensions->addLabeledWidget(unitsMenu);

  separator = new Separator(this, Separator::Horizontal);
  CHECK_PTR(separator);
  controls->addWidget(separator);

  // zoom control
  zoomCtrl = new ValSlider(50, 200, 1, 10, 100, this, "Zoom");
  CHECK_PTR(zoomCtrl);
  connect(zoomCtrl, SIGNAL(valueChanged(int)), SLOT(setZoom(int)) );
  controls->addLabeledWidget(zoomCtrl);

  separator = new Separator(this, Separator::Horizontal);
  CHECK_PTR(separator);
  controls->addWidget(separator);
  
  // scan and preview buttons
  QBoxLayout *btns = new QBoxLayout(QBoxLayout::LeftToRight);
  CHECK_PTR(btns);
  controls->addLayout(btns);
  QPushButton *b;

  b = new QPushButton("Scan", this);
  CHECK_PTR(b);
  b->adjustSize();
  b->setMinimumSize(b->size());
  connect(b, SIGNAL(clicked()), SLOT(doScan()) );
  btns->addWidget(b);

  b = new QPushButton("Preview", this);
  CHECK_PTR(b);
  b->adjustSize();
  b->setMinimumSize(b->size());
  connect(b, SIGNAL(clicked()), SLOT(doPreview()) );
  btns->addWidget(b);

  controls->addStretch(10);

  // Now add the preview widget
  previewWin = new EPGetRegion(this);
  CHECK_PTR(previewWin);
  previewWin->setMinimumSize(370, 520); // kind of random!
  previewWin->setFrameStyle(QFrame::Panel | QFrame::Sunken);
  connect(previewWin, SIGNAL(regionChanged(const QRect&)),
	  SLOT(setArea(const QRect&)) );
  layout->addWidget(previewWin, 10);

  setDefaultValues();
  
  layout->activate();
}

EPScan::~EPScan()
{
  delete scanner;
  delete helpWin;
}

void
EPScan::makeMenu(QComboBox *menu, MenuItemList *data)
{
  for (MenuItem *i = data->first(); i; i=data->next()) 
    menu->insertItem(i->item);
  menu->adjustSize();
  menu->setMinimumSize(menu->size());
}

void
EPScan::initScanner(const char *device)
{
  scanner = new Scanner;
  CHECK_PTR(scanner);
  int res;
  if (device)
    res = scanner->open(device);
  else
    res = scanner->open();
  if (res < 0) {
    cerr << "Failed to initialize scanner: " << scanner->strError() << endl;
    exit(1);
  }

  scanner->getSettings(Settings);
  scanner->getIdentity(Identity);
  Settings.scanmode = SCN_MODE_NORMAL;
  Settings.color_mode = SCN_CLR_COLOR_LINE;
  Settings.linecounter = 32 * 1024;
}

void
EPScan::initMenus()
{
  // image type menu
  MenuItemList& I = imageTypeData;
  I.append(new MenuItem("Color", (int)Type::COLOR) );
  I.append(new MenuItem("Grayscale", (int)Type::GRAY) );
  I.append(new MenuItem("Halftone", (int)Type::HALFTONE) );
  I.append(new MenuItem("Line/Text", (int)Type::LINEART) );

  MenuItemList& D = destinationData;
  D.append(new MenuItem("Color Laser Printer", (int)Dest::COLORLASER) );
  D.append(new MenuItem("Laser Printer", (int)Dest::LASER) );
  D.append(new MenuItem("Color Screen", (int)Dest::COLORSCREEN) );
  D.append(new MenuItem("Black & White Screen", (int)Dest::BWSCREEN) );

  MenuItemList& U = unitsData;
  U.append(new MenuItem("inch", Units::INCH) );
  U.append(new MenuItem("cm", Units::CM) );
  
  QString num;
  for (int i = 0; Identity.resolutions[i]; i++) {
    num.setNum(Identity.resolutions[i]);
    resolutionData.append(new MenuItem(num, Identity.resolutions[i]) );
  }
}
  

int
EPScan::setMenu(QComboBox *menu, MenuItemList *data, int item)
{
  int nth = 0;
  for (MenuItem *i = data->first(); i; i = data->next(), nth++) {
    if (item == (int)i->data) {
      menu->setCurrentItem(nth);
      return nth;
    }
  }
  return -1;
}
      
void
EPScan::setDefaultValues()
{
  QRect range(0, 0,
	      (int)(Identity.max_horiz_area * 100),
	      (int)(Identity.max_vert_area * 100));

  previewWin->setRange(range);
  previewWin->setRegion(range);
  setArea(range);
  
  setImageType( setMenu(imageTypeMenu, &imageTypeData, (int)Type::COLOR) );
  setDestination( setMenu(destinationMenu, &destinationData,
			  (int)Dest::COLORSCREEN) );
  setResolution( setMenu(resolutionMenu, &resolutionData, 75) );
  displayDim( setMenu(unitsMenu, &unitsData, Units::INCH) );

  setZoom(100);
  setBrightness(0);
}

  
/// SLOTS
void
EPScan::setImageType(int id)
{
  MenuItem *item = imageTypeData.at(id);
  if (!item) {
    warning("setImageType called with invalid id");
    return;
  }

  switch (item->data) {
  case Type::COLOR:
    Settings.color_mode = SCN_CLR_COLOR_LINE;
    Settings.bits_per_pixel = 8;
    break;
  case Type::GRAY:
    Settings.color_mode = SCN_CLR_MONO;
    Settings.bits_per_pixel = 8;
    break;
  case Type::HALFTONE:
    Settings.color_mode = SCN_CLR_MONO;
    Settings.bits_per_pixel = 1;
    Settings.halftone = SCN_HTONE_HARD;
    break;
  case Type::LINEART:
    Settings.color_mode = SCN_CLR_MONO;
    Settings.bits_per_pixel = 1;
    Settings.halftone = SCN_HTONE_DISABLE;
    break;
  default:
    warning("Unrecognized image type");
  }
}

void
EPScan::setDestination(int id)
{
  MenuItem *item = destinationData.at(id);
  if (!item) {
    warning("setDestination called with invalid id");
    return;
  }

  switch (item->data) {
  case Dest::COLORLASER:
  case Dest::LASER:
    Settings.gamma = SCN_GAMMA_PRINTA;
    Settings.color_correction = SCN_CLR_CORRECT_LASER;
    break;
  case Dest::DOTMAT:
    Settings.gamma = SCN_GAMMA_PRINTB;
    Settings.color_correction = SCN_CLR_CORRECT_DOTMAT;
    break;
  case Dest::COLORSCREEN:
    Settings.gamma = SCN_GAMMA_CRTB;
    Settings.color_correction = SCN_CLR_CORRECT_CRT;
    break;
  case Dest::BWSCREEN:
    Settings.gamma = SCN_GAMMA_CRTA;
    Settings.color_correction = SCN_CLR_CORRECT_CRT;
    break;
  default:
    warning("Unrecognized data in setDestination");
  }
}

void
EPScan::setResolution(int id)
{
  MenuItem *item = resolutionData.at(id);
  if (!item) {
    warning("setResolution called with invalid id");
    return;
  }

  Settings.main_scan_res = Settings.sub_scan_res = item->data;
}

void
EPScan::setBrightness(int val)
{
  Settings.brightness = val;
}

void
EPScan::setZoom(int val)
{
  Settings.main_scan_zoom = Settings.sub_scan_zoom = val;
}

void
EPScan::setArea(const QRect& r)
{
  Settings.main_scan_offset = (float)r.x() / 100.0;
  Settings.sub_scan_offset = (float)r.y() / 100.0;
  
  Settings.main_scan_area = (float)r.width() / 100.0;
  Settings.sub_scan_area = (float)r.height() / 100.0;

  displayDim(unitsMenu->currentItem());
}

void
EPScan::displayDim(int id)
{
  MenuItem *item = unitsData.at(id);
  if (!item) {
    warning("displayDim called with invalid id");
    return;
  }

  QString w, h;
  if (item->data == Units::INCH) {
    w.setNum(Settings.main_scan_area, 'f', 2);
    h.setNum(Settings.sub_scan_area, 'f', 2);
  } else {
    w.setNum(Settings.main_scan_area * 2.54, 'f', 2);
    h.setNum(Settings.sub_scan_area * 2.54, 'f', 2);
  }

  widthLabel->setText(w);
  heightLabel->setText(h);
}


void
EPScan::doScan()
{
  QFileInfo fi(lastSavedFile);
  QString fname = QFileDialog::getSaveFileName(fi.dirPath(TRUE),
					       "*.tif", this);
  if (fname.isEmpty())
    return;

  lastSavedFile = fname;
  
  if (scanner->applySettings(Settings)) {
    warning("Error in settings: %s", scanner->strError());
    return;
  }

  if (scanner->initScan(fname)) {
    warning("Error initializing scan: %s", scanner->strError());
    unlink(fname);
    return;
  }

  ProgressReport *prg = new ProgressReport(scanner, this);
  bool res = prg->exec();
  delete prg;

  if (!res) {
    warning("Picture was not successfuly scanned.");
    unlink(fname);    
    return;
  }
}
  
void
EPScan::doPreview()
{
  ScanNewSettings set = Settings;

  set.main_scan_res = set.sub_scan_res = 75;
  set.main_scan_zoom = set.sub_scan_zoom = 50;
  set.main_scan_offset = set.sub_scan_offset = 0.0;
  set.main_scan_area = Identity.max_horiz_area;
  set.sub_scan_area = Identity.max_vert_area;
  set.scanmode = SCN_MODE_FAST;
  
  char *p = tempnam(0, 0);
  CHECK_PTR(p);
  QString fname = (const char *)p;
  free(p);
  
  if (scanner->applySettings(set)) {
    warning("Error in settings: %s", scanner->strError());
    return;
  }

  if (scanner->initScan(fname)) {
    warning("Error initializing scan: %s", scanner->strError());
    unlink(fname);
    return;
  }

  ProgressReport *prg = new ProgressReport(scanner, this);
  bool res = prg->exec();
  delete prg;

  if (!res) {
    warning("Picture was not successfuly scanned.");
    unlink(fname);
    return;
  }

  QPixmap pix;
  if (pix.load(fname))
    previewWin->setPixmap(pix);
  else 
    warning("failed to display pixmap");
  unlink(fname);
}
  
