/*
 *   XPP - The X Printing Panel
 *   --------------------------
 *
 *   Implementation of the "printFiles" class which is the central class of
 *   XPP for handling the printers, their options and the files to print
 *
 *   See the header file (xpp.h) for an overview of the fields and methods.
 *
 *   Copyright 2000 by Till Kamppeter
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA
 *   02111-1307  USA
 *
 */

/*
 * Include necessary headers...
 */

#include "xpp.h"

#ifndef WIN32
#  include <signal.h>

/*
 * Local functions.
 */

void	sighandler(int);

#endif /* !WIN32 */


/*
 * Globals...
 */

char	tempfile[1024];		/* Temporary file for printing from stdin */

/*
 *  Parse options and send files for printing.
 */

printFiles::printFiles(int  argc, /* I - Number of command-line arguments */
                       char *argv[],  /* I - Command-line arguments */
                       int &exitstat) /* O - != 0 if an error occurred */
{
  int		i,j;		/* Looping vars */
  const char    *tmpstr1;       /* Temporary string constant */
  int		num_options;	/* Number of options */
  cups_option_t	*options;	/* Options given on the command line */

#ifdef HAVE_SIGACTION
  struct sigaction action;	/* Signal action */
#endif /* HAVE_SIGACTION */

  // prepare basic variables

  STDIN_STR   = "<stdin>";
  exitstat    = 0;
  deletefile  = 0;
  //dest        = cupsGetDefault(); /* not reliable, dests[i].is_default
  //                                   used instead */
  dest        = NULL;
  dest_pos    = 0;
  num_options = 0;
  options     = NULL;
  file_printed= 0;
  filelist    = (char *)malloc(BLOCK_SIZE);
  title       = NULL;
  extra_options = NULL;
  num_copies  = 1;
  priority    = 50;

  // Set CUPS server, port, and user

  CupsHelper::setup();

  // check CUPSserver infos

  if (!CupsHelper::checkHost())
  {
    fl_alert("Unable to connect to CUPS server, check options.");
    exitstat = 1;
    return;
  }


  // Check whether we have printers, if not, abort program
  num_dests   = cupsGetDests(&dests); // Printer names and options
  if (num_dests <= 0)
  {
    fl_alert("No printer found, aborting.");
    exitstat = 1;
    return;
  }

  for (i = 1; i < argc; i ++)
    if (argv[i][0] == '-')
      switch (argv[i][1])
      {
	case 'i' : /* indent */
	case 'w' : /* width */
        case 'c' : /* CIFPLOT */
	  //case 'd' : /* DVI */ /* '-d' is used as with the 'lp' command */
	case 'f' : /* FORTRAN */
	case 'g' : /* plot */
	  //case 'n' : /* Ditroff */ /* '-n' is used as with the 'lp' command*/
	  //case 't' : /* Troff */ /* '-t' is used as with the 'lp' command */
	case 'v' : /* Raster image */
	    fl_message("Warning: \'%c\' format modifier not supported - output may not be correct!",argv[i][1]);
            if (((argv[i][1] == 'i') || (argv[i][1] == 'w')) && 
                (argv[i][2] == '\0')) i++;
	    break;

	case 'o' : /* Option */
	    if (argv[i][2] != '\0')
	      num_options = cupsParseOptions(argv[i] + 2, num_options, &options);
	    else
	    {
	      i ++;
	      num_options = cupsParseOptions(argv[i], num_options, &options);
	    }
	    break;

	case 'l' : /* Literal/raw */
            num_options = cupsParseOptions("raw", num_options, &options);
	    break;

	case 'p' : /* Prettyprint */
            num_options = cupsParseOptions("prettyprint", num_options, &options);
	    break;

	case 'h' : /* Suppress burst page */
	case 's' : /* Don't use symlinks */
	    break;

	case 'm' : /* Mail on completion */
	    fl_message("Warning: email notification is not supported!");
	    break;

	case 'r' : /* Remove file after printing */
	    deletefile = 1;
	    break;

        case 'd' :
        case 'P' : /* Destination printer or class */
	    if (argv[i][2] != '\0')
	      dest = argv[i] + 2;
	    else
	    {
	      i ++;
	      dest = argv[i];
	    }
	    {	  
	      char *tmp;

	      if ((tmp = strchr((char*)dest,'/'))) {
		tmp[0] = '\0';
		instance = tmp + 1;
	      }	
	    }	
	    break;
        case 'n' :
	case '#' : /* Number of copies */
	    if (argv[i][2] != '\0')
	      num_copies = atoi(argv[i] + 2);
	    else
	    {
	      i ++;
	      num_copies = atoi(argv[i]);
	    }

	    if (num_copies < 1 || num_copies > 100)
	    {
	      fl_alert("Number of copies must be between 1 and 100.");
	      exitstat = 1;
              return;
	    }

	    break;

        case 'q' : /* Queue priority */ /* '-p' occupied by 'prettyprint' */
            if (argv[i][2] != '\0')
              priority = atoi(argv[i] + 2);
            else
            {
              i ++;
              priority = atoi(argv[i]);
            }
 
            if (priority < 1 || priority > 100)
            {
              fl_alert("Priority must be between 1 and 100.");
              exitstat = 1;
              return;
            }
 
            break;

	case 'C' : /* Class */
	case 'J' : /* Job name */
        case 't' :
	case 'T' : /* Title */
	    if (argv[i][2] != '\0')
	      title = argv[i] + 2;
	    else
	    {
	      i ++;
	      title = argv[i];
	    }
	    break;

	default :
	    fl_alert("Unknown option \'%c\'!", argv[i][1]);
            exitstat = 1;
	    return;
      }
    else
    {
     /*
      * Add a file to the list of files to print and put the name into quotes
      * if it contains spaces
      */

      file_printed = 1;
      int has_space = 0;
      if (strchr(argv[i], ' ') != NULL) has_space = 2;
      unsigned int filelist_size=(strlen(filelist)/BLOCK_SIZE+1)*BLOCK_SIZE;
      if ( filelist_size < strlen(filelist)+strlen(argv[i])+has_space+2 )
        filelist = (char *)realloc(filelist,filelist_size+BLOCK_SIZE);
      if (has_space) strcat(filelist,"\"");
      strcat(filelist,(const char *)argv[i]);
      if (has_space) strcat(filelist,"\"");
      strcat(filelist," ");
    }

  // Remove trailing space in the end of the file list

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

  // Prepare variables for printer menu

  printerPack = new Fl_Pack(0,0,2 * MENU_WIDTH,MENU_HEIGHT,NULL);
  printerPack->type(FL_VERTICAL);
  ppds        = (ppd_file_t **)calloc(sizeof(ppd_file_t *),num_dests);
  special_opts = (special_opt_t **)calloc(sizeof(special_opt_t *),
                                              num_dests);
  num_special_opts = (int *)calloc(sizeof(int), num_dests);
  menu_strs   = (char **)calloc(sizeof(char *),num_dests);
  menu_buttons = (Fl_Round_Button **)calloc(sizeof(Fl_Round_Button *),
                                            num_dests);
  info_lines  = (Fl_Box **)calloc(sizeof(Fl_Box *), num_dests);

  // Load data about all available printers
  // and prepare the menu entries for the printers

  for (i = 0; i < num_dests; i ++) {
    // add the command line options to the printer's option list
    for (j = 0; j < num_options; j ++)
      dests[i].num_options = cupsAddOption(options[j].name, options[j].value, 
                             dests[i].num_options, &(dests[i].options));

    // Initialize pointer for PPD file and numerical/string options

    ppds[i] = NULL;
    special_opts[i] = NULL;
    num_special_opts[i] = 0;

    // Set up text for menu entry

    menu_strs[i]=(char *)calloc(sizeof(char),128);

    // Get printer information to show in the menu entry

    ipp_t *request = CupsHelper::newIppRequest();
    char buf[1024], *state, *type, *location, *comment;
    sprintf(buf,"ipp://%s:%d/printers/%s",CupsHelper::host(),
            CupsHelper::port(),dests[i].name);
    ippAddString(request,IPP_TAG_OPERATION,IPP_TAG_URI,"printer-uri",NULL,buf);
    request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES;
    request = CupsHelper::processRequest(request,"/printers/");
    if (!request || request->curtag != IPP_TAG_PRINTER) {
      state = "Unknown";
      type = "No Information Available";
      location = "Location Unknown";
      comment = "Unable to retrieve printer infos";
    } else {
      ipp_attribute_t *attr = ippFindAttribute(request,"printer-state",
                                               IPP_TAG_ENUM);
      if (attr)
        switch (attr->values[0].integer) {
          case IPP_PRINTER_IDLE:
            state = "Printer idle";
            break;
          case IPP_PRINTER_PROCESSING:
            state = "Processing...";
            break;
          case IPP_PRINTER_STOPPED:
            state = "Stopped";
            break;
        }
      else state = "Unknown";
      attr = ippFindAttribute(request,"printer-info",IPP_TAG_TEXT);
      if (attr)
        type = attr->values[0].string.text;
      else
        type = "No Information Available";
      if (strlen(type) == 0) type = "No Information Available";
      attr = ippFindAttribute(request,"printer-location",IPP_TAG_TEXT);
      if (attr)
        location = attr->values[0].string.text;
      else
        location = "Location Unknown";
      if (strlen(location) == 0) location = "Location Unknown";
      attr = ippFindAttribute(request,"printer-make-and-model",IPP_TAG_TEXT);
      if (attr)
        comment = attr->values[0].string.text;
      else
        comment = "";
    }

    int prtype = CupsHelper::printerType(dests[i].name);
    if ( prtype < 0 ) {
      if ( dests[i].instance != NULL )
        sprintf(menu_strs[i], "%s on %s: Destination not available",
                dests[i].instance, dests[i].name);
      else
        sprintf(menu_strs[i], "%s: Destination not available",
                dests[i].name);
    } else if ((prtype & CUPS_PRINTER_CLASS) || 
               (prtype & CUPS_PRINTER_IMPLICIT)) {
      if ( dests[i].instance != NULL )
        sprintf(menu_strs[i], "%s on %s: %s (printer class)",
                dests[i].instance, dests[i].name, comment);
      else
        sprintf(menu_strs[i], "%s: %s (printer class)",
               dests[i].name, comment);
    } else {
      if ( dests[i].instance != NULL )
        sprintf(menu_strs[i], "%s on %s: %s (single printer)",
                dests[i].instance, dests[i].name, comment);
      else
        sprintf(menu_strs[i], "%s: %s (single printer)",
                dests[i].name, comment);
    }

    // Make the main line of the menu entry
    menu_buttons[i]=new Fl_Round_Button(0,(ITEM_HEIGHT+INFO_HEIGHT)*i,
                                        0, ITEM_HEIGHT,menu_strs[i]);
    menu_buttons[i]->type(FL_RADIO_BUTTON);
    menu_buttons[i]->box(FL_FLAT_BOX);
    menu_buttons[i]->color(i & 1 ? FL_LIGHT2 : FL_GRAY);
    menu_buttons[i]->selection_color(FL_BLACK);
    menu_buttons[i]->labelfont(FL_HELVETICA_BOLD);
    // Mark first entry in the printer list, just for the case that CUPS
    // did not report a default printer. The mark will immediately be
    // changed as soon as a default printer is found.
    if (i == 0) menu_buttons[i]->setonly();
    // Mark the entry of the default printer defined under CUPS
    if (dests[i].is_default) {
      menu_buttons[i]->setonly();
      dest_pos = i;
      default_pr=dests[i].name;
    }
    // Small info line under printer entry
    sprintf(buf,"          State: %s     Location: %s     Type: %s",
            state, location, type);
    info_lines[i] = new Fl_Box(FL_FLAT_BOX,
                               0,(ITEM_HEIGHT+INFO_HEIGHT)*i+ITEM_HEIGHT,
                               0, INFO_HEIGHT, strdup(buf));
    info_lines[i]->labelsize(10);
    info_lines[i]->align(FL_ALIGN_LEFT|FL_ALIGN_TOP|FL_ALIGN_INSIDE);
    info_lines[i]->color(i & 1 ? FL_LIGHT2 : FL_GRAY);
  }
  printerPack->end();
  cupsFreeOptions(num_options, options);

  // If a valid printer is given on the command line, switch to it.
  // Printer names can be abbreviated, if they are ambiguous the first
  // suitable entry in the list is taken (therefore the backward loop).
  // This way local printers are always preferred.

  dest_set_ext = 0;
  if (dest != NULL) {
    if (instance) {
      for (i = num_dests-1; i >= 0 ; i --) {
        if (dests[i].instance
	    && strncmp(dests[i].name,dest,strlen(dest)) == 0
	    && strncmp(dests[i].instance,instance,strlen(instance)) == 0) {
          menu_buttons[i]->setonly();
          dest_pos = i;
          dest_set_ext = 1;
        };
      };
    };
    if (dest_set_ext == 0) {	
    for (i = num_dests-1; i >= 0 ; i --) {
      if (strncmp(dests[i].name,dest,strlen(dest)) == 0) {
        menu_buttons[i]->setonly();
        dest_pos = i;
        dest_set_ext = 1;
      };
    };
    };
  };
  if (dest_set_ext == 0) dest=default_pr;
}

/*
 *  Update variables dest and destpos to the destination chosen in the menu
 */

void
printFiles::setDest()
{
  int		i;		/* Looping var */

  for (i = 0; i < num_dests; i ++) {
    if (menu_buttons[i]->value() == 1) {
      dest=dests[i].name;
      instance=dests[i].instance;
      dest_pos=i;
    }
  }
}

/*
 *  Insert the printer list in a scroll box
 */

void
printFiles::insertPrinterList(Fl_Scroll *o) /* The scroll box where to 
                                               insert the list */
{
  //o->remove(printerPack);
  o->add(printerPack);
  o->resizable(printerPack);
  printerPack->resize(o->x(),o->y(),
                      2 * o->w(),o->h());
  o->scrollbar.linesize(ITEM_HEIGHT+INFO_HEIGHT);
  //if ((dest_pos+1)*(ITEM_HEIGHT+INFO_HEIGHT) <= MENU_HEIGHT) 
  //  o->position(0,0);
  //else 
  //  o->position(0,(dest_pos+1)*(ITEM_HEIGHT+INFO_HEIGHT)-MENU_HEIGHT);
}

/*
 *  Print the files when 'Print' button is clicked
 */

void
printFiles::print(int &exitstat)
{
  int		i;		/* Looping var */
  char          *itemptr;       /* Which file name/extra option to process 
				   now? */
  char          *endptr;        /* marks end of filename/extra option */
  char          *listend;       /* end of file/extra option list */
  char          endchar;        /* character delimiting the file name or 
				   extra option currently examined */
  int		num_options;	/* Number of options */
  cups_option_t	*options;	/* Options */

  exitstat = 0;

  setDest();

  if (dest == NULL)
  {
    fl_alert("Error - no destination available.");
    exitstat = 1;
    return;
  }

  // Load the PPD file to check whether there are conflicting options
  // and set numerical/string options to guarantee that the defaults
  // defined in the PPD file are used (even when the backend driver
  // has other defaults).

  if (!ppds[dest_pos]) { // Try to load the PPD file
    const char* str = cupsGetPPD(dests[dest_pos].name);
    if (str) {
	ppds[dest_pos] = ppdOpenFile(str);
	unlink(str);
    }
    // Load numerical/string options
    if (ppds[dest_pos] != NULL)
      num_special_opts[dest_pos] = 
        getSpecialOptions(&((special_opts)[dest_pos]));
  }

  // Copy the options given on the XPP command line and by the GUI

  num_options=0;
  options=NULL;
  sprintf(buffer, "%d", num_copies);
  num_options = cupsAddOption("copies", buffer, num_options, &options);
  sprintf(buffer, "%d", priority);
  num_options = cupsAddOption("job-priority", buffer, num_options, &options);
  for (i = 0; i < dests[dest_pos].num_options; i ++)
    num_options = cupsAddOption(dests[dest_pos].options[i].name,
                                dests[dest_pos].options[i].value,
                                num_options, &options);

  // Add the options which were typed into the "Extra Options" field

  if (extra_options != NULL) {
    itemptr = (char *)extra_options;
    listend = (char *)extra_options + strlen(extra_options);

    while (itemptr != listend) {
      while ((*itemptr == ' ') or (*itemptr == ',') or (*itemptr == ';') or 
	     (*itemptr == '\t') or (*itemptr == '\n')) 
	itemptr ++;
      if (strlen(itemptr) != 0) {
	endptr = strpbrk(itemptr, " ,;\t\n");
	if (endptr == NULL) endptr = listend;
	endchar = *endptr;
	*endptr = '\0';
	num_options = cupsParseOptions(itemptr, num_options, &options);
	
	itemptr = endptr;
	if (endptr != listend) {
	  *endptr = endchar;
	  itemptr++;
	}
      }
    }
  }

  // Check for conflicting options and if there are conflicting options
  // advice the user and return

  if (ppds[dest_pos]) {
    // Mark current options in PPD 
    ppdMarkDefaults(ppds[dest_pos]);
    cupsMarkOptions(ppds[dest_pos],dests[dest_pos].num_options,
                    dests[dest_pos].options);
    // Check conflicts
    if (ppdConflicts(ppds[dest_pos])) {
      fl_alert("Cannot print with these settings, there is a conflict between some options. Click on \"Options\" and look for the options marked in red. Please correct them!");
      exitstat = 1;
      return;
    } 
  }

  // Go through the list of files to be printed and send them into the 
  // print queue

  itemptr = filelist;
  listend = filelist + strlen(filelist);
  file_printed = 0;

  while (itemptr != listend)
  {
    while (*itemptr == ' ') itemptr ++;
    if (*itemptr == '\"')
    {
      endchar='\"';
      itemptr ++;
    } else endchar=' ';
    if (strlen(itemptr) != 0)
    {
      endptr = strchr(itemptr, endchar);
      if (endptr == NULL) endptr = listend;
      *endptr = '\0';
      if (strcmp(itemptr,STDIN_STR)  == 0) printStdin(exitstat, options,
						      num_options);
      else
      { 
        if (title)
          job_id = cupsPrintFile(dest, itemptr, title, num_options, options);
        else
        {
          char *filename;

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

          job_id = cupsPrintFile(dest, itemptr, filename, num_options, options);
        }

        if (job_id < 1)
        {
	  fl_alert("Unable to print file \'%s\' - %s.",
	             itemptr, ippErrorString(cupsLastError()));
          exitstat = 1;
        }
        else if (deletefile)
          unlink(itemptr);
      }

      itemptr = endptr;
      if (endptr != listend) 
      {
        *endptr = endchar;
        itemptr++;
      }
      file_printed=1;
    }
  }

  if (file_printed == 0) printStdin(exitstat,options,num_options);

  cupsFreeOptions(num_options, options);
}

/*
 *  Print from Standard Input
 */

void
printFiles::printStdin(int &exitstat, cups_option_t *options, int num_options)
{
  int		i;		/* Looping var */

  exitstat = 0;

#ifndef WIN32
#if defined(HAVE_SIGSET)
  sigset(SIGHUP, sighandler);
  sigset(SIGINT, sighandler);
  sigset(SIGTERM, sighandler);
#elif defined(HAVE_SIGACTION)
  memset(&action, 0, sizeof(action));
  action.sa_handler = sighandler;

  sigaction(SIGHUP, &action, NULL);
  sigaction(SIGINT, &action, NULL);
  sigaction(SIGTERM, &action, NULL);
#else
  signal(SIGHUP, sighandler);
  signal(SIGINT, sighandler);
  signal(SIGTERM, sighandler);
#endif
#endif // !WIN32

  temp = fopen(cupsTempFile(tempfile, sizeof(tempfile)), "w");

  if (temp == NULL)
  {
    fl_alert("Unable to create temporary file.");
    exitstat = 1;
    return;
  }

  while ((i = fread(buffer, 1, sizeof(buffer), stdin)) > 0)
    fwrite(buffer, 1, i, temp);

  i = ftell(temp);
  fclose(temp);

  if (i <= 0)
  {
    fl_alert("Standard input is empty, so no job has been sent.");
    exitstat = 1;
    return;
  }

  if (title)
    job_id = cupsPrintFile(dest, tempfile, title, num_options, options);
  else
    job_id = cupsPrintFile(dest, tempfile, "STDIN", num_options, options);

  unlink(tempfile);

  if (job_id < 1)
  {
    fl_alert("Unable to print standard input - %s.",
               ippErrorString(cupsLastError()));
    exitstat = 1;
    return;
  }
}

/*
 * initialize banner page buttons
 */

void
printFiles::initBannerMenues(Fl_Choice *before, Fl_Choice *after)
                                /* banner menues */
{
  int		i;              /* Looping var */

  // Which printer was chosen?

  setDest();

  // Prepare the menues

  before->clear();
  before->deactivate();
  after->clear();
  after->deactivate();

  // Do an IPP request to get the banner information of the current printer
  // return when the request fails.

  ipp_t *request = CupsHelper::newIppRequest();
  char buffer[1024], *t;
  const char *s, *u;
  sprintf(buffer,"ipp://%s:%d/printers/%s",CupsHelper::host(),
          CupsHelper::port(),dests[dest_pos].name);
  ippAddString(request,IPP_TAG_OPERATION,IPP_TAG_URI,
               "printer-uri",NULL,buffer);
  request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES;
  request = CupsHelper::processRequest(request,"/printers/");
  if (!request || request->curtag != IPP_TAG_PRINTER) return;

  // Check whether the printer supports banner pages and continue only
  // if it does so
  ipp_attribute_t *attr = ippFindAttribute(request, "job-sheets-supported",
                                           IPP_TAG_NAME);
  if (attr) {
    // Enable the menus and insert all available banners
    before->activate();
    after->activate();
    for (i = 0; i < attr->num_values; i ++) {
      // Use the replacement method to avoid weird banner file names
      // messing up the menues
      before->add("x",0,0,0,0);
      before->replace(i,attr->values[i].string.text);
      after->add("x",0,0,0,0);
      after->replace(i,attr->values[i].string.text);
    }
    // Determine which banners are currently set
    u = before->menu()->label();
    if (s = cupsGetOption("job-sheets",num_changed_options,
                                       changed_options)) {
      strcpy(buffer,s);
      s = buffer;
      t = strchr(buffer,',');
      if (t) {
        *t = '\0';
        t ++;
      } else {
        t = (char *)u;
      }
    } else if ((attr = ippFindAttribute(request, "job-sheets-default",
                                        IPP_TAG_NAME)) != NULL) {
      if (attr->num_values > 0) s = attr->values[0].string.text;
      else s = u;
      if (attr->num_values > 1) t = attr->values[1].string.text;
      else t = (char *)u;
    } else {
      s = u;
      t = (char *)u;
    }
    // Determine the menu indices of the settings and mark the
    // appropriate menu entry
    for (i = 0; i < before->size() - 1; i ++) {
      if (strcmp(before->menu()[i].label(),s) == 0) before->value(i);
      if (strcmp(after->menu()[i].label(),t) == 0) after->value(i);
    }
  }
}

/*
 * Enter changes on the banner settings into the attribute list
 */

void
printFiles::setBannerChanges(Fl_Choice *before, Fl_Choice *after)
                                /* banner menues */
{
  int		i;              /* Looping var */

  // Which printer was chosen?

  setDest();

  // Do an IPP request to get the default banner settings

  ipp_t *request = CupsHelper::newIppRequest();
  char buffer[1024]; 
  const char *s, *t, *u;
  sprintf(buffer,"ipp://%s:%d/printers/%s",CupsHelper::host(),
          CupsHelper::port(),dests[dest_pos].name);
  ippAddString(request,IPP_TAG_OPERATION,IPP_TAG_URI,
               "printer-uri",NULL,buffer);
  request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES;
  request = CupsHelper::processRequest(request,"/printers/");

  // Get the default settings

  ipp_attribute_t *attr;
  if ((request) && (request->curtag == IPP_TAG_PRINTER))
    attr = ippFindAttribute(request,"job-sheets-default", IPP_TAG_NAME);
  else attr = NULL;
  u = before->menu()->label();
  if (attr) {
    if (attr->num_values > 0) s = attr->values[0].string.text;
    else s = u;
    if (attr->num_values > 1) t = attr->values[1].string.text;
    else t = u;
  } else {
    s = u;
    t = u;
  }

  // If the user settings differ from the defaults, set a "job-sheets"
  // option

  if ((strcmp(before->menu()[before->value()].label(),s) != 0) ||
      (strcmp(after->menu()[after->value()].label(),t) != 0)) {
    sprintf(buffer,"%s,%s",
            before->menu()[before->value()].label(),
            after->menu()[after->value()].label());
    num_changed_options = 
      cupsAddOption("job-sheets",buffer,
                    num_changed_options,&(changed_options));    
  } else {
    num_changed_options = 
      removeOptions("job-sheets",num_changed_options,&(changed_options));
  }
}

/*
 * Read Foomatic numerical and string/password options from the PPD file
 */

int /* O - index of the record of the given option in the array */
printFiles::getSpecialOptionIndex(int *num_special_opts_in_arr,
				  /* number of options in the array */
				  special_opt_t **special_opts_arr,
				  /* array of numerical/string options */
				  const char *option) {
                                  /* I - option to search for */
  int           i;                /* Looping var */
  for (i = 0; i < *num_special_opts_in_arr; i ++)
    if (strcmp((*special_opts_arr)[i].keyword, option) == 0)
      return i;
  (*num_special_opts_in_arr) ++;
  *special_opts_arr = (special_opt_t *)realloc
    (*special_opts_arr, sizeof(special_opt_t) *
     (*num_special_opts_in_arr));
  strncpy((*special_opts_arr)[i].keyword, option, 40);
  (*special_opts_arr)[i].text[0] = '\0';
  (*special_opts_arr)[i].type = 0;
  (*special_opts_arr)[i].min = 0;
  (*special_opts_arr)[i].max = 0;
  (*special_opts_arr)[i].defvalue = 0;
  (*special_opts_arr)[i].defstr[0] = '\0';
  (*special_opts_arr)[i].havedefault = 0;
  return i;
}

int /* O - number of numerical/string options for the current printer */
printFiles::getSpecialOptions(special_opt_t **special_opts_arr)
                              /* array of numerical/string options */
{
  int           options_found;  /* how many options found */
  int           current_option; /* index of current option */
  int		i;              /* Looping vars */
  const char	*filename;	/* File to load */

  FILE *ppdfile;
  char line[1024], /* buffer for reading PPD file line by
                      line to search for numerical/string options */
       item[1024], /* item to be defined (left of "=>") */
       value[1024], /* value for item (right of "=>") */
       argname[1024], /* name of the current argument */
       comment[1024], /* human-readable argument name */
       numbuffer[128], /* buffer to store the value as a CUPS option */
       defstr[1024], /* buffer for default in case of a string/password
                        option */
       typestr[20]; /* Option types in "*FoomaticRIPOption" lines in the
		       PPD */
  const char *line_contents; /* contents of line */
  const char *scan; /* pointer scanning the line */
  char *writepointer;
  const char *optionvalue; /* Value of the option if it is set by the user */
  double min, max, defvalue; /* Range and default of numerical 
                                Foomatic option */
  int opttype; /* 0 = other, 1 = int, 2 = float, 3 = string, 4 = password */
  int havedefault; /* Did we find a default value? */
  int openbrackets; /* How many curled brackets are open? */
  int inquotes; /* are we in quotes now? */
  int inargspart; /* are we in the arguments part now? */

  // Which printer was chosen?

  setDest();

  // Name of temporary copy of the PPD file (download the PPD file from the
  // server if necessary)

  if ((filename = cupsGetPPD(dests[dest_pos].name)) == NULL) return(0);

  // Open PPD file to search for numerical/string options of Foomatic
  if ((ppdfile = fopen(filename,"r")) == NULL) return(0);

  // delete the temp file
  unlink(filename);

  // Reset all variables
  opttype = 0;
  min = 0.0; max = 0.0; defvalue = 0.0;
  openbrackets = 0;
  inquotes = 0;
  writepointer = item;
  inargspart = 0;
  options_found = 0;
  item[0] = '\0';
  value[0] = '\0';
  argname[0] = '\0';
  comment[0] = '\0';
  havedefault = 0;
  // Read the PPD file, line by line.
  while (fgets(line,sizeof(line),ppdfile)) {
    if (line_contents = strstr(line,"*% COMDATA #")) {
      // evaluate lines with old (2.x) Foomatic info
      line_contents += 12; // Go to the text after 
                           // "*% COMDATA #"
      for (scan = line_contents; 
           (*scan != '\n') && (*scan != '\0');
           scan ++) {
        switch(*scan) {
          case '[': // open square bracket
          case '{': // open curled bracket
            if (!inquotes) {
              openbrackets ++;
              // we are on the left hand side now
              *writepointer = '\0';
              writepointer = item;
              // in which type of block are we now?
              if ((openbrackets == 2) && 
                  (strncasecmp(item,"args",4) == 0)) {
                // we are entering the arguments section now
                inargspart = 1;
      	      }
              if ((openbrackets == 3) && 
                  (inargspart == 1)) {
                // new argument, get its name
                strcpy(argname,item);
	      }
              // item already evaluated now
              item[0] = '\0';
            } else {*writepointer = *scan; writepointer ++;}
            break;
	  case ',': // end of logical line
          case ']': // close square bracket
	  case '}': // close curled bracket
            if (!inquotes) {
              // right hand side completed, go to left hand side
              *writepointer = '\0';
              writepointer = item;
              // evaluate logical line
              if (item[0]) {
                // Machine-readable argument name
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"name") == 0)) {
                  strcpy(argname,value);
                }
                // Human-readable argument name
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"comment") == 0)) {
                  strcpy(comment,value);
	        }
                // argument type
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"type") == 0)) {
                  if (strcasecmp(value,"int") == 0) opttype = 1;
                  if (strcasecmp(value,"float") == 0) opttype = 2;
                  if (strcasecmp(value,"string") == 0) opttype = 3;
		}
                // minimum value
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"min") == 0)) {
                  min = atof(value);
		}
                // maximum value
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"max") == 0)) {
                  max = atof(value);
		}
                // default value
                if ((openbrackets == 3) &&
                    (inargspart == 1) &&
                    (strcasecmp(item,"default") == 0)) {
		  havedefault = 1;
		  if (opttype < 3)
		    defvalue = atof(value);
		  else
		    strncpy(defstr, value, 1023);
		}
                // item already evaluated now
                item[0] = '\0';
              }
              // close bracket
              if ((*scan == '}') || (*scan == ']')) {
                // which block did we complete now?
                if ((openbrackets == 2) && 
                    (inargspart == 1)) {
                  // We are leaving the arguments part now
                  inargspart = 0;
                }
                if ((openbrackets == 3) && 
                    (inargspart == 1)) {
                  // The current option is completely parsed
                  // Is the option a valid numerical/string option?
                  if ((opttype > 0) &&
                      ((min != max) || (opttype > 2)) &&
                      (argname[0])) {
		    if (opttype < 3) {
		      // Correct the default value, if necessary
		      if (min < max) {
			if (defvalue < min) defvalue = min;
			if (defvalue > max) defvalue = max;
		      } else {
			if (defvalue < max) defvalue = max;
			if (defvalue > min) defvalue = min;
		      }
		    }
                    // Store the found argument
                    current_option =
		      getSpecialOptionIndex(&options_found,
					    special_opts_arr,
					    argname);
                    strncpy((*special_opts_arr)[current_option].text,
                            comment,80);
                    (*special_opts_arr)[current_option].type=opttype-1;
		    if (opttype < 3) {
		      (*special_opts_arr)[current_option].min=min;
		      (*special_opts_arr)[current_option].max=max;
		    }
                    // has the user supplied a value for this option?
                    for (i = 0; i < dests[dest_pos].num_options; i ++)
                      if (strcasecmp(dests[dest_pos].options[i].name,argname) 
                          == 0) {
                        if (dests[dest_pos].options[i].value) {
			  havedefault = 1;
			  if (opttype < 3)
			    defvalue=atof(dests[dest_pos].options[i].value);
			  else
			    strncpy(defstr,
				    dests[dest_pos].options[i].value, 1023);
			}
                        break;
		      }
		    
		    if (havedefault == 1) {
		      (*special_opts_arr)[current_option].havedefault = 1;
		      if (opttype < 3)
			(*special_opts_arr)[current_option].defvalue = 
			  defvalue;
		      else
			strncpy((*special_opts_arr)[current_option].defstr, 
				defstr, 1023);
		      // Set an option in the printer's option list
		      // for every numerical or string option to
		      // guarantee that the printer uses the shown
		      // value, even when the driver default and the
		      // PPD default are not equal.
		      if (opttype == 1) {
			sprintf(numbuffer,"%d",(int)(defvalue));
		      } else if (opttype == 1) {
			sprintf(numbuffer,"%.3f",defvalue);
		      } else {
			strncpy(numbuffer, defstr, 1023);
		      }
		      dests[dest_pos].num_options =
			cupsAddOption(argname,numbuffer,
				      dests[dest_pos].num_options,
				      &(dests[dest_pos].options));
		    }
                  }
                  // reset the values
                  argname[0] = '\0';
                  comment[0] = '\0';
		  defstr[0] = '\0';
                  opttype = 0;
                  min = 0.0; max = 0.0; defvalue = 0.0;
		  havedefault = 0;
                }
                openbrackets --;
              }
            } else {*writepointer = *scan; writepointer ++;}
            break;
	  case '\'': // quote
            if (!inquotes) { // open quote pair
              inquotes = 1;
	    } else { // close quote pair
              inquotes = 0;
            }
            break;
	  case '=': // "=>"
            if ((!inquotes) && (*(scan + 1) == '>')) {
              scan ++;
              // left hand side completed, go to right hand side
              *writepointer = '\0';
              writepointer = value;
            } else {*writepointer = *scan; writepointer ++;}
            break;
	  case ' ':  // white space
	  case '\t': // white space
	  case '\r': // Carriage return of dossy PPD files ("\n\r" at line end)
            if (!inquotes) {
              // ignore white space and carriage returns outside quotes
            } else {*writepointer = *scan; writepointer ++;}
            break;
	  default:
            // write all other characters
            *writepointer = *scan; writepointer ++;
            break;
        } 
      }
      inquotes = 0; // quote pairs cannot enclose more
                    // than one line
    } else if (line_contents = strstr(line,"*FoomaticRIP")) {
      // evaluate lines with new (3.x) Foomatic info
      line_contents += 12; // Go to the text after 
                           // "*FoomaticRIP"
      if (sscanf(line_contents, "Option %40[^: ]: %19s", &argname, 
		 &typestr) == 2) {
	if (strcmp(typestr, "int") == 0) {
	  // Integer numerical option
	  current_option =
	    getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	  (*special_opts_arr)[current_option].type=0;
	  (*special_opts_arr)[current_option].text[0] = '\0';
	} else if (strcmp(typestr, "float") == 0) {
	  // Floating point numerical option
	  current_option =
	    getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	  (*special_opts_arr)[current_option].type=1;
	  (*special_opts_arr)[current_option].text[0] = '\0';
	} else if (strcmp(typestr, "string") == 0) {
	  // String option
	  current_option =
	    getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	  (*special_opts_arr)[current_option].type=2;
	  (*special_opts_arr)[current_option].text[0] = '\0';
	} else if (strcmp(typestr, "password") == 0) {
	  // Password option
	  current_option =
	    getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	  (*special_opts_arr)[current_option].type=3;
	  (*special_opts_arr)[current_option].text[0] = '\0';
	}
      } else if (sscanf(line_contents,
			"OptionRange %40[^: ]: %lf %lf", &argname, &min, &max)
		 == 3) {
	// Maximum and minimum values for numerical options
	current_option =
	  getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	(*special_opts_arr)[current_option].min=min;
	(*special_opts_arr)[current_option].max=max;
	(*special_opts_arr)[current_option].text[0] = '\0';
      } else if (sscanf(line_contents,
			"Default%40[^: ]: %1023s", &argname, &defstr)
		 == 2) {
	// Default value
	current_option =
	  getSpecialOptionIndex(&options_found, special_opts_arr,
				  argname);
	// Has the user supplied a value for this option?
	for (i = 0; i < dests[dest_pos].num_options; i ++)
	  if (strcasecmp(dests[dest_pos].options[i].name,argname) 
	      == 0) {
	    if (dests[dest_pos].options[i].value)
	      strncpy(defstr,
		      dests[dest_pos].options[i].value, 1023);
	    break;
	  }
	// Convert to a number if possible
	defvalue = atof(defstr);
	// Set default for numerical option
	(*special_opts_arr)[current_option].defvalue = 
	  defvalue;
	// Set default for string/password option
	strncpy((*special_opts_arr)[current_option].defstr, 
		defstr, 1023);
	(*special_opts_arr)[current_option].text[0] = '\0';
	// Set an option in the printer's option list for every
	// numerical/string option to guarantee that the printer uses the
	// shown value, even when the driver default and the PPD
	// default are not equal.
	dests[dest_pos].num_options =
	  cupsAddOption(argname,defstr,
			dests[dest_pos].num_options,
			&(dests[dest_pos].options));
	// Mark that we have found a default setting
	(*special_opts_arr)[current_option].havedefault = 1;
      }
    }
  }
  fclose(ppdfile);
  return(options_found);
}

/*
 *  Determine the value of an option, taking into account PPD file options,
 *  options from "~/.lpotions", and command line options
 */

int                                              /* O - menu pos. of item */
printFiles::getChosenValue(const char *option1,
                           const char *option2,  /* I - options to examine */
                           const Fl_Menu_Item *values) 
                                           /* I - check value in this menu */
{
  int	       i,j,k;         /* Looping vars */
  const Fl_Menu_Item *item;   /* Item to check */
  ppd_option_t *ppdoption;    /* choice struct taken from PPD file */
  char         *val,          /* Pointer into value */
               *ptr,          /* Pointer into string */
               s[255];        /* Temporary string */
  const char   *option[2] = { option1, option2 };
                              /* the two given option keywords */
  int          nopt;          /* Number of option keywords given */

  // Set nopt

  if (option2) nopt = 2; else nopt = 1;

  // Which printer was chosen?

  setDest();

  // Check the option list of the destination struct first, because it
  // overrides the defaults set in the PPD file

  for (item = values, j = 0; item->text; item++, j ++) 
  for (i = 0; i < dests[dest_pos].num_options; i ++)
  for (k = 0; k < nopt; k ++)
    if (strcasecmp(dests[dest_pos].options[i].name,option[k]) == 0)
    {
      if (strcasecmp(option[k],"media") == 0)
      {
        for (val = dests[dest_pos].options[i].value; *val;)
	{
          for (ptr = s; *val && *val != ',' && (int)(ptr - s) < (int)(sizeof(s) - 1);)
            *ptr++ = *val++;
          *ptr++ = '\0';
          if (*val == ',') val ++;
          if (strcasecmp(s,(char *)(item->user_data_)) == 0) return(j);
        }
      } 
      else if (strcasecmp(dests[dest_pos].options[i].value,
                          (char *)(item->user_data_)) == 0)
        return(j);
    }

  // Check for defaults in the PPD file

  if (ppds[dest_pos] == NULL) return (0);

  for (item=values, j = 0; item->text; item ++, j ++) 
  for (k = 0; k < nopt; k ++)
  if (strcasecmp(option[k], "media") == 0)
  {
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"PageSize")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"InputSlot")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"MediaType")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"EFMediaQualityMode")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"ManualFeed")))
      if (strcasecmp(ppdoption->defchoice,"True") == 0)
        if (strcasecmp((char *)(item->user_data_), "manual") == 0) return(j);
  } 
  else if (strcasecmp(option[k], "sides") == 0)
  {
    if (!(ppdoption = ppdFindOption(ppds[dest_pos],"Duplex")))
      if (!(ppdoption = ppdFindOption(ppds[dest_pos],"EFDuplex")))
        ppdoption = ppdFindOption(ppds[dest_pos],"KD03Duplex");
    if (ppdoption)
    { 
      if (strcasecmp(ppdoption->defchoice,"DuplexNoTumble") == 0)
      {
        if (strcasecmp((char *)(item->user_data_),
                       "two-sided-long-edge") == 0) return(j);
      }
      else if (strcasecmp(ppdoption->defchoice,"DuplexTumble") == 0)
      {
        if (strcasecmp((char *)(item->user_data_),
                       "two-sided-short-edge") == 0) return(j);
      }
      else
      {
        if (strcasecmp((char *)(item->user_data_),
                       "one-sided") == 0) return(j);
      }
    }
  }
  else if ((strcasecmp(option[k], "resolution") == 0) ||
           (strcasecmp(option[k], "printer-resolution") == 0))
  {
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"Resolution")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"SetResolution")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"JCLResolution")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"CNRes_PGP")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
  }
  else if (strcasecmp(option[k], "output-bin") == 0)
  {
    if ((ppdoption = ppdFindOption(ppds[dest_pos],"OutputBin")))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);
  }
  else
  {
    if ((ppdoption = ppdFindOption(ppds[dest_pos],option[k])))
      if (strcasecmp(ppdoption->defchoice,
                     (char *)(item->user_data_)) == 0) return(j);    
  }

  // Combination option=value not found

  return(0);
}

/*
 *  Get value of an option from "~/.lpotions" or command line
 */

const char *                                    /* O - value */
printFiles::getOptionValue(const char *option)  /* I - option to examine */
{
  int	       i;             /* Looping var */

  // Search the option in the option list of the option dialog
  // and return a pointer to its value

  for (i = 0; i < num_changed_options; i ++)
    if (strcasecmp(changed_options[i].name,option) == 0)
      return(changed_options[i].value);

  // Option not found or empty

  return(NULL);
}

/*
 *  remove an option from an option list
 */

int                                                    /* new # of options*/
printFiles::removeOptions(const char *option,          /* which option */
                          int option_list_length,      /* # opt in list */
                          cups_option_t **option_list) /* from which list */
{
  int	        i,j;             /* Looping vars */
  cups_option_t *temp1, *temp2, *temp3;

  for (i = 0, temp1 = *option_list; i < option_list_length; i ++, temp1 ++) {
    while ((temp1->name) &&
           (strcasecmp(temp1->name,option) == 0)) {
      free(temp1->name);
      if (temp1->value) free(temp1->value);
      for (j = i+1, temp2 = temp1, temp3 = temp1 + 1;
           j < option_list_length; j ++, temp2 ++, temp3 ++) {
        temp2->name = temp3->name;  
        temp2->value = temp3->value;
      }
      temp2->name = NULL;
      temp2->value = NULL;
      option_list_length --;
    }
  }
  if (*option_list != NULL) 
    *option_list = (cups_option_t *)realloc(*option_list,
      sizeof(cups_option_t) * option_list_length);
  return(option_list_length);
}

/*
 * Get default page size and margins of a printer and read the margin
 * settings from the user-supplied options
 */

int                             /* O - user-supplied settings exist? */
printFiles::getPageMargins(int &top, int &bottom, int &left, int &right,
                                /* O - obtained values of margins */
                           int &length, int &width, int &orientation)
                                /* O - obtained height and width of the page */
				/* O - orientation */
{
  int           i;
  ppd_size_t    *pagesize;
  const char    *str;
  const char    *val;          /* value read from user-suppiled option */
  int           user_supplied; /* was there a user-suppiled option? */

  // Standard page sizes and margins in points (1 pt = 1/72 inch)

  const int num_default_pagesizes = 6;
  ppd_size_t    default_pagesize[] = {
        {0,"Letter",612,792,18,36,594,756},
        {0,"Legal",612,1008,18,36,594,972},
        {0,"A4",595,842,18,36,577,806},
        {0,"A3",842,1190,18,36,824,1154},
        {0,"COM10",297,684,18,36,279,648},
        {0,"DL",312,624,18,36,294,588}
  };

  // Which printer was chosen?

  setDest();

  // Get default page size

  if (ppds[dest_pos] != NULL) {
    // Check PPD file if available
    ppd_choice_t *ch = ppdFindMarkedChoice(ppds[dest_pos],"PageSize");
    str = (ch ? ch->choice : 0);
    pagesize = ppdPageSize(ppds[dest_pos],(str ? str : "Letter"));
    // Broken PPD files where the width and the length of the page
    // are not defined
    if ((pagesize->length == 0) || (pagesize->width == 0))
      for (int i = 0; i < num_default_pagesizes; i++)
        if (strcasecmp((str ? str : "Letter"),default_pagesize[i].name) == 0) {
          pagesize->length = (default_pagesize + i)->length;
          pagesize->width = (default_pagesize + i)->width;
          break;
        }
    if ((pagesize->length == 0) || (pagesize->width == 0)) {
      pagesize->length = default_pagesize->length;
      pagesize->width = default_pagesize->width;
    }
  } else {
    // Default page size without PPD file: Letter
    pagesize = default_pagesize;
  }

  // If the user has chosen another page size, get it

  if (str = cupsGetOption("media",num_changed_options,changed_options)) {
    // Option "media"
    char            *s = new char[strlen(str)+2],
                    *c;
    ppd_size_t      *sz(0);
    strcpy(s,str);
    strcat(s,",");
    c = strtok(s,",");
    while (c && !sz) {
      if (ppds[dest_pos]) {
        // Page size info from PPD file 
        sz = ppdPageSize(ppds[dest_pos],c);
        // Broken PPD files where the width and the length of the page
        // are not defined
        if ((pagesize->length == 0) || (pagesize->width == 0))
          for (int i = 0; i < num_default_pagesizes; i++)
            if (strcasecmp(c,default_pagesize[i].name) == 0) {
              pagesize->length = (default_pagesize + i)->length;
              pagesize->width = (default_pagesize + i)->width;
              break;
            }
        if ((pagesize->length == 0) || (pagesize->width == 0)) {
          pagesize->length = default_pagesize->length;
          pagesize->width = default_pagesize->width;
        }
      }
      // Page size info from base of standard values
      if (!sz)
        for (int i = 0; i < num_default_pagesizes; i++)
          if (strcasecmp(c,default_pagesize[i].name) == 0) {
            sz = default_pagesize + i;
            break;
          }
      c = strtok(NULL,",");
    }
    delete [] s;
    if (sz) pagesize = sz;
  }

  if (str = cupsGetOption("PageSize",num_changed_options,changed_options)) {
    // Option "PageSize"
    ppd_size_t      *sz(0);
    if (ppds[dest_pos]) sz = ppdPageSize(ppds[dest_pos],str);
    if (!sz)
      for (i = 0; i < num_default_pagesizes; i++)
        if (strcasecmp(str,default_pagesize[i].name) == 0) {
          sz = default_pagesize + i;
          break;
        }
  }

  length = (int)(pagesize->length);
  width = (int)(pagesize->width);
  top = (int)(length-pagesize->top);
  bottom = (int)(pagesize->bottom);
  left = (int)(pagesize->left);
  right = (int)(width-pagesize->right);

  // Change length and width when the printout is in Landscape format

  orientation = 3;
  if (getOptionValue("landscape")) orientation = 4;
  if (str = getOptionValue("orientation-requested")) orientation = atoi(str);
  if ((orientation == 4) || (orientation == 5)) {
    i = length; length = width; width = i;
  }

  // Adjust default margins with paper orientation

  int v[4] = {left, bottom, right, top};
  i = orientation-3;
  if (i > 1) i ^= 1;
  left = v[i%4];
  bottom = v[(i+1)%4];
  right = v[(i+2)%4];
  top = v[(i+3)%4];
  
  user_supplied = 0;
  if (val = (getOptionValue("page-top"))) {
    user_supplied = 1;
    top = atoi(val); 
  }
  if (val = (getOptionValue("page-bottom"))) {
    user_supplied = 1;
    bottom = atoi(val); 
  }
  if (val = (getOptionValue("page-left"))) {
    user_supplied = 1;
    left = atoi(val); 
  }
  if (val = (getOptionValue("page-right"))) {
    user_supplied = 1;
    right = atoi(val); 
  }

  return(user_supplied);
}

int                             /* O - user-supplied settings exist? */
printFiles::getPageMargins(int &top, int &bottom, int &left, int &right,
                                /* O - obtained values of margins */
                           int &length, int &width)
    /* O - obtained height and width of the page */
{
    int orientation;
    getPageMargins(top, bottom, left,right, length, width, orientation);
}

/*
 *  Scan PPD file to build the widgets for the printer-specific options
 */

void
printFiles::buildOptionWidgets()
{
  int		j, k, m;	/* Looping vars */
  int           nextWidgetY;    /* Where should the next widget be drawn? */
  int           markedfound;    /* was there any option marked? */   
  ppd_group_t	*group;		/* UI group */
  ppd_option_t	*option;	/* Standard UI option */
  ppd_choice_t	*choice;	/* Standard UI option choice */
  special_opt_t *special_option;/* Foomatic numerical/string/password 
				   option */
  char          buffer[1024];   /* buffer for generating strings */
  int           readonly;       /* 1 for readonly tab (Installed Options) */
  int           isspecial;    /* 1 when the option is a Foomatic numerical
				   or string/password option */
  int           specialgroupinserted = 0; /* Did we already insert an
					    "Special" tab in the end? */
  Fl_Group      *currentTab;    /* Tab where widgets are currently added.
                                   must be made red if current options
                                   conflicts. */
  Fl_Choice     *alsoaddto = NULL;/* Pointer to widget in "Basic" options tab */
                                /* where the choices from the PPD file */
                                /* should be added, too */

  // Which printer was chosen?

  setDest();

  // Make created groups being added as tabs in the option dialog

  Fl_Group::current(tabgroup);

  // Make "Basic" tab black to remove the red from a formerly conflicting
  // option

  tabgroup->child(0)->labelcolor(FL_BLACK);

  // Reset conflict flag

  conflicting = 0;

  // Go through the PPD file and generate a widget for every option

  if (ppds[dest_pos] != NULL) {
    // We have a PPD file, so delete the default options from the widgets
    // for standard options
    papersourcewidget->menu(NULL);
    papersizewidget->menu(NULL);
    papertypewidget->menu(NULL);
    duplexwidget->menu(NULL);

    // Standard PPD options (Boolean, Pickone, Pickmany)
    for (j = 0, group = ppds[dest_pos]->groups; 
         j < ppds[dest_pos]->num_groups;
         j ++, group ++) {
      // Insert group
      Fl_Group* o = new Fl_Group(5, 35, 530, 390, group->text);
      o->user_data((void *)this);
      currentTab = o;
      // Make group "Installed Options" or similar readonly (changing these 
      // options by normal users has no sense)
      if (strstr(group->text,"nstall") != NULL) {
        readonly = 1;
        o->set_output();
      } else readonly = 0;
      // conflicting=0;
      nextWidgetY = 45;
      if (readonly) {
        Fl_Box* b = new Fl_Box
          (15, nextWidgetY, 510, 50, "These settings are defined by the printer hardware. They cannot be changed by the user.");
        b->align(FL_ALIGN_WRAP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
        b->box(FL_ENGRAVED_BOX);
        nextWidgetY += 55;
      }
      for (k = 0, option = group->options; k < group->num_options; k ++, option ++)
      {
	if (option->ui < 2) {// BOOLEAN or PICKONE
	  // Omit option "PageRegion"
	  if (strcasecmp(option->keyword,"PageRegion") == 0) continue;
	  // Insert new group if there is no space for new widgets
	  if (nextWidgetY > 400) {
	    o->end();
	    Fl_Group* o = new Fl_Group(5, 35, 530, 390, group->text);
	    o->user_data((void *)this);
	    currentTab = o;
	    if (readonly) o->set_output();
	    nextWidgetY = 45;
	  }
	  // Do we have a numerical or string/password option?
	  isspecial = 0;
	  for (m = 0, special_option = special_opts[dest_pos]; 
	       m < num_special_opts[dest_pos];
	       m ++, special_option ++)
	    {
	      if (strcmp(option->keyword, special_option->keyword) == 0) {
		isspecial = 1;
		// Remove the menu text from the option to mark that it
		// appeared also as a regular option in the PPD file and
		// so is already inserted here, so it does not need to
		// be inserted in the "Special" group afterwards.
		special_option->text[0] = '\0';
		break;
	      }
	    }
	  // Insert widget
	  if ((isspecial == 1) && (special_option->type < 2)) { 
	    // Slider for numerical option
	    sprintf(buffer,"%s: ", option->text);
	    Input_Slider* o = new Input_Slider
	      (265, nextWidgetY, 260, 25, strdup(buffer));
	    o->type(FL_HOR_NICE_SLIDER);
	    o->minimum(special_option->min);
	    o->maximum(special_option->max);
	    if (special_option->havedefault == 1)
	      o->value(special_option->defvalue);
	    else
	      o->value(atof(option->defchoice));
	    for (m = 0; m < num_changed_options; m ++)
	      if (strcasecmp(changed_options[m].name,
			     special_option->keyword) == 0) {
		if (changed_options[m].value)
		  o->value(atof(changed_options[m].value));
		break;
	      }
	    o->align(FL_ALIGN_LEFT);
	    o->box(FL_FLAT_BOX);
	    sprintf(buffer,"%s %d",special_option->keyword,special_option->type);
	    o->user_data((void *)strdup(buffer));
	    o->callback((Fl_Callback*)(cbNumericalOption));
	    if (special_option->type == 0) {
	      // option is integer, make sure that only integer values
	      // can be adjusted
	      k = (int)(special_option->max) - (int)(special_option->min);
	      // Max. 260 steps on full length of scrollbar (1 step >= 1 
	      // pixel)
	      if (k <= 260) {
		o->step(1,1);
	      } else {
		o->step(k/260+1,1);
	      }
	    } else {
	      // option is float, set a small step size
	      o->step(0.001);
	    }
	    nextWidgetY += 30;
	  } else {
	    // Menu for enumerated choice and string, in case of string
	    // with possibility to edit the chosen value

	    // If option treated now is "PageSize", "InputSlot", or
	    // "Duplex" replace the original (default) choices by the
	    // ones listed in the PPD file
	    if (strcasecmp(option->keyword,"InputSlot") == 0) {
	      // The choices will also be added to this widget
	      alsoaddto = papersourcewidget;
	    } else if (strcasecmp(option->keyword,"PageSize") == 0) {
	      // The choices will also be added to this widget
	      alsoaddto = papersizewidget;
	    } else if (strcasecmp(option->keyword,"MediaType") == 0) {
	      // The choices will also be added to this widget
	      alsoaddto = papertypewidget;
	    } else if (strstr(option->keyword,"Duplex")) {
	      // check whether the word "install" is in one of the
	      // choices, if so, this option is to mark whether a
	      // duplexer is installed and not to turn on and off
	      // double-sided printing. Do not link this option to the
	      // "Duplex" option on the "Basic" tab in this case
	      int dupinst = 0;
	      for (m = 0, choice = option->choices;
		   m < option->num_choices;
		   m ++, choice ++)
		{
		  if ((strstr(choice->choice,"nstall")) ||
		      (strstr(choice->text,"nstall")) ||
		      (strstr(choice->choice,"INSTALL")) ||
		      (strstr(choice->text,"INSTALL"))) dupinst = 1;
		}
	      // The choices will also be added to this widget
	      if (dupinst == 0) alsoaddto = duplexwidget;
	      else alsoaddto = NULL;
	    } else alsoaddto = NULL;
	    // Tell to the widget on the "Basic" tab which option to change
	    if (alsoaddto) alsoaddto->user_data(option->keyword);

	    sprintf(buffer,"%s: ",option->text);
	    if ((isspecial == 1) && (special_option->type > 1)) {
	      // String/password: Input field to enter string + menu with
	      // choosable strings
	      Fl_Menu_Button* o;
	      Fl_Input* p;
	      Fl_Box* q;
	      if (option->num_choices > 0) {
		q = new Fl_Box
		  (265, nextWidgetY, 260, 25, "");
		q->box(FL_DOWN_BOX);
		if (special_option->type == 3) {
		  p = new Fl_Secret_Input
		    (267, nextWidgetY+2, 228, 21, strdup(buffer));
		} else {
		  p = new Fl_Input
		    (267, nextWidgetY+2, 228, 21, strdup(buffer));
		}
		p->box(FL_FLAT_BOX);
		o = new Fl_Menu_Button
		  (493, nextWidgetY+2, 30, 21, "");
		o->box(FL_UP_BOX);
		o->user_data((void *)p);
	      } else {
		if (special_option->type == 3) {
		  p = new Fl_Secret_Input
		    (265, nextWidgetY, 260, 25, strdup(buffer));
		} else {
		  p = new Fl_Input
		    (265, nextWidgetY, 260, 25, strdup(buffer));
		}
	      }
	      sprintf(buffer,"%s",special_option->keyword);
	      p->user_data((void *)strdup(buffer));
	      p->callback((Fl_Callback*)(cbStringInput));
	      if (option->conflicted) {
		p->labelcolor(FL_RED);
		p->textcolor(FL_BLACK);
		if (alsoaddto) {
		  alsoaddto->labelcolor(FL_RED);
		  alsoaddto->textcolor(FL_BLACK);
		}
		conflicting = 1;
		currentTab->labelcolor(FL_RED);
		if (alsoaddto) tabgroup->child(0)->labelcolor(FL_RED);
	      } else {
		p->labelcolor(FL_BLACK);
		p->textcolor(FL_BLACK);
		if (alsoaddto) {
		  alsoaddto->labelcolor(FL_BLACK);
		  alsoaddto->textcolor(FL_BLACK);
		}
	      } 
	      if (readonly) p->set_output();
	      if (option->num_choices > 0) {
		markedfound = 0;
		for (m = 0, choice = option->choices;
		     m < option->num_choices;
		     m ++, choice ++) {
		  sprintf(buffer,"%s=%s",option->keyword,choice->choice);
		  o->add("x",0,(Fl_Callback *)(cbStringInputMenu),(void *)strdup(buffer),0);
		  // The replacing is done to get '/' into the menues
		  o->replace(m,choice->text);
		  if (alsoaddto) {
		    alsoaddto->add("x",0,NULL,(void *)strdup(choice->choice),0);
		    alsoaddto->replace(m,choice->text);
		  };
		  if (choice->marked != 0) {
		    o->value(m);
		    p->value(choice->choice);
		    if (alsoaddto) alsoaddto->value(m);
		    markedfound = 1;
		  }
		}
		if (markedfound == 0) {
		  for (m = 0, choice = option->choices;
		       m < option->num_choices;
		       m ++, choice ++)
		    if (strcmp(option->defchoice, choice->choice) == 0) {
		      o->value(m);
		      p->value(choice->choice);
		      if (alsoaddto) alsoaddto->value(m);
		      choice->marked=1;
		      markedfound=1;
		    }
		}
		if (markedfound == 0) {
		  o->value(0);
		  p->value(option->choices->choice);
		}
	      } else {
		p->value("");
	      }
	      if (special_option->havedefault == 1)
	        p->value(special_option->defstr);
	      else
		p->value(option->defchoice);
	      for (m = 0; m < num_changed_options; m ++)
		if (strcasecmp(changed_options[m].name,
			       special_option->keyword) == 0) {
		  if (changed_options[m].value)
		    p->value(changed_options[m].value);
		  break;
		}
	    } else {
	      // Enumerated choice: Menu to choose setting
	      Fl_Choice* o = new Fl_Choice
		(265, nextWidgetY, 260, 25, strdup(buffer));
	      if (option->conflicted) {
		o->labelcolor(FL_RED);
		o->textcolor(FL_BLACK);
		if (alsoaddto) {
		  alsoaddto->labelcolor(FL_RED);
		  alsoaddto->textcolor(FL_BLACK);
		}
		conflicting = 1;
		currentTab->labelcolor(FL_RED);
		if (alsoaddto) tabgroup->child(0)->labelcolor(FL_RED);
	      } else {
		o->labelcolor(FL_BLACK);
		o->textcolor(FL_BLACK);
		if (alsoaddto) {
		  alsoaddto->labelcolor(FL_BLACK);
		  alsoaddto->textcolor(FL_BLACK);
		}
	      } 
	      if (readonly) o->set_output();
	      markedfound = 0;
	      for (m = 0, choice = option->choices;
		   m < option->num_choices;
		   m ++, choice ++) {
		sprintf(buffer,"%s=%s",option->keyword,choice->choice);
		o->add("x",0,(Fl_Callback *)(cbPickOneOption),(void *)strdup(buffer),0);
		// The replacing is done to get '/' into the menues
		o->replace(m,choice->text);
		if (alsoaddto) {
		  alsoaddto->add("x",0,NULL,(void *)strdup(choice->choice),0);
		  alsoaddto->replace(m,choice->text);
		};
		if (choice->marked != 0) {
		  o->value(m);
		  if (alsoaddto) alsoaddto->value(m);
		  markedfound = 1;
		}
	      }
	      if (markedfound == 0) {
		for (m = 0, choice = option->choices;
		     m < option->num_choices;
		     m ++, choice ++)
		  if (strcmp(option->defchoice, choice->choice) == 0) {
		    o->value(m);
		    if (alsoaddto) alsoaddto->value(m);
		    choice->marked=1;
		    markedfound=1;
		  }
	      }
	      if (markedfound == 0) o->value(0);
	    }
	    nextWidgetY += 30;
	    alsoaddto = NULL;
	  }
        } else {
	  // PICKMANY
          // Insert new group if there is no space for new widgets
          if (nextWidgetY > 375) {
            o->end();
            Fl_Group* o = new Fl_Group(5, 35, 530, 390, group->text);
            o->user_data((void *)this);
            currentTab = o;
            if (readonly) o->set_output();
            nextWidgetY = 45;
          }
          sprintf(buffer,"%s: ",option->text);
          Fl_Box* b = new Fl_Box
            (5, nextWidgetY, 260, 25, strdup(buffer));
          b->align(FL_ALIGN_RIGHT|FL_ALIGN_INSIDE);
          if (option->conflicted) {
            b->labelcolor(FL_RED);
            conflicting = 1;
            currentTab->labelcolor(FL_RED);
          } else b->labelcolor(FL_BLACK); 
          Fl_Multi_Browser* o = new Fl_Multi_Browser
            (265, nextWidgetY, 260, 50);
          o->user_data((void *)option->keyword);
          o->callback((Fl_Callback *)(cbPickManyOption));
          if (readonly) o->set_output();
          for (m = 0, choice = option->choices;
	       m < option->num_choices;
	       m ++, choice ++)
	  {
            o->add(choice->text,(void *)choice->choice);
            if (choice->marked != 0) o->select(m);
	  }
          nextWidgetY += 55;
        }
      }
      o->end();
    }
    // Foomatic 2.x numerical/string options
    if (num_special_opts[dest_pos] > 0) {
      Fl_Group* o;
      for (j = 0, special_option = special_opts[dest_pos]; 
           j < num_special_opts[dest_pos];
           j ++, special_option ++)
      {
	if (strlen(special_option->text) == 0) continue;
	// We have a numerical or string option not defined the regular way
	// in the PPD file, but a pure Foomatic 2.x option defined
	// in the Perl data structure in the COMDATA lines, so we need
	// to add an extra group for these options
	if (specialgroupinserted == 0) {
	  // Insert group
	  o = new Fl_Group(5, 35, 530, 390, "Special");
	  o->user_data((void *)this);
	  currentTab = o;
	  nextWidgetY = 45;
	  specialgroupinserted = 1;
	}
        // Insert new group if there is no space for new widgets
        if (nextWidgetY > 400) {
          o->end();
          Fl_Group* o = new Fl_Group(5, 35, 530, 390, "Special");
          o->user_data((void *)this);
          currentTab = o;
          nextWidgetY = 45;
        }
        // Insert widget
        sprintf(buffer,"%s: ",special_option->text);
	if (special_option->type < 2) {
	  // Slider for numerical option
	  Input_Slider* o = new Input_Slider
	    (225, nextWidgetY, 300, 25, strdup(buffer));
	  o->type(FL_HOR_NICE_SLIDER);
	  o->minimum(special_option->min);
	  o->maximum(special_option->max);
	  if (special_option->havedefault == 1)
	    o->value(special_option->defvalue);
	  else {
	    if ((special_option->min <= 0) && (special_option->max >= 0))
	      o->value(0);
	    else if (special_option->min > 0)
	      o->value(special_option->min);
	    else
	      o->value(special_option->max);
	  }
	  for (k = 0; k < num_changed_options; k ++)
	    if (strcasecmp(changed_options[k].name,special_option->keyword) == 0) {
	      if (changed_options[k].value)
		o->value(atof(changed_options[k].value));
	      break;
	    }
	  o->align(FL_ALIGN_LEFT);
	  o->box(FL_FLAT_BOX);
	  sprintf(buffer,"%s %d",special_option->keyword,special_option->type);
	  o->user_data((void *)strdup(buffer));
	  o->callback((Fl_Callback*)(cbNumericalOption));
	  if (special_option->type == 0) {
	    // option is integer, make sure that only integer values can be
	    // adjusted
	    k = (int)(special_option->max) - (int)(special_option->min);
	    // Max. 300 steps on full length of scrollbar (1 step >= 1 
	    // pixel)
	    if (k <= 300) {
	      o->step(1,1);
	    } else {
	      o->step(k/300+1,1);
	    }
	  } else {
	    // option is float, set a small step size
	    o->step(0.001);
	  }
	} else {
	  // String: Input field to enter string
	  Fl_Input* p = new Fl_Input
	    (225, nextWidgetY, 300, 25, strdup(buffer));
	  sprintf(buffer,"%s",special_option->keyword);
	  p->user_data((void *)strdup(buffer));
	  p->callback((Fl_Callback*)(cbStringInput));
	  p->value("");
	  if (special_option->havedefault == 1)
	    p->value(special_option->defstr);
	  for (m = 0; m < num_changed_options; m ++)
	    if (strcasecmp(changed_options[m].name,
			   special_option->keyword) == 0) {
	      if (changed_options[m].value)
		p->value(changed_options[m].value);
	      break;
	    }
	}
	nextWidgetY += 30;
      }
    }

    // Deactivate standard option widgets when the PPD file didn't contain
    // an option for them
    if (papersourcewidget->size() < 2) {
      papersourcewidget->deactivate();
      papersourcewidget->user_data((void *)"none");
    } else papersourcewidget->activate();
    if (papersizewidget->size() < 2) {
      papersizewidget->deactivate();
      papersizewidget->user_data((void *)"none");
    } else papersizewidget->activate();
    if (papertypewidget->size() < 2) {
      papertypewidget->deactivate();
      papertypewidget->user_data((void *)"none");
    } else papertypewidget->activate();
    if (duplexwidget->size() < 2) {
      duplexwidget->deactivate();
      duplexwidget->user_data((void *)"none");
    } else duplexwidget->activate();

  }
  tabgroup->value(tabgroup->child(0));
}

/*
 *  Remove all groups of PPD options
 */

void
printFiles::removeOptionWidgets()
{
  while (tabgroup->children() > 4) tabgroup->remove(tabgroup->child(4));
}

/*
 *  Refresh all groups of PPD options
 */

void
printFiles::refreshOptionWidgets()
{
  int i;      /* Looping var */
  int top, bottom, left, right, length, width; /* paper size values */
  int orientation; /* paper orientation */
  // Which printer was chosen?

  setDest();

  if (ppds[dest_pos] != NULL) {
    // Mark the default settings in the PPD file (to unmark options which
    // were reset by the user)
    ppdMarkDefaults(ppds[dest_pos]);
    // Mark all given options as set in the option dialog
    cupsMarkOptions(ppds[dest_pos],num_changed_options,changed_options);
    // Store the number of the tab where the user has been before in i
    for (i = 0; i < tabgroup->children(); i++)
      if (tabgroup->value() == tabgroup->child(i)) break;
    // remove old widgets
    removeOptionWidgets();
    // build updated widgets
    buildOptionWidgets();
    // bring the panel to the top where we were before
    tabgroup->value(tabgroup->child(i));
    // Adjust the sliders for the text margins to the chosen paper size
    getPageMargins(top, bottom, left, right, length, width, orientation);
    if (!custom_set) {
	if (orientation == 4 || orientation == 5) {
	    /* rotate back for physical orientation */
	    custom_width_pspt = length;
	    custom_height_pspt = width; 
	} else {
	    custom_width_pspt = width;
	    custom_height_pspt = length; 
	}
    }
    topmargin->maximum(length);
    bottommargin->minimum(length);
    leftmargin->maximum(width);
    rightmargin->minimum(width);
    topmargin->value(top);
    bottommargin->value(bottom);
    leftmargin->value(left);
    rightmargin->value(right);
    papercustomsizewidget->callback()((Fl_Widget*)papercustomsizewidget,NULL);
    // redraw dialog
    tabgroup->redraw();
  }
}
   
/*
 *  Callback functions for the option widgets
 */

inline void printFiles::cbiPickOneOption(Fl_Widget* o, // From which widget 
					               // we are called
                                         void* v)      // option to add
{
  char *option, *arg;
  option = strdup((char *)v);
  arg = strchr(option,'=');
  if (arg == NULL) arg = ""; else {*arg = '\0'; arg ++; }
  num_changed_options=cupsAddOption(option,arg,
				    num_changed_options,&changed_options);
  free(option);
  refreshOptionWidgets();
}

void printFiles::cbPickOneOption(Fl_Widget* o, // From which widget 
		     		               // we are called
                                 void* v)      // option to add
{
  ((printFiles*)(o->parent()->user_data()))->cbiPickOneOption(o,v);
}

inline void printFiles::cbiPickManyOption(Fl_Widget* o, // From which widget 
					                // we are called
                                          void* v)      // option to add
{
  int i;
  char *option;
  char arg[1024];
  Fl_Multi_Browser *m = (Fl_Multi_Browser *)o;
  option = strdup((char *)v);
  arg[0] = '\0';
  for (i = 1; i <= m->size(); i++) {
    if (m->selected(i)) {
      if (strlen(arg) > 0) strcat(arg,",");
      strcat(arg,(char *)(m->data(i)));
    }
  }
  num_changed_options=cupsAddOption(option,arg,
				    num_changed_options,&changed_options);
  free(option);
  refreshOptionWidgets();
}

void printFiles::cbPickManyOption(Fl_Widget* o, // From which widget 
		     		                // we are called
                                  void* v)      // option to add
{
  ((printFiles*)(o->parent()->user_data()))->cbiPickManyOption(o,v);
}

inline void printFiles::cbiNumericalOption(Input_Slider* o, 
                                                         // From which widget 
					                 // we are called
                                           void* v)      // option to change
{
  char *option, *type, arg[128];
  option = strdup((char *)v);
  type = strchr(option,' ');
  *type = '\0';
  type ++;
  if (*type == '0') {
    sprintf(arg,"%d",(int)(o->clamp(o->value())));
  } else {
    sprintf(arg,"%.3f",o->clamp(o->value()));
  }
  num_changed_options=cupsAddOption(option,arg,
				    num_changed_options,&changed_options);
  free(option);
}

void printFiles::cbNumericalOption(Input_Slider* o, // From which widget 
		     		                       // we are called
                                   void* v)            // option to change
{
  ((printFiles*)(o->parent()->user_data()))->cbiNumericalOption(o,v);
}

inline void printFiles::cbiStringInput(Fl_Widget* o,// From which widget
				                    // we are called
				       void* v)     // option to add
{
  char *option;
  option = strdup((char *)v);
  num_changed_options=cupsAddOption(option,((Fl_Input*)o)->value(),
				    num_changed_options,&changed_options);
  free(option);
  refreshOptionWidgets();
}

void printFiles::cbStringInput(Fl_Widget* o, // From which widget 
			                     // we are called
			       void* v)      // option to add
{
  ((printFiles*)(o->parent()->user_data()))->cbiStringInput(o,v);
}

inline void printFiles::cbiStringInputMenu(Fl_Widget* o,// From which widget
					                // we are called
					   void* v)     // option to add
{
  char *option, *arg;
  option = strdup((char *)v);
  arg = strchr(option,'=');
  if (arg == NULL) arg = ""; else {*arg = '\0'; arg ++; }
  num_changed_options=cupsAddOption(option,arg,
				    num_changed_options,&changed_options);
  ((Fl_Input*)(o->user_data()))->value(arg);
  free(option);
  refreshOptionWidgets();
}

void printFiles::cbStringInputMenu(Fl_Widget* o, // From which widget 
				                 // we are called
				   void* v)      // option to add
{
  ((printFiles*)(o->parent()->user_data()))->cbiStringInputMenu(o,v);
}

/*
 *  Set up destination list
 */

void
printFiles::setupDestinationList(Fl_Choice* destChoice)  // Widget where to
                                                         // fill in the 
                                                         // destination
                                                         // names
{
  int   i;              /* Looping var */
  char  buffer[1024];   /* buffer for generating strings */

  // Delete former menu entries

  destChoice->clear();

  // Put in the destinations as currently listed

  for (i = 0; i < num_dests; i ++) {
    if ((!dests[i].instance) ||
        (dests[i].instance[0] == '\0'))
      sprintf(buffer,"Default instance on %s",dests[i].name);
    else
      sprintf(buffer,"%s on %s",dests[i].instance,dests[i].name);
    // Use the replacement method to avoid the creation of a seperator
    destChoice->add("x",0,0,0,0);
    destChoice->replace(i,buffer);
  }

}

/*
 *  Copy an instance of a destination
 */

int // -2 when nothing was copied, -1 when an existing destination was 
    // overwritten, else the position of the new instance in the menu in
    // the main window
printFiles::copyInstance(int dest_index,    // Index of the destination to 
		                            // copy
                    const char *new_name)   // Name of the new instanc
{
  int   i;              /* Looping var */
  char  buffer[1024];   /* buffer for generating strings */
  int   new_index;      /* position where to insert new instance */
  int   overwrite = 0;  /* Do we overwrite an existing destination? */

  // Which printer was chosen in the main window?

  setDest();

  // Avoid that a blank destination name is NULL.

  if ((!new_name) || (new_name[0] == '\0')) {
    new_name = "";
  }

  // Did the user enter a valid instance name?

  if (((dests[dest_index].instance) && 
       (strcasecmp(new_name,dests[dest_index].instance) == 0)) ||
      ((!dests[dest_index].instance) &&
       (new_name[0] == '\0'))) {
    fl_alert("The new instance must be different to the original instance!");
    return(-2);
  }
  if (strspn(new_name,"_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") < strlen(new_name)) {
    fl_alert("The new instance name should contain no other characters than letters, numbers, and the underscore!");
    return(-2);
  }

  // Where should the instance be inserted?

  i = 0;
  while (strcasecmp(dests[dest_index].name,dests[i].name) != 0) i ++;
  if (new_name[0] != '\0')
    while ((i < num_dests) &&
           (strcasecmp(dests[dest_index].name,dests[i].name) == 0) &&
           ((!dests[i].instance) ||
            (strcasecmp(new_name,dests[i].instance) > 0))) i ++;
  new_index = i;

  // Does the name of the new instance already exist or is it going to
  // overwrite the default instance (blank destination name)?
  // If the user agrees the already existing instance with the chosen name
  // is overwritten, otherwise we exit from this function with errer state
  // -2.

  i = 0;
  while ((i < num_dests) &&
         ((strcasecmp(dests[dest_index].name,dests[i].name) != 0) ||
          ((new_name[0] != '\0') &&
           ((!dests[i].instance) ||
            (strcasecmp(new_name,dests[i].instance) != 0))) ||
          ((new_name[0] == '\0') && 
           (dests[i].instance)))) i ++;

  if (new_name[0] == '\0') {
    if ((i < num_dests) &&
        (strcasecmp(dests[dest_index].name,dests[i].name) == 0) &&
        (!dests[i].instance)) {
      if (!fl_ask("Do you really want to overwrite the default instance of \"%s\"?",dests[dest_index].name)) {
        return(-2);
      } else {
        new_index = i;
        overwrite = 1;
      }
    }
  } else {
    if ((i < num_dests) &&
        (strcasecmp(dests[dest_index].name,dests[i].name) == 0) && 
        (strcasecmp(new_name,dests[i].instance) == 0)) {
      if (!fl_ask("The instance \"%s\" for the queue \"%s\" already exists, do you want to overwrite it?",new_name,dests[dest_index].name)) {
        return(-2);
      } else {
        new_index = i;
        overwrite = 1;
      }
    }
  }

  if (overwrite) {

    // Delete options of destination

    cupsFreeOptions(dests[new_index].num_options, dests[new_index].options);
    dests[new_index].num_options = 0;
    dests[new_index].options = NULL;

  } else {

    // Enlarge all the printer data arrays by one

    num_dests ++;
    dests = (cups_dest_t *)realloc(dests, sizeof(cups_dest_t) * num_dests);
    ppds = (ppd_file_t **)realloc(ppds, sizeof(ppd_file_t *) * num_dests);
    special_opts = (special_opt_t **)realloc(special_opts,
                                                 sizeof(special_opt_t *) *
                                                 num_dests);
    num_special_opts = (int *)realloc(num_special_opts,
                                        sizeof(int) * num_dests);
    menu_strs = (char **)realloc(menu_strs, sizeof(char *) * num_dests);
    menu_buttons = (Fl_Round_Button **)realloc(menu_buttons,
                                               sizeof(Fl_Round_Button *) *
                                               num_dests);
    info_lines = (Fl_Box **)realloc(info_lines, sizeof(Fl_Box *) *
                                    num_dests);

    // Move up all destinations after the new one

    for (i = num_dests - 2; i >= new_index; i --) {
      dests[i+1] = dests[i];
      ppds[i+1] = ppds[i];
      special_opts[i+1] = special_opts[i];
      num_special_opts[i+1] = num_special_opts[i];
      menu_strs[i+1] = menu_strs[i];
      menu_buttons[i+1] = menu_buttons[i];
      menu_buttons[i+1]->color(i & 1 ? FL_GRAY : FL_LIGHT2);
      info_lines[i+1] = info_lines[i];
      info_lines[i+1]->color(i & 1 ? FL_GRAY : FL_LIGHT2);
    }
    if (dest_index >= new_index) dest_index ++;

    // Reset the position of all menu widgets

    for (i = 0; i < num_dests; i ++)
      if (i != new_index) {
        menu_buttons[i]->resize(0, (ITEM_HEIGHT+INFO_HEIGHT)*i,
                                0, ITEM_HEIGHT);
        info_lines[i]->resize(0, 
                         (ITEM_HEIGHT+INFO_HEIGHT)*i+ITEM_HEIGHT,
                          0, ITEM_HEIGHT);
      }

    // Insert new instance into the data structures

    dests[new_index].name = dests[dest_index].name;
    if (new_name[0] != '\0') {
      dests[new_index].instance = strdup(new_name);
    } else {
      dests[new_index].instance = NULL;
    }
    dests[new_index].is_default = 0;
    dests[new_index].num_options = 0;
    dests[new_index].options = NULL;
   
    ppds[new_index] = NULL;
    special_opts[new_index] = NULL;
    num_special_opts[new_index] = 0;

    menu_strs[new_index] = (char *)calloc(sizeof(char),128);
    if ((!dests[dest_index].instance) ||
        (dests[dest_index].instance[0] == '\0')) {
      sprintf(buffer,"%s on %s", new_name, menu_strs[dest_index]);
    } else {
      char* s = strstr(menu_strs[dest_index], " on ");
      sprintf(buffer,"%s%s",new_name,s);
    }
    char* s = buffer;
    if (new_name[0] == '\0') s = s + 4;
    strcpy(menu_strs[new_index], s);

    // Insert new entry into the destination menu

    printerPack->begin();
    menu_buttons[new_index]=new Fl_Round_Button
                            (0, (ITEM_HEIGHT+INFO_HEIGHT)*new_index,
                             0, ITEM_HEIGHT, menu_strs[new_index]);
    menu_buttons[new_index]->type(FL_RADIO_BUTTON);
    menu_buttons[new_index]->box(FL_FLAT_BOX);
    menu_buttons[new_index]->color(new_index & 1 ? FL_LIGHT2 : FL_GRAY);
    menu_buttons[new_index]->selection_color(FL_BLACK);
    menu_buttons[new_index]->labelfont(FL_HELVETICA_BOLD);
    menu_buttons[new_index]->clear();
    printerPack->insert(*menu_buttons[new_index], 2*new_index);
    info_lines[new_index] = new Fl_Box(FL_FLAT_BOX,
                     0, (ITEM_HEIGHT+INFO_HEIGHT)*new_index+ITEM_HEIGHT,
                     0, INFO_HEIGHT, 
                     strdup(info_lines[dest_index]->label()));
    info_lines[new_index]->labelsize(10);
    info_lines[new_index]->align(FL_ALIGN_LEFT|FL_ALIGN_TOP|FL_ALIGN_INSIDE);
    info_lines[new_index]->color(new_index & 1 ? FL_LIGHT2 : FL_GRAY);
    printerPack->insert(*info_lines[new_index], 2*new_index+1);
    printerPack->end();

    // Correct position of the mark in the main window

    if (dest_pos >= new_index) dest_pos++;
    dest=dests[dest_pos].name;
    menu_buttons[dest_pos]->setonly();

    // Redraw the destination menu in the main window

    printerPack->parent()->redraw();
    printerPack->draw();
  
  }

  // Copy the options into the data set of the new instance

  for (i = 0; i < dests[dest_index].num_options; i ++)
    dests[new_index].num_options =
      cupsAddOption(
        dests[dest_index].options[i].name,
        dests[dest_index].options[i].value,
        dests[new_index].num_options,
        &(dests[new_index].options));

  // Insert instance in config file ~/.lpoptions or /etc/cups/lpoptions

  int num_stored_dests = 0;
  cups_dest_t *stored_dests = NULL, *new_dest = NULL;

  num_stored_dests = cupsGetDests(&stored_dests);
  if (overwrite) {
    new_dest = cupsGetDest
               (dests[new_index].name, 
                dests[new_index].instance, num_stored_dests, stored_dests);
    cupsFreeOptions(new_dest->num_options, new_dest->options);
    new_dest->num_options = 0;
    new_dest->options = NULL;
  } else {
    num_stored_dests = cupsAddDest(dests[new_index].name, 
                dests[new_index].instance, num_stored_dests, &stored_dests);
    new_dest = cupsGetDest
               (dests[new_index].name, 
                dests[new_index].instance, num_stored_dests, stored_dests);
  }
  new_dest->options = dests[new_index].options;
  new_dest->num_options = dests[new_index].num_options;
  cupsSetDests(num_stored_dests,stored_dests);
  new_dest->options = NULL;
  new_dest->num_options = 0;
  cupsFreeDests(num_stored_dests,stored_dests);

  // Mark options in the PPD file so that the option widgets are displayed
  // correctly

  ppdMarkDefaults(ppds[new_index]);
  cupsMarkOptions(ppds[new_index],
                  dests[new_index].num_options, dests[new_index].options);
  
  if (overwrite) return(-1); else return(new_index);
}

/*
 *  Delete an instance of a destination
 */

int // -1 when no destination entry was removed from the menu, otherwise
    // the index of the removed entry
printFiles::deleteInstance(int dest_index)  // Index of the destination to 
		                            // delete
{
  int   i;              /* Looping var */
  int num_stored_dests = 0;
  cups_dest_t *stored_dests = NULL, *dest_to_delete = NULL;

  // Delete the instance in config file ~/.lpoptions or /etc/cups/lpoptions

  num_stored_dests = cupsGetDests(&stored_dests);
  dest_to_delete = cupsGetDest
                   (dests[dest_index].name, 
                    dests[dest_index].instance,
                    num_stored_dests, stored_dests);
  if (dest_to_delete) {
    cupsFreeOptions(dest_to_delete->num_options, dest_to_delete->options);
    num_stored_dests --;
    i = dest_to_delete - stored_dests;
    if (i < num_stored_dests)
      memcpy(dest_to_delete, dest_to_delete + 1, 
             (num_stored_dests - i) * sizeof(cups_dest_t));
    cupsSetDests(num_stored_dests,stored_dests);
    cupsFreeDests(num_stored_dests,stored_dests);
    num_stored_dests = 0;
    stored_dests = NULL;
  }

  // Load the instance data again and check whether the destination was
  // really deleted. If the destination still exists, it is either a
  // local printer queue or class, it is provided by /etc/cups/lpoptions,
  // or by broadcasting or "BrowsePoll". In this case simply revert the
  // options to the defaults and keep the destination in the menu. Issue
  // a warning to the user and return -1 to say that nothing has changed
  // in the destinations menu of the main window.

  num_stored_dests = cupsGetDests(&stored_dests);
  dest_to_delete = cupsGetDest
                   (dests[dest_index].name, 
                    dests[dest_index].instance,
                    num_stored_dests, stored_dests);
  if (dest_to_delete) {
    cupsFreeOptions(dests[dest_index].num_options, 
                    dests[dest_index].options);
    dests[dest_index].num_options = dest_to_delete->num_options;
    dests[dest_index].options = dest_to_delete->options;
    dest_to_delete->num_options = 0;
    dest_to_delete->options = NULL;
    cupsFreeDests(num_stored_dests,stored_dests);
    ppdMarkDefaults(ppds[dest_index]);
    cupsMarkOptions(ppds[dest_index],
                    dests[dest_index].num_options, 
                    dests[dest_index].options);
    fl_message("Warning: This instance could not be deleted from the destination list because it is provided by the system. Only the options were reverted to the system defaults.");
    return(-1);
  }
  
  // Free memory occupied by the removed entry and take it out of the
  // menu

  printerPack->remove(menu_buttons[dest_index]);
  printerPack->remove(info_lines[dest_index]);
  free(menu_buttons[dest_index]);
  free(info_lines[dest_index]);
  if (ppds[dest_index] != NULL) ppdClose(ppds[dest_index]);
  free(menu_strs[dest_index]);
  free(special_opts[dest_index]);

  // Move down all destinations after the deleted one

  for (i = dest_index; i < num_dests-1; i ++) {
    dests[i] = dests[i+1];
    ppds[i] = ppds[i+1];
    special_opts[i] = special_opts[i+1];
    num_special_opts[i] = num_special_opts[i+1];
    menu_strs[i] = menu_strs[i+1];
    menu_buttons[i] = menu_buttons[i+1];
    menu_buttons[i]->color(i & 1 ? FL_LIGHT2 : FL_GRAY);
    info_lines[i] = info_lines[i+1];
    info_lines[i]->color(i & 1 ? FL_LIGHT2 : FL_GRAY);
  }

  // Reduce all the printer data arrays by one

  num_dests --;
  dests = (cups_dest_t *)realloc(dests, sizeof(cups_dest_t) * num_dests);
  ppds = (ppd_file_t **)realloc(ppds, sizeof(ppd_file_t *) * num_dests);
  special_opts = (special_opt_t **)realloc(special_opts,
                                               sizeof(special_opt_t *) *
                                               num_dests);
  num_special_opts = (int *)realloc(num_special_opts,
                                      sizeof(int) * num_dests);
  menu_strs = (char **)realloc(menu_strs, sizeof(char *) * num_dests);
  menu_buttons = (Fl_Round_Button **)realloc(menu_buttons,
                                             sizeof(Fl_Round_Button *) *
                                             num_dests);
  info_lines = (Fl_Box **)realloc(info_lines, sizeof(Fl_Box *) *
                                  num_dests);

  // Reset the position of all menu widgets

  for (i = 0; i < num_dests; i ++) {
    menu_buttons[i]->resize(0, (ITEM_HEIGHT+INFO_HEIGHT)*i,
                            0, ITEM_HEIGHT);
    info_lines[i]->resize(0, (ITEM_HEIGHT+INFO_HEIGHT)*i+ITEM_HEIGHT,
                          0, ITEM_HEIGHT);
  }

  // Correct position of the mark in the main window

  if ((dest_pos > dest_index) && (dest_pos > 0)) dest_pos --;
  if (dest_pos >= num_dests) dest_pos = num_dests - 1;
  dest=dests[dest_pos].name;
  menu_buttons[dest_pos]->setonly();

  // Redraw the destination menu in the main window

  printerPack->parent()->redraw();
  printerPack->draw();
  
  return(dest_index);
}

/*
 *  Free all memory used for dynamical structures
 */

printFiles::~printFiles()

{
  int		i;		/* Looping var */
  
  // Free all memory and delete all temporary files

  for (i = 0; i < num_dests; i ++) {
    if (ppds[i] != NULL) ppdClose(ppds[i]);
    free(menu_strs[i]);
    free(special_opts[i]);
  }
  free(info_lines);
  free(menu_buttons);
  free(menu_strs);
  free(ppds);
  free(special_opts);
  free(num_special_opts);
  free(dests);
  free(filelist);
}

#ifndef WIN32
/*
 * 'sighandler()' - Signal catcher for when we print from stdin...
 */

void
sighandler(int s)	/* I - Signal number */
{
 /*
  * Remove the temporary file we're using to print from stdin...
  */

  unlink(tempfile);

 /*
  * Exit...
  */

  exit(s);
}
#endif /* !WIN32 */


/*
 * End of "$Id: xpp.cxx,v 1.24 2004/12/06 19:00:04 tillkamppeter Exp $".
 */
