/* xsane -- a graphical (X11, gtk) scanner-oriented SANE frontend

   xsane-front-gtk.c

   Oliver Rauch <Oliver.Rauch@rauch-domain.de>
   Copyright (C) 1998-2002 Oliver Rauch
   This file is part of the XSANE package.

   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 "xsane.h"
#include "xsane-back-gtk.h"
#include "xsane-preferences.h"
#include "xsane-preview.h"
#include "xsane-save.h"
#include "xsane-gamma.h"
#include "xsane-setup.h"
#include <md5.h>

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
#include <png.h>
#include <zlib.h>
#endif
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

/* forward declarations: */

int xsane_parse_options(char *options, char *argv[]);
void xsane_get_bounds(const SANE_Option_Descriptor *opt, double *minp, double *maxp);
double xsane_find_best_resolution(int well_known_option, double dpi);
int xsane_set_resolution(int well_known_option, double resolution);
void xsane_set_all_resolutions(void);
void xsane_define_maximum_output_size();
void xsane_close_dialog_callback(GtkWidget *widget, gpointer data);
void xsane_authorization_button_callback(GtkWidget *widget, gpointer data);
gint xsane_authorization_callback(SANE_String_Const resource,
                                  SANE_Char username[SANE_MAX_USERNAME_LEN],
                                  SANE_Char password[SANE_MAX_PASSWORD_LEN]);
void xsane_progress_cancel(GtkWidget *widget, gpointer data);
void xsane_progress_new(char *bar_text, char *info, GtkSignalFunc callback, int *cancel_data_pointer);
void xsane_progress_update(gfloat newval);
void xsane_progress_clear();
GtkWidget *xsane_vendor_pixmap_new(GdkWindow *window, GtkWidget *parent);
GtkWidget *xsane_toggle_button_new_with_pixmap(GdkWindow *window, GtkWidget *parent, const char *xpm_d[], const char *desc,
                                         int *state, void *xsane_toggle_button_callback);
GtkWidget *xsane_button_new_with_pixmap(GdkWindow *window, GtkWidget *parent, const char *xpm_d[], const char *desc,
                                        void *xsane_button_callback, gpointer data);
void xsane_option_menu_new(GtkWidget *parent, char *str_list[], const char *val, int option_number, const char *desc,
                           void *option_menu_callback, SANE_Int settable, const gchar *widget_name);
void xsane_option_menu_new_with_pixmap(GdkWindow *window, GtkBox *parent, const char *xpm_d[], const char *desc,
                                       char *str_list[], const char *val,
                                       GtkObject **data, int option,
                                       void *option_menu_callback, SANE_Int settable, const gchar *widget_name);
void xsane_range_new(GtkBox *parent, char *labeltext, const char *desc,
                     float min, float max, float quant, float page_step,
                     int digits, double *val, GtkObject **data, void *xsane_range_callback, SANE_Int settable);
void xsane_range_new_with_pixmap(GdkWindow *window, GtkBox *parent, const char *xpm_d[], const char *desc,
                                 float min, float max, float quant, float page_step, int digits,
                                 double *val, GtkObject **data, int option, void *xsane_range_callback, SANE_Int settable);
static void xsane_outputfilename_changed_callback(GtkWidget *widget, gpointer data);
void xsane_set_filename(gchar *filename);
void xsane_separator_new(GtkWidget *xsane_parent, int dist);
GtkWidget *xsane_info_table_text_new(GtkWidget *table, gchar *text, int row, int colomn);
GtkWidget *xsane_info_text_new(GtkWidget *parent, gchar *text);
void xsane_refresh_dialog(void);
void xsane_set_sensitivity(SANE_Int sensitivity);
void xsane_update_param(void *arg);
void xsane_define_output_filename(void);
int xsane_identify_output_format(char *filename, char **ext);

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_parse_options(char *options, char *argv[])
{
 int optpos = 0;
 int bufpos = 0;
 int arg    = 0;
 char buf[256];
 
  DBG(DBG_proc, "xsane_parse_options\n");
 
  while (options[optpos] != 0)
  {
    switch(options[optpos])
    {
      case ' ':
        buf[bufpos] = 0;
        argv[arg++] = strdup(buf);
        bufpos = 0;
        optpos++;
       break;
 
      case '\"':
        optpos++; /* skip " */
        while ((options[optpos] != 0) && (options[optpos] != '\"'))
        {
          buf[bufpos++] = options[optpos++];
        }
        optpos++; /* skip " */
       break;
 
      default:
        buf[bufpos++] = options[optpos++];
       break;
    }
  }
  buf[bufpos] = 0;
  argv[arg++] = strdup(buf);

 return arg;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_get_bounds(const SANE_Option_Descriptor *opt, double *minp, double *maxp)
{
 double min, max;
 int i;

  DBG(DBG_proc, "xsane_get_bounds\n");

  min = -INF;
  max =  INF;
  switch (opt->constraint_type)
  {
    case SANE_CONSTRAINT_RANGE:
      min = opt->constraint.range->min;
      max = opt->constraint.range->max;
      break;

    case SANE_CONSTRAINT_WORD_LIST:
      min =  INF;
      max = -INF;

      for (i = 1; i <= opt->constraint.word_list[0]; ++i)
      {
        if (opt->constraint.word_list[i] < min)
        {
          min = opt->constraint.word_list[i];
        }
        if (opt->constraint.word_list[i] > max)
        {
          max = opt->constraint.word_list[i];
        }
      }
     break;

    default:
     break;
  }

  if (opt->type == SANE_TYPE_FIXED)
  {
    if (min > -INF && min < INF)
    {
      min = SANE_UNFIX (min);
    }
    if (max > -INF && max < INF)
    {
      max = SANE_UNFIX (max);
    }
  }
  *minp = min;
  *maxp = max;
}                                                        

/* ---------------------------------------------------------------------------------------------------------------------- */

double xsane_find_best_resolution(int well_known_option, double dpi)
{
 const SANE_Option_Descriptor *opt;
 double bestdpi;

  DBG(DBG_proc, "xsane_find_best_resolution\n");

  opt = xsane_get_option_descriptor(xsane.dev, well_known_option);

  if (!opt)
  {
    return -1.0; /* option does not exits */
  }

  if (opt->constraint_type == SANE_CONSTRAINT_RANGE)
  {
   double quant=0;
   double min=0;
   double max=0;

    switch (opt->type)
    {
      case SANE_TYPE_INT:
        min   = opt->constraint.range->min;
        max   = opt->constraint.range->max;
        quant = opt->constraint.range->quant;
      break;

      case SANE_TYPE_FIXED:
        min   = SANE_UNFIX(opt->constraint.range->min);
        max   = SANE_UNFIX(opt->constraint.range->max);
        quant = SANE_UNFIX(opt->constraint.range->quant);
      break;

      default:
        DBG(DBG_error, "find_best_resolution: %s %d\n", ERR_UNKNOWN_TYPE, opt->type);
    }

    bestdpi = dpi;

    if (quant != 0) /* make sure selected value fits into quantisation */
    {
     int factor;
     double diff;

      factor = (int) (dpi - min) / quant;

      diff = dpi - min - factor * quant;
      bestdpi = min + factor * quant;

      if (diff >quant/2.0)
      {
        bestdpi += quant;
      }
    }

    if (bestdpi < min)
    {
      bestdpi = min;
    }

    if (bestdpi > max)
    {
      bestdpi = max;
    }
  }
  else if (opt->constraint_type == SANE_CONSTRAINT_WORD_LIST)
  {
   SANE_Word diff;
   SANE_Word val;
   int items;
   int i;

    items = opt->constraint.word_list[0];

    bestdpi = opt->constraint.word_list[1];
    if (opt->type == SANE_TYPE_FIXED)
    {
      bestdpi = SANE_UNFIX(bestdpi);
    }

    diff = abs(bestdpi - dpi);

    for (i=1; i<=items; i++)
    {
      val = opt->constraint.word_list[i];
      if (opt->type == SANE_TYPE_FIXED)
      {
        val = SANE_UNFIX(val);
      }

      if (abs(val - dpi) < diff)
      {
        diff = abs(val - dpi);
        bestdpi = val;
      }
    }
  }
  else
  {
    DBG(DBG_error, "find_best_resolution: %s %d\n", ERR_UNKNOWN_CONSTRAINT_TYPE, opt->constraint_type);
    return -1; /* error */
  }

  return bestdpi;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_set_resolution(int well_known_option, double resolution)
{
 const SANE_Option_Descriptor *opt;
 double bestdpi;
 SANE_Word dpi;

  DBG(DBG_proc, "xsane_set_resolution\n");

  opt = xsane_get_option_descriptor(xsane.dev, well_known_option);

  if (!opt)
  {
    return -1; /* option does not exits */
  }

  if (!SANE_OPTION_IS_ACTIVE(opt->cap))
  {
    return -1; /* option is not active */
  }

  bestdpi = xsane_find_best_resolution(well_known_option, resolution);

  if (bestdpi < 0)
  {
     DBG(DBG_error, "set_resolution: %s\n", ERR_FAILED_SET_RESOLUTION);
    return -1;
  }

  switch (opt->type)
  {
    case SANE_TYPE_INT:
      dpi = bestdpi;
    break;

    case SANE_TYPE_FIXED:
      dpi = SANE_FIX(bestdpi);
    break;

    default:
     DBG(DBG_error, "set_resolution: %s %d\n", ERR_UNKNOWN_TYPE, opt->type);
    return 1; /* error */
  }

  /* it makes problems to call xsane_back_gtk_set_option. This would allow a */
  /* panel_rebuild that can mess up a lot at this place*/
  xsane_control_option(xsane.dev, well_known_option, SANE_ACTION_SET_VALUE, &dpi, 0);

 return 0; /* everything is ok */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_all_resolutions(void)
{
 int printer_resolution;

  DBG(DBG_proc, "xsane_set_all_resolutions\n");

  if (xsane_set_resolution(xsane.well_known.dpi_y, xsane.resolution_y)) /* set y resolution if possible */
  {
    xsane_set_resolution(xsane.well_known.dpi, xsane.resolution); /* set common resolution if necessary */
    xsane.resolution_x = xsane.resolution;
    xsane.resolution_y = xsane.resolution;
  }
  else
  {
    xsane_set_resolution(xsane.well_known.dpi_x, xsane.resolution_x); /* set x resolution if possible */
  }

  switch (xsane.param.format)
  {
    case SANE_FRAME_GRAY:
      if (xsane.param.depth == 1)
      {
        printer_resolution = preferences.printer[preferences.printernr]->lineart_resolution;
      }
      else
      {
        printer_resolution = preferences.printer[preferences.printernr]->grayscale_resolution;
      }
    break;

    case SANE_FRAME_RGB:
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
    default:
      printer_resolution = preferences.printer[preferences.printernr]->color_resolution;
    break;
  }

  xsane.zoom   = xsane.resolution   / printer_resolution;
  xsane.zoom_x = xsane.resolution_x / printer_resolution;
  xsane.zoom_y = xsane.resolution_y / printer_resolution; 
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_define_maximum_output_size()
{
 const SANE_Option_Descriptor *opt;

  DBG(DBG_proc, "xsane_define_maximum_output_size\n");

  opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.coord[0]);

  if ( (opt) && (opt->unit== SANE_UNIT_MM) )
  {
    switch(xsane.xsane_mode)
    {
      case XSANE_SAVE:

        xsane_define_output_filename();
        xsane.xsane_output_format = xsane_identify_output_format(xsane.output_filename, 0);

        preview_set_maximum_output_size(xsane.preview, INF, INF);
       break;

      case XSANE_VIEWER:
        preview_set_maximum_output_size(xsane.preview, INF, INF);
       break;

      case XSANE_COPY:
        if (preferences.psrotate) /* rotate: landscape */
        {
          preview_set_maximum_output_size(xsane.preview,
                                          preferences.printer[preferences.printernr]->height / xsane.zoom_y,
                                          preferences.printer[preferences.printernr]->width  / xsane.zoom_x);
        }
        else /* do not rotate: portrait */
        {
          preview_set_maximum_output_size(xsane.preview,
                                          preferences.printer[preferences.printernr]->width  / xsane.zoom_x,
                                          preferences.printer[preferences.printernr]->height / xsane.zoom_y);
        }
       break;

      case XSANE_FAX:
        preview_set_maximum_output_size(xsane.preview, preferences.fax_width, preferences.fax_height);
       break;

      case XSANE_MAIL:
        preview_set_maximum_output_size(xsane.preview, INF, INF);
       break;

      default:
        preview_set_maximum_output_size(xsane.preview, INF, INF);
    }
  }
  else
  {
    preview_set_maximum_output_size(xsane.preview, INF, INF);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_close_dialog_callback(GtkWidget *widget, gpointer data)
{
 GtkWidget *dialog_widget = data;

  DBG(DBG_proc, "xsane_close_dialog_callback\n");

  gtk_widget_destroy(dialog_widget);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int authorization_flag;

void xsane_authorization_button_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_authorization_button_callback\n");

  authorization_flag = (long) data;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_authorization_callback(SANE_String_Const resource,
                                  SANE_Char username[SANE_MAX_USERNAME_LEN],
                                  SANE_Char password[SANE_MAX_PASSWORD_LEN])
{
 GtkWidget *authorize_dialog, *vbox, *hbox, *button, *label;
 GtkWidget *username_widget, *password_widget;
 char buf[SANE_MAX_PASSWORD_LEN+SANE_MAX_USERNAME_LEN+128];
 char *input;
 char *resource_string;
 int len;
 int resource_len;
 int secure_password_transmission = 0;
 char password_filename[PATH_MAX];
 struct stat password_stat;
 FILE *password_file;
 unsigned char md5digest[16];
 int query_user = 1;

  DBG(DBG_proc, "xsane_authorization_callback\n");

  if (strstr(resource, "$MD5$") != NULL) /* secure password authorisation */
  {
    DBG(DBG_info, "xsane_authorization_callback: secure (MD5) password transmission requested\n");
    secure_password_transmission = 1;
    resource_len = strstr(resource, "$MD5$") - resource;
  }
  else
  {
    DBG(DBG_info, "xsane_authorization_callback: insecure password transmission requested\n");
    resource_len = strlen(resource);
  }
  resource_string = alloca(resource_len+1);
  snprintf(resource_string, resource_len+1, "%s", resource);

  xsane_back_gtk_make_path(sizeof(password_filename), password_filename, NULL, NULL, "pass", NULL, NULL, XSANE_PATH_LOCAL_SANE);

  /* if password transmission is secure and file ~/.sane/pass exists and it's permissions are x00 then
     try to read username and pasword for resource from that file */
  if ((stat(password_filename, &password_stat) == 0) && (secure_password_transmission))
  {
    if ((password_stat.st_mode & 63) != 0) /* 63 = 0x077 */
    {
      snprintf(buf, sizeof(buf), ERR_PASSWORD_FILE_INSECURE, password_filename);
      xsane_back_gtk_error(buf, TRUE);
    }
    else /* ok, password file has secure permissions, we can use it */
    {
      password_file = fopen(password_filename, "r");

      if (password_file)
      {
        DBG(DBG_info, "xsane authorization: opened %s as password file\n", password_filename);
        /* file format: "username:password:resource" */
        while (fgets(buf, sizeof(buf), password_file))
        {
         char *stored_username;
         char *stored_password;
         char *stored_resource;
         char *marker;

          marker = strrchr(buf, '\n');
          if (marker)
          {
            *marker = 0; /* remove \n at end of read line */
          }

          marker = strrchr(buf, '\r');
          if (marker)
          {
            *marker = 0; /* remove \r at end of read line (eg for windows file) */
          }

          marker = strchr(buf, ':');
          if (marker)
          {
            stored_username = buf;
            *marker = 0; /* set \0 to end of stored_username */
            stored_password = marker + 1;

            marker = strchr(stored_password, ':');
            if (marker)
            {
              *marker = 0; /* set \0 to end of stored_password */
              stored_resource = marker + 1;

              if (strcmp(stored_resource, resource_string) == 0) /* password file resource equals requested resource */
              {
                strcpy(username, stored_username);
                strcpy(password, stored_password);
                query_user = 0;
              }
            }
          }
        }

        fclose(password_file);
      }
      else
      {
        DBG(DBG_info, "xsane authorization: could not open existing password file %s\n", password_filename);
      }
    }
  }
  else
  {
    DBG(DBG_info, "xsane authorization: password file %s does not exist\n", password_filename);
  }

  if (query_user)
  {
    authorize_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(authorize_dialog), GTK_WIN_POS_CENTER);
    gtk_window_set_resizable(GTK_WINDOW(authorize_dialog), FALSE);
    g_signal_connect(GTK_OBJECT(authorize_dialog), "delete_event", GTK_SIGNAL_FUNC(xsane_authorization_button_callback), (void *) -1); /* -1 = cancel */
    snprintf(buf, sizeof(buf), "%s %s", xsane.prog_name, WINDOW_AUTHORIZE);
    gtk_window_set_title(GTK_WINDOW(authorize_dialog), buf);
    xsane_set_window_icon(authorize_dialog, 0);

    vbox = gtk_vbox_new(/* not homogeneous */ FALSE, 10); /* y-space between all box items */
    gtk_container_add(GTK_CONTAINER(authorize_dialog), vbox);
    gtk_widget_show(vbox);

    /* print resourece string */
    snprintf(buf, sizeof(buf), "\n\n%s %s", TEXT_AUTHORIZATION_REQ, resource_string);
    label = gtk_label_new(buf);
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); /* y-space around authorization text */
    gtk_widget_show(label);

    /* print securety of password transmission */
    if (secure_password_transmission)
    {
      snprintf(buf, sizeof(buf), "%s\n", TEXT_AUTHORIZATION_SECURE);
    }
    else
    {
      snprintf(buf, sizeof(buf), "%s\n", TEXT_AUTHORIZATION_INSECURE);
    }
    label = gtk_label_new(buf);
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); /* y-space around authorization text */
    gtk_widget_show(label);

    /* ask for username */
    hbox = gtk_hbox_new(FALSE, 10); /* x-space between label and input filed */
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); /* y-space around inner items */

    label = gtk_label_new(TEXT_USERNAME);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10); /* x-space around label */
    gtk_widget_show(label);

    username_widget = gtk_entry_new_with_max_length(SANE_MAX_USERNAME_LEN-1);
    gtk_widget_set_size_request(username_widget, 250, -1);
    gtk_entry_set_text(GTK_ENTRY(username_widget), "");
    gtk_box_pack_end(GTK_BOX(hbox), username_widget, FALSE, FALSE, 10); /* x-space around input filed */
    gtk_widget_show(username_widget);
    gtk_widget_show(hbox);


    /* ask for password */
    hbox = gtk_hbox_new(FALSE, 10); /* x-space between label and input filed */
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); /* y-space around inner items */

    label = gtk_label_new(TEXT_PASSWORD);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10); /* x-space around label */
    gtk_widget_show(label);

    password_widget = gtk_entry_new_with_max_length(SANE_MAX_PASSWORD_LEN-1);
    gtk_entry_set_visibility(GTK_ENTRY(password_widget), FALSE); /* make entered text invisible */
    gtk_widget_set_size_request(password_widget, 250, -1);
    gtk_entry_set_text(GTK_ENTRY(password_widget), "");
    gtk_box_pack_end(GTK_BOX(hbox), password_widget, FALSE, FALSE, 10); /* x-space around input filed */
    gtk_widget_show(password_widget);
    gtk_widget_show(hbox);

    /* buttons */
    hbox = gtk_hbox_new(TRUE, 10); /* x-space between buttons */
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 10);  /* y-space around buttons */

#ifdef HAVE_GTK2
    button = gtk_button_new_from_stock(GTK_STOCK_OK);
#else
    button = gtk_button_new_with_label(BUTTON_OK);
#endif
    GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
    g_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(xsane_authorization_button_callback), (void *) 1);
    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 10); /* x-space around OK-button */
    gtk_widget_grab_default(button);
    gtk_widget_show(button);

#ifdef HAVE_GTK2
    button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
#else
    button = gtk_button_new_with_label(BUTTON_CANCEL);
#endif
    g_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(xsane_authorization_button_callback), (void *) -1);
    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 10); /* x-space around cancel-button */
    gtk_widget_show(button);

    gtk_widget_show(hbox);

    gtk_widget_show(authorize_dialog);


    username[0]=0;
    password[0]=0;

    authorization_flag = 0;

    /* wait for ok or cancel */
    while (authorization_flag == 0)
    {
      gtk_main_iteration();
    }

    if (authorization_flag == 1) /* 1=ok, -1=cancel */
    {
      input = gtk_entry_get_text(GTK_ENTRY(username_widget));
      len = strlen(input);
      memcpy(username, input, len);
      username[len] = 0;

      input = gtk_entry_get_text(GTK_ENTRY(password_widget));
      len = strlen(input);
      memcpy(password, input, len);
      password[len] = 0;
    }
    gtk_widget_destroy(authorize_dialog);
  }

  if (secure_password_transmission)
  {
    DBG(DBG_info, "xsane authorization: calculating md5digest of password\n");

    snprintf(buf, sizeof(buf), "%s%s", (strstr(resource, "$MD5$")) + 5, password); /* random string from backend + password */
    md5_buffer(buf, strlen(buf), md5digest); /* calculate md5digest */
#if 0 /* makes problems with WIN32 */
    memset(password, 0, SANE_MAX_PASSWORD_LEN); /* clear password */
#endif

    sprintf(password, "$MD5$%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
            md5digest[0],  md5digest[1],  md5digest[2],  md5digest[3],
            md5digest[4],  md5digest[5],  md5digest[6],  md5digest[7],
            md5digest[8],  md5digest[9],  md5digest[10], md5digest[11],
            md5digest[12], md5digest[13], md5digest[14], md5digest[15]);
  }         

  return TRUE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_progress_cancel(GtkWidget *widget, gpointer data)
{
 void *cancel_data_pointer;
 GtkFunction callback = (GtkFunction) data;

  DBG(DBG_proc, "xsane_progress_cancel\n");

  cancel_data_pointer = gtk_object_get_data(GTK_OBJECT(widget), "progress-cancel-data-pointer");

  (callback)(cancel_data_pointer);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_progress_new(char *bar_text, char *info, GtkSignalFunc callback, int *cancel_data_pointer)
{
  DBG(DBG_proc, "xsane_progress_new\n");

  gtk_label_set(GTK_LABEL(xsane.info_label), info);
  gtk_progress_set_format_string(GTK_PROGRESS(xsane.progress_bar), bar_text); 
  gtk_progress_bar_update(GTK_PROGRESS_BAR(xsane.progress_bar), 0.0);
  gtk_widget_set_sensitive(GTK_WIDGET(xsane.cancel_button), TRUE);
  gtk_object_set_data(GTK_OBJECT(xsane.cancel_button), "progress-cancel-data-pointer", cancel_data_pointer);
  g_signal_connect(GTK_OBJECT(xsane.cancel_button), "clicked", (GtkSignalFunc) xsane_progress_cancel, callback);
  xsane.cancel_callback = callback;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_progress_clear()
{
  DBG(DBG_proc, "xsane_progress_clear\n");

  /* do not call xsane_update_param() here because it overrites the good scanning parameters with bad guessed ones */
  gtk_progress_set_format_string(GTK_PROGRESS(xsane.progress_bar), ""); 
  gtk_progress_bar_update(GTK_PROGRESS_BAR(xsane.progress_bar), 0.0);
  gtk_widget_set_sensitive(GTK_WIDGET(xsane.cancel_button), FALSE);

  if (xsane.cancel_callback)
  {
    g_signal_handlers_disconnect_by_func(GTK_OBJECT(xsane.cancel_button), (GtkSignalFunc) xsane_progress_cancel, xsane.cancel_callback);
    xsane.cancel_callback = 0;
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_progress_update(gfloat newval)
{
  DBG(DBG_proc, "xsane_progress_update\n");

  if (newval < 0.0)
  {
    newval = 0.0;
  }

  if (newval > 1.0)
  {
    newval = 1.0;
  }

  gtk_progress_bar_update(GTK_PROGRESS_BAR(xsane.progress_bar), newval);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* add the scanner vendorīs logo if available */
/* when the logo is not available use the xsane logo */
GtkWidget *xsane_vendor_pixmap_new(GdkWindow *window, GtkWidget *parent)
{
 char filename[PATH_MAX];
 GtkWidget *hbox, *vbox;
 GtkWidget *pixmapwidget = NULL;
 GdkBitmap *mask;
 GdkPixmap *pixmap = NULL;

  if (xsane.devlist[xsane.selected_dev]->vendor)
  {
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", NULL, NULL, xsane.devlist[xsane.selected_dev]->vendor, "-logo.xpm", XSANE_PATH_SYSTEM);
    pixmap = gdk_pixmap_create_from_xpm(window, &mask, xsane.bg_trans, filename);
  }

  if (!pixmap) /* vendor logo not available, use backend logo */
  {
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", NULL, NULL, xsane.backend, "-logo.xpm", XSANE_PATH_SYSTEM);
    pixmap = gdk_pixmap_create_from_xpm(window, &mask, xsane.bg_trans, filename);
  }

  if (!pixmap) /* backend logo not available, use xsane logo */
  {
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", NULL, NULL, "sane-xsane", "-logo.xpm", XSANE_PATH_SYSTEM);
    pixmap = gdk_pixmap_create_from_xpm(window, &mask, xsane.bg_trans, filename);
  }

  if (pixmap) /* ok, we have a pixmap, so letīs show it */
  {
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_box_pack_end(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
    gtk_widget_show(vbox);   

    hbox = gtk_hbox_new(TRUE /* homogeneous */ , 2);
    gtk_container_add(GTK_CONTAINER(vbox), hbox);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), 0);
    gtk_widget_show(hbox);

    pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask); /* now add the pixmap */
    gtk_box_pack_start(GTK_BOX(hbox), pixmapwidget, FALSE /* expand */, FALSE /* fill */, 2);
    gdk_drawable_unref(pixmap);
    gtk_widget_show(pixmapwidget);
  }

 return pixmapwidget;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_toggle_button_new_with_pixmap(GdkWindow *window, GtkWidget *parent, const char *xpm_d[], const char *desc,
                                         int *state, void *xsane_toggle_button_callback)
{
 GtkWidget *button;
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;

  DBG(DBG_proc, "xsane_toggle_button_new_with_pixmap\n");

  button = gtk_toggle_button_new();
  xsane_back_gtk_set_tooltip(xsane.tooltips, button, desc);

  pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, xsane.bg_trans, (gchar **) xpm_d);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_container_add(GTK_CONTAINER(button), pixmapwidget);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);

  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), *state);
  g_signal_connect(GTK_OBJECT(button), "toggled", (GtkSignalFunc) xsane_toggle_button_callback, (GtkObject *) state);
  gtk_box_pack_start(GTK_BOX(parent), button, FALSE, FALSE, 0);
  gtk_widget_show(button);

 return button;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_button_new_with_pixmap(GdkWindow *window, GtkWidget *parent, const char *xpm_d[], const char *desc,
                                  void *xsane_button_callback, gpointer data)
{
 GtkWidget *button;
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;

  DBG(DBG_proc, "xsane_button_new_with_pixmap\n");

  button = gtk_button_new();
  xsane_back_gtk_set_tooltip(xsane.tooltips, button, desc);

  pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, xsane.bg_trans, (gchar **) xpm_d);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_container_add(GTK_CONTAINER(button), pixmapwidget);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);

  if (xsane_button_callback)
  {
    g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_button_callback, data);
  }
  gtk_box_pack_start(GTK_BOX(parent), button, FALSE, FALSE, 0);
  gtk_widget_show(button);

  return(button);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_option_menu_lookup(GSGMenuItem menu_items[], const char *string)
{
 int i;

  DBG(DBG_proc, "xsane_option_menu_lookup\n");

  for (i = 0; strcmp(menu_items[i].label, string) != 0; ++i);
  return i;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_option_menu_callback(GtkWidget *widget, gpointer data)
{
 GSGMenuItem *menu_item = data;
 GSGDialogElement *elem = menu_item->elem;
 const SANE_Option_Descriptor *opt;
 int opt_num;
 double dval;
 SANE_Word val;
 void *valp = &val;

  DBG(DBG_proc, "xsane_option_menu_callback\n");

  opt_num = elem - xsane.element;
  opt = xsane_get_option_descriptor(xsane.dev, opt_num);
  switch (opt->type)
  {
    case SANE_TYPE_INT:
      sscanf(menu_item->label, "%d", &val);
      break;

    case SANE_TYPE_FIXED:
      sscanf(menu_item->label, "%lg", &dval);
      val = SANE_FIX(dval);
      break;

    case SANE_TYPE_STRING:
      valp = menu_item->label;
      break;

    default:
      DBG(DBG_error, "xsane_option_menu_callback: %s %d\n", ERR_UNKNOWN_TYPE, opt->type);
      break;
  }
  xsane_back_gtk_set_option(opt_num, valp, SANE_ACTION_SET_VALUE);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_option_menu_new(GtkWidget *parent, char *str_list[], const char *val, int option_number, const char *desc,
                           void *option_menu_callback, SANE_Int settable, const gchar *widget_name)
{
 GtkWidget *option_menu, *menu, *item;
 GSGMenuItem *menu_items;
 GSGDialogElement *elem;
 int i, num_items;

  DBG(DBG_proc, "xsane_option_menu_new\n");

  elem = xsane.element + option_number;

  for (num_items = 0; str_list[num_items]; ++num_items);
  menu_items = malloc(num_items * sizeof(menu_items[0]));

  menu = gtk_menu_new();
  if (widget_name)
  {
    gtk_widget_set_name(menu, widget_name);
  }

  for (i = 0; i < num_items; ++i)
  {
    item = gtk_menu_item_new_with_label(_BGT(str_list[i]));
    gtk_container_add(GTK_CONTAINER(menu), item);

    if (option_menu_callback)
    {
      g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) option_menu_callback, menu_items + i);
    }
    else
    {
      g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_option_menu_callback, menu_items + i);
    }

    gtk_widget_show(item);

    menu_items[i].label = str_list[i];
    menu_items[i].elem  = elem;
    menu_items[i].index = i;
  }

  option_menu = gtk_option_menu_new();
  xsane_back_gtk_set_tooltip(xsane.tooltips, option_menu, desc);
  gtk_box_pack_end(GTK_BOX(parent), option_menu, FALSE, FALSE, 2);
  gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu);
  gtk_option_menu_set_history(GTK_OPTION_MENU(option_menu), xsane_option_menu_lookup(menu_items, val));

  gtk_widget_show(option_menu);

  gtk_widget_set_sensitive(option_menu, settable);

  elem->widget  = option_menu;
  elem->menu_size = num_items;
  elem->menu = menu_items;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_option_menu_new_with_pixmap(GdkWindow *window, GtkBox *parent, const char *xpm_d[], const char *desc,
                                       char *str_list[], const char *val,
                                       GtkObject **data, int option,
                                       void *option_menu_callback, SANE_Int settable, const gchar *widget_name)
{
 GtkWidget *hbox;
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;

  DBG(DBG_proc, "xsane_option_menu_new_with_pixmap\n");

  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(parent, hbox, FALSE, FALSE, 0);

  pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, xsane.bg_trans, (gchar **) xpm_d);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(hbox), pixmapwidget, FALSE, FALSE, 2);
  gtk_widget_show(pixmapwidget);

  xsane_option_menu_new(hbox, str_list, val, option, desc, option_menu_callback, settable, widget_name);
  gtk_widget_show(hbox);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_range_display_value_right_callback(GtkAdjustment *adjust, gpointer data)
{
 gchar buf[256];
 int digits = (int) data;
 GtkLabel *label;

  snprintf(buf, sizeof(buf), "%1.*f", digits, adjust->value);
  label = (GtkLabel *) gtk_object_get_data(GTK_OBJECT(adjust), "value-label"); 
  gtk_label_set_text(label, buf);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_range_new(GtkBox *parent, char *labeltext, const char *desc,
                     float min, float max, float quant, float page_step,
                     int digits, double *val, GtkObject **data, void *xsane_range_callback, SANE_Int settable)
{
 GtkWidget *hbox;
 GtkWidget *label;
 GtkWidget *slider = NULL;
 GtkWidget *spinbutton;
 GtkWidget *value_label;

  DBG(DBG_proc, "xsane_slider_new\n");

  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(parent, hbox, FALSE, FALSE, 2);

  label = gtk_label_new(labeltext);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 1);

  *data = gtk_adjustment_new(*val, min, max, quant, page_step, (max-min) * 1e-40);
  /* 1e-40 => hscrollbar has an unwanted side effect: the maximum is not the maximum */
  /* of the given range, it is reduced by the page_size, so it has to be very small */

  /* value label */
  if (preferences.show_range_mode & 8)
  {
    value_label = gtk_label_new("");
    gtk_widget_set_size_request(value_label, 35, -1);
    gtk_box_pack_end(GTK_BOX(hbox), value_label, FALSE, FALSE, 1);
    
    g_signal_connect(*data, "value_changed", (GtkSignalFunc) xsane_range_display_value_right_callback, (void *) digits);
    gtk_object_set_data(GTK_OBJECT(*data), "value-label", value_label); 
    g_signal_emit_by_name(GTK_OBJECT(*data), "value_changed"); /* update value */
    gtk_widget_show(value_label);
    gtk_widget_set_sensitive(value_label, settable);  
  }

  /* spinbutton */
  if (preferences.show_range_mode & 4)
  {
    spinbutton = gtk_spin_button_new(GTK_ADJUSTMENT(*data), 0, digits);
    if (preferences.show_range_mode & 3) /* slider also visible */
    {
      gtk_widget_set_size_request(spinbutton, 60, -1);
    }
    else /* slider not visible */
    {
      gtk_widget_set_size_request(spinbutton, 100, -1);
    }
    xsane_back_gtk_set_tooltip(xsane.tooltips, spinbutton, desc);
    gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(spinbutton), FALSE);
    gtk_box_pack_end(GTK_BOX(hbox), spinbutton, FALSE, FALSE, 5); /* make spinbutton not sizeable */
    gtk_widget_show(spinbutton);
    gtk_widget_set_sensitive(spinbutton, settable);  
  }

  /* slider */
  if (preferences.show_range_mode & 3)
  {
    if (preferences.show_range_mode & 1) /* bit 0 (val 1) : scale */
    {
      slider = gtk_hscale_new(GTK_ADJUSTMENT(*data));
      gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE);
      gtk_scale_set_digits(GTK_SCALE(slider), digits);
    }
    else /* bit 1 (val 2) : scrollbar */
    {
      slider = gtk_hscrollbar_new(GTK_ADJUSTMENT(*data));
    }
    xsane_back_gtk_set_tooltip(xsane.tooltips, slider, desc);
    gtk_widget_set_size_request(slider, 180, -1);
    /* GTK_UPDATE_CONTINUOUS, GTK_UPDATE_DISCONTINUOUS, GTK_UPDATE_DELAYED */
    gtk_range_set_update_policy(GTK_RANGE(slider), preferences.gtk_update_policy);
    gtk_box_pack_end(GTK_BOX(hbox), slider, FALSE, FALSE, 5); /* make slider not sizeable */
    gtk_widget_show(slider);
    gtk_widget_set_sensitive(slider, settable);  
  }

  if (xsane_range_callback)
  {
    g_signal_connect(*data, "value_changed", (GtkSignalFunc) xsane_range_callback, val);
  }

  gtk_widget_show(label);
  gtk_widget_show(hbox);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_range_new_with_pixmap(GdkWindow *window, GtkBox *parent, const char *xpm_d[], const char *desc,
                                 float min, float max, float quant, float page_step,
                                 int digits, double *val, GtkObject **data, int option, void *xsane_range_callback, SANE_Int settable)
{
 GtkWidget *hbox;
 GtkWidget *slider = NULL;
 GtkWidget *spinbutton;
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;
 GtkWidget *value_label;

  DBG(DBG_proc, "xsane_slider_new_with_pixmap\n");

  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(parent, hbox, FALSE, FALSE, 1);

  pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, xsane.bg_trans, (gchar **) xpm_d);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(hbox), pixmapwidget, FALSE, FALSE, 2);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);

  *data = gtk_adjustment_new(*val, min, max, quant, page_step, (max-min) * 1e-40);
  /* 1e-40 => hscrollbar has an unwanted side effect: the maximum is not the maximum */
  /* of the given range, it is reduced by the page_size, so it has to be very small */

  /* value label */
  if (preferences.show_range_mode & 8)
  {
    value_label = gtk_label_new("");
    gtk_widget_set_size_request(value_label, 35, -1);
    gtk_box_pack_end(GTK_BOX(hbox), value_label, FALSE, FALSE, 1);
    
    g_signal_connect(*data, "value_changed", (GtkSignalFunc) xsane_range_display_value_right_callback, (void *) digits);
    gtk_object_set_data(GTK_OBJECT(*data), "value-label", value_label); 
    g_signal_emit_by_name(GTK_OBJECT(*data), "value_changed"); /* update value */
    gtk_widget_show(value_label);
    gtk_widget_set_sensitive(value_label, settable);  
  }

  /* spinbutton */
  if (preferences.show_range_mode & 4)
  {
    spinbutton = gtk_spin_button_new(GTK_ADJUSTMENT(*data), 0, digits);
    gtk_widget_set_size_request(spinbutton, 60, -1);
    xsane_back_gtk_set_tooltip(xsane.tooltips, spinbutton, desc);
    gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(spinbutton), FALSE);
    if (preferences.show_range_mode & 3) /* slider also visible */
    {
      gtk_box_pack_end(GTK_BOX(hbox), spinbutton, FALSE, FALSE, 5); /* make spinbutton not sizeable */
    }
    else /* slider not visible */
    {
      gtk_box_pack_end(GTK_BOX(hbox), spinbutton, TRUE, TRUE, 5); /* make spinbutton sizeable */
    }
    gtk_widget_show(spinbutton);
    gtk_widget_set_sensitive(spinbutton, settable);  
  }

  /* slider */
  if (preferences.show_range_mode & 3)
  {
    if (preferences.show_range_mode & 1) /* bit 0 (val 1) : scale */
    {
      slider = gtk_hscale_new(GTK_ADJUSTMENT(*data));
      gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE);
      gtk_scale_set_digits(GTK_SCALE(slider), digits);
    }
    else /* bit 1 (val 2) : scrollbar */
    {
      slider = gtk_hscrollbar_new(GTK_ADJUSTMENT(*data));
    }
    xsane_back_gtk_set_tooltip(xsane.tooltips, slider, desc);
    gtk_widget_set_size_request(slider, 170, -1);
    /* GTK_UPDATE_CONTINUOUS, GTK_UPDATE_DISCONTINUOUS, GTK_UPDATE_DELAYED */
    gtk_range_set_update_policy(GTK_RANGE(slider), preferences.gtk_update_policy);
    gtk_box_pack_end(GTK_BOX(hbox), slider, TRUE, TRUE, 5); /* make slider sizeable */
    gtk_widget_show(slider);
    gtk_widget_set_sensitive(slider, settable);  
  }

  if (xsane_range_callback)
  {
    g_signal_connect(*data, "value_changed", (GtkSignalFunc) xsane_range_callback, val);
  }

  gtk_widget_show(hbox);

  if (option)
  {
   GSGDialogElement *elem;

    elem=xsane.element + option;
    elem->data    = *data;
    elem->widget  = slider;
  }
}

/* ----------------------------------------------------------------------------------------------------------------- */

static void xsane_browse_filename_callback(GtkWidget *widget, gpointer data)
{
 char filename[1024];
 char windowname[256];

  DBG(DBG_proc, "xsane_browse_filename_callback\n");

  xsane_set_sensitivity(FALSE);

  if (xsane.filetype) /* set filetype to "by ext." */
  {
   char buffer[256];

    snprintf(buffer, sizeof(buffer), "%s%s", preferences.filename, xsane.filetype);
    free(preferences.filename);
    free(xsane.filetype);
    xsane.filetype = NULL;
    preferences.filename = strdup(buffer);
  }

  if (preferences.filename) /* make sure a correct filename is defined */
  {
    strncpy(filename, preferences.filename, sizeof(filename));
    filename[sizeof(filename) - 1] = '\0';
  }
  else /* no filename given, take standard filename */
  {
    strcpy(filename, OUT_FILENAME);
  }

  snprintf(windowname, sizeof(windowname), "%s %s %s", xsane.prog_name, WINDOW_OUTPUT_FILENAME, xsane.device_text);

  umask((mode_t) preferences.directory_umask); /* define new file permissions */    
  xsane_back_gtk_get_filename(windowname, filename, sizeof(filename), filename, TRUE, TRUE, FALSE);
  umask(XSANE_DEFAULT_UMASK); /* define new file permissions */    

  if (preferences.filename)
  {
    free((void *) preferences.filename);
  }

  xsane_set_sensitivity(TRUE);

  preferences.filename = strdup(filename);

  /* correct length of filename counter if it is shorter than minimum length */
  xsane_update_counter_in_filename(&preferences.filename, FALSE, 0, preferences.filename_counter_len);

  xsane_set_filename(preferences.filename);

  gtk_option_menu_set_history(GTK_OPTION_MENU(xsane.filetype_option_menu), 0); /* set menu to "by ext" */
  xsane_define_maximum_output_size(); /* is necessary in postscript mode */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_filename(gchar *filename)
{
  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.outputfilename_entry), (GtkSignalFunc) xsane_outputfilename_changed_callback, NULL);
  gtk_entry_set_text(GTK_ENTRY(xsane.outputfilename_entry), (char *) filename); /* update filename in entry */
  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.outputfilename_entry), (GtkSignalFunc) xsane_outputfilename_changed_callback, NULL);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_filename_counter_step_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_filename_counter_step_callback\n");
 
  preferences.filename_counter_step = (int) data;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_filetype_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_filetype_callback\n");

  if (data)
  {
   char *extension, *filename;
 
    extension = strrchr(preferences.filename, '.');

    if ((extension) && (extension != preferences.filename))
    {
      if ( (!strcasecmp(extension, ".pnm"))  || (!strcasecmp(extension, ".raw"))
        || (!strcasecmp(extension, ".png"))  || (!strcasecmp(extension, ".ps"))
        || (!strcasecmp(extension, ".rgba"))
        || (!strcasecmp(extension, ".tiff")) || (!strcasecmp(extension, ".tif"))
        || (!strcasecmp(extension, ".jpg"))  || (!strcasecmp(extension, ".jpeg"))
         ) /* remove filetype extension */
      {
        filename = preferences.filename;
        *extension = 0; /* remove extension */
        preferences.filename = strdup(filename); /* filename without extension */
        free(filename); /* free unused memory */
      }
    }
  }
  else if (xsane.filetype)
  {
   char buffer[256];

    snprintf(buffer, sizeof(buffer), "%s%s", preferences.filename, xsane.filetype);
    free(preferences.filename);
    free(xsane.filetype);
    xsane.filetype = NULL;
    preferences.filename = strdup(buffer);
  }

  if (data)
  {
    xsane.filetype = strdup((char *) data); /* set extension for filename */
  }

  /* correct length of filename counter if it is shorter than minimum length */
  xsane_update_counter_in_filename(&preferences.filename, FALSE, 0, preferences.filename_counter_len);
  xsane_set_filename(preferences.filename);
  xsane_define_maximum_output_size(); /* is necessary in postscript mode */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_outputfilename_changed_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_outputfilename_changed_callback\n");

  if (preferences.filename)
  {
    free((void *) preferences.filename);
  }
  preferences.filename = strdup(gtk_entry_get_text(GTK_ENTRY(widget)));

  xsane_define_maximum_output_size(); /* is necessary in postscript mode */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_outputfilename_new(GtkWidget *vbox)
{
 GtkWidget *hbox;
 GtkWidget *text;
 GtkWidget *button;
 GtkWidget *xsane_filetype_menu, *xsane_filetype_item;
 GtkWidget *xsane_filename_counter_step_option_menu;
 GtkWidget *xsane_filename_counter_step_menu;
 GtkWidget *xsane_filename_counter_step_item;
 GtkWidget *xsane_label;
 gchar buf[200];
 int i,j;
 int filetype_nr;
 int select_item = 0;

  DBG(DBG_proc, "xsane_outputfilename_new\n");

  /* first line: disk icon, filename box */

  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 2);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);

  button = xsane_button_new_with_pixmap(xsane.xsane_window->window, hbox, file_xpm, DESC_BROWSE_FILENAME,
                                        (GtkSignalFunc) xsane_browse_filename_callback, NULL);
  gtk_widget_add_accelerator(button, "clicked", xsane.accelerator_group, GDK_B, GDK_CONTROL_MASK, DEF_GTK_ACCEL_LOCKED);

  text = gtk_entry_new();
  gtk_widget_set_size_request(text, 80, -1); /* set minimum size */
  xsane_back_gtk_set_tooltip(xsane.tooltips, text, DESC_FILENAME);
  gtk_entry_set_max_length(GTK_ENTRY(text), 255);
  gtk_entry_set_text(GTK_ENTRY(text), (char *) preferences.filename);
  gtk_box_pack_start(GTK_BOX(hbox), text, TRUE, TRUE, 4);
  g_signal_connect(GTK_OBJECT(text), "changed", (GtkSignalFunc) xsane_outputfilename_changed_callback, NULL);

  xsane.outputfilename_entry = text;

  gtk_widget_show(text);
  gtk_widget_show(hbox);


  /* second line: Step, Type */

  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 2);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);

  /* filename counter step */
 
  xsane_label = gtk_label_new(TEXT_FILENAME_COUNTER_STEP);
  gtk_box_pack_start(GTK_BOX(hbox), xsane_label, FALSE, FALSE, 2);
  gtk_widget_show(xsane_label);
 
  xsane_filename_counter_step_option_menu = gtk_option_menu_new();
  xsane_back_gtk_set_tooltip(xsane.tooltips, xsane_filename_counter_step_option_menu, DESC_FILENAME_COUNTER_STEP);
  gtk_box_pack_start(GTK_BOX(hbox), xsane_filename_counter_step_option_menu, FALSE, FALSE, 2);
  gtk_widget_show(xsane_filename_counter_step_option_menu);
  gtk_widget_show(hbox);
 
  xsane_filename_counter_step_menu = gtk_menu_new();

  select_item = 0;
  j = -2;
  for (i=0; i < 5; i++)
  {
    snprintf(buf, sizeof(buf), "%+d", j);
    xsane_filename_counter_step_item = gtk_menu_item_new_with_label(buf);
    gtk_container_add(GTK_CONTAINER(xsane_filename_counter_step_menu), xsane_filename_counter_step_item);
    g_signal_connect(GTK_OBJECT(xsane_filename_counter_step_item), "activate", (GtkSignalFunc) xsane_filename_counter_step_callback, (void *) j);
    gtk_widget_show(xsane_filename_counter_step_item);
    if (preferences.filename_counter_step == j++)
    {
      select_item = i;
    }
  }
 
  gtk_option_menu_set_menu(GTK_OPTION_MENU(xsane_filename_counter_step_option_menu), xsane_filename_counter_step_menu);
  gtk_option_menu_set_history(GTK_OPTION_MENU(xsane_filename_counter_step_option_menu), select_item);

  /* filetype */

  xsane_filetype_menu = gtk_menu_new();

  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_BY_EXT);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, NULL);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr = 0;
  select_item = 0;

#ifdef HAVE_LIBJPEG
  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_JPEG);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_JPEG);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_JPEG)) )
  {
    select_item = filetype_nr;
  }
#endif

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_PNG);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_PNG);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_PNG)) )
  {
    select_item = filetype_nr;
  }
#endif
#endif

  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_PNM);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_PNM);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_PNM)) )
  {
    select_item = filetype_nr;
  }

  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_PS);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_PS);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_PS)) )
  {
    select_item = filetype_nr;
  }

  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_RAW);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_RAW);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_RAW)) )
  {
    select_item = filetype_nr;
  }

#ifdef SUPPORT_RGBA
  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_RGBA);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_RGBA);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_RGBA)) )
  {
    select_item = filetype_nr;
  }
#endif

#ifdef HAVE_LIBTIFF
  xsane_filetype_item = gtk_menu_item_new_with_label(MENU_ITEM_FILETYPE_TIFF);
  gtk_container_add(GTK_CONTAINER(xsane_filetype_menu), xsane_filetype_item);
  g_signal_connect(GTK_OBJECT(xsane_filetype_item), "activate", (GtkSignalFunc) xsane_filetype_callback, (void *) XSANE_FILETYPE_TIFF);
  gtk_widget_show(xsane_filetype_item);
  filetype_nr++;
  if ( (xsane.filetype) && (!strcasecmp(xsane.filetype, XSANE_FILETYPE_TIFF)) )
  {
    select_item = filetype_nr;
  }
#endif

  xsane.filetype_option_menu = gtk_option_menu_new();
  xsane_back_gtk_set_tooltip(xsane.tooltips, xsane.filetype_option_menu, DESC_FILETYPE);
  gtk_box_pack_end(GTK_BOX(hbox), xsane.filetype_option_menu, FALSE, FALSE, 2);
  gtk_option_menu_set_menu(GTK_OPTION_MENU(xsane.filetype_option_menu), xsane_filetype_menu);
  gtk_option_menu_set_history(GTK_OPTION_MENU(xsane.filetype_option_menu), select_item);
  gtk_widget_show(xsane.filetype_option_menu);

  xsane_label = gtk_label_new(TEXT_FILETYPE); /* opposite order because of box_pack_end */
  gtk_box_pack_end(GTK_BOX(hbox), xsane_label, FALSE, FALSE, 2);
  gtk_widget_show(xsane_label);

  gtk_widget_show(text);
  gtk_widget_show(hbox);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_separator_new(GtkWidget *xsane_parent, int dist)
{
 GtkWidget *xsane_separator;

  DBG(DBG_proc, "xsane_separator_new\n");

  xsane_separator = gtk_hseparator_new();
  gtk_box_pack_start(GTK_BOX(xsane_parent), xsane_separator, FALSE, FALSE, dist);
  gtk_widget_show(xsane_separator);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_info_table_text_new(GtkWidget *table, gchar *text, int row, int colomn)
{
 GtkWidget *hbox, *label;

  DBG(DBG_proc, "xsane_info_table_text_new\n");

  hbox = gtk_hbox_new(/* homogeneous */ FALSE, 0);
  gtk_table_attach_defaults(GTK_TABLE(table), hbox, row, row+1, colomn, colomn+1);
  gtk_widget_show(hbox);

  label = gtk_label_new(text);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
  gtk_widget_show(label);

 return label;
}
/* ---------------------------------------------------------------------------------------------------------------------- */
#if 0
GtkWidget *xsane_info_text_new(GtkWidget *parent, gchar *text)
{
 GtkWidget *hbox, *label;

  DBG(DBG_proc, "xsane_info_text_new\n");

  hbox = gtk_hbox_new(/* homogeneous */ FALSE, 0);
  gtk_box_pack_start(GTK_BOX(parent), hbox, TRUE, TRUE, 5);
  gtk_widget_show(hbox);

  label = gtk_label_new(text);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
  gtk_widget_show(label);

 return label;
}
#endif
/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_refresh_dialog(void)
{
  DBG(DBG_proc, "xsane_refresh_dialog\n");

  xsane_back_gtk_refresh_dialog();
}                    

/* ---------------------------------------------------------------------------------------------------------------------- */

/* Update the info line with the latest size information and update histogram.  */
void xsane_update_param(void *arg)
{
 gchar buf[200];
 const char *unit;

  DBG(DBG_proc, "xsane_update_param\n");

  if (!xsane.info_label)
  {
    return;
  }

  if (xsane.block_update_param) /* if we change more than one value, we only want to update all once */
  {
    return;
  }

  if (xsane.preview)
  {
    preview_update_surface(xsane.preview, 0);
  }

  if (sane_get_parameters(xsane.dev, &xsane.param) == SANE_STATUS_GOOD)
  {
   float size = (float) xsane.param.bytes_per_line * (float) xsane.param.lines;
   int depth = xsane.param.depth;

    if ( (depth == 16) && (preferences.reduce_16bit_to_8bit) )
    {
      depth = 8;
      size /= 2;
    }

    unit = "B";

    if (xsane.param.format >= SANE_FRAME_RED && xsane.param.format <= SANE_FRAME_BLUE)
    {
      size *= 3.0;
      depth *= 3;
    }
    else if (xsane.param.format == SANE_FRAME_RGB)
    {
      depth *= 3;
    }
#ifdef SUPPORT_RGBA
    else if (xsane.param.format == SANE_FRAME_RGBA)
    {
      depth *= 4;
    }
#endif

    if (size >= 1024.0 * 1024.0)
    {
      size /= 1024.0 * 1024.0;
      unit = "MB";
    }
    else if (size >= 1024.0)
    {
      size /= 1024.0;
      unit = "KB";
    }
    snprintf(buf, sizeof(buf), "%d*%d*%d (%1.1f %s)", xsane.param.pixels_per_line, xsane.param.lines, depth, size, unit);

    if (xsane.param.format == SANE_FRAME_GRAY)
    {
      xsane.xsane_colors = 1;
    }
#ifdef SUPPORT_RGBA
    else if (xsane.param.format == SANE_FRAME_RGBA)
    {
      xsane.xsane_colors = 4;
    }
#endif
    else /* RGB */
    {
      xsane.xsane_colors = 3;
    }
  }
  else 
  {
    snprintf(buf, sizeof(buf), TEXT_INVALID_PARAMS);
  }

  gtk_label_set(GTK_LABEL(xsane.info_label), buf);


  if (xsane.preview->surface_unit == SANE_UNIT_MM)
  {
   double dx, dy;

    unit = xsane_back_gtk_unit_string(xsane.preview->surface_unit);

    dx = fabs(xsane.preview->selection.coordinate[2] - xsane.preview->selection.coordinate[0]) / preferences.length_unit;
    dy = fabs(xsane.preview->selection.coordinate[3] - xsane.preview->selection.coordinate[1]) / preferences.length_unit;

    snprintf(buf, sizeof(buf), "%1.2f %s x %1.2f %s", dx, unit, dy, unit);
    gtk_progress_set_format_string(GTK_PROGRESS(xsane.progress_bar), buf);
  }

  xsane_update_histogram(TRUE /* update raw */);

  preview_display_valid(xsane.preview);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_define_output_filename(void)
{
 char buffer[256];

  DBG(DBG_proc, "xsane_define_output_filename\n");

  if (xsane.output_filename)
  {
    free(xsane.output_filename);
    xsane.output_filename = 0;
  }

  if (!xsane.force_filename)
  {
    if (xsane.filetype)
    {
      snprintf(buffer, sizeof(buffer), "%s%s", preferences.filename, xsane.filetype);
      xsane.output_filename = strdup(buffer);
    }
    else
    {
      xsane.output_filename = strdup(preferences.filename);
    }
  }
  else
  {
      xsane.output_filename = strdup(xsane.external_filename);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_identify_output_format(char *filename, char **ext)
{
 char *extension;
 int output_format=-1;

  DBG(DBG_proc, "xsane_identify_output_format\n");

  extension = strrchr(filename, '.');
  if (extension)
  {
    extension++; /* skip "." */
  }

  output_format = XSANE_UNKNOWN;

  if (extension)
  {
    if (!strcasecmp(extension, "raw"))
    {
      if (xsane.param.depth == 16)
      {
        output_format = XSANE_RAW16;
      }
    }
    else if ( (!strcasecmp(extension, "pnm")) || (!strcasecmp(extension, "ppm")) ||
              (!strcasecmp(extension, "pgm")) || (!strcasecmp(extension, "pbm")) )
    {
      if (xsane.param.depth == 16)
      {
        output_format = XSANE_PNM16;
      }
      else
      {
        output_format = XSANE_PNM;
      }
    }
#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
    else if (!strcasecmp(extension, "png"))
    {
      output_format = XSANE_PNG;
    }
#endif
#endif
#ifdef HAVE_LIBJPEG
    else if ( (!strcasecmp(extension, "jpg")) || (!strcasecmp(extension, "jpeg")) )
    {
      output_format = XSANE_JPEG;
    }
#endif
    else if (!strcasecmp(extension, "ps"))
    {
      output_format = XSANE_PS;
    }
#ifdef HAVE_LIBTIFF
    else if ( (!strcasecmp(extension, "tif")) || (!strcasecmp(extension, "tiff")) )
    {
      output_format = XSANE_TIFF;
    }
#endif
#ifdef SUPPORT_RGBA
    else if (!strcasecmp(extension, "rgba"))
    {
      output_format = XSANE_RGBA;
    }
#endif
  }

  if (ext)
  {
    if (extension)
    {
      *ext = strdup(extension);
    }
    else
    {
      *ext = 0;
    }
  }

 return output_format;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_change_working_directory(void)
{
 char filename[PATH_MAX];
 char windowname[256];

  DBG(DBG_proc, "xsane_change_working_directory\n");

  xsane_set_sensitivity(FALSE);

  sprintf(windowname, "%s %s %s", xsane.prog_name, WINDOW_CHANGE_WORKING_DIR, xsane.device_text);
  if (getcwd(filename, sizeof(filename)))
  {
    xsane_back_gtk_get_filename(windowname, filename, sizeof(filename), filename, TRUE, FALSE, TRUE);
    if (chdir(filename))
    {
     char buf[256];

      snprintf(buf, sizeof(buf), "%s %s (%s).", ERR_CHANGE_WORKING_DIR, filename, strerror(errno));
      xsane_back_gtk_error(buf, TRUE);
      xsane_set_sensitivity(TRUE);
      return;
    }
  }

  xsane_set_sensitivity(TRUE);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int eula_accept_flag;
static GtkWidget *eula_dialog = NULL;

static void xsane_eula_button_callback(GtkWidget *widget, gpointer data)
{
  eula_accept_flag = (int) data;

  DBG(DBG_proc ,"xsane_eula_button_callback(%d)\n", eula_accept_flag);

  gtk_widget_destroy(eula_dialog);
  eula_dialog = NULL;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_display_eula(int ask_for_accept)
/* returns FALSE if accepted, TRUE if not accepted */
{
 GtkWidget *vbox, *hbox, *button, *label;
 GtkAccelGroup *accelerator_group;
 char buf[1024];
 char filename[PATH_MAX];
 FILE *infile;

  DBG(DBG_proc, "xsane_display_eula(%d)\n", ask_for_accept);

  if (eula_dialog) /* make sure the dialog is only opend once */
  {
    return 0;
  }

  eula_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_size_request(eula_dialog, 550, 580);
  gtk_window_set_position(GTK_WINDOW(eula_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(eula_dialog), TRUE);
  g_signal_connect(GTK_OBJECT(eula_dialog), "delete_event", GTK_SIGNAL_FUNC(xsane_eula_button_callback), (void *) -1); /* -1 = cancel */
  snprintf(buf, sizeof(buf), "%s %s", xsane.prog_name, WINDOW_EULA);
  gtk_window_set_title(GTK_WINDOW(eula_dialog), buf);
  xsane_set_window_icon(eula_dialog, 0);

  accelerator_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(eula_dialog), accelerator_group);

  vbox = gtk_vbox_new(FALSE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
  gtk_container_add(GTK_CONTAINER(eula_dialog), vbox);
  gtk_widget_show(vbox); 

  /* display XSane copyright message */
  snprintf(buf, sizeof(buf), "XSane %s %s\n"
                             "%s %s\n"
                             "\n"
                             "%s\n"
                             "%s %s\n"
                             "%s %s\n",
                             TEXT_VERSION, XSANE_VERSION,
                             XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT,
                             TEXT_EULA,
                             TEXT_HOMEPAGE, XSANE_HOMEPAGE,
                             TEXT_EMAIL, XSANE_EMAIL);

  label = gtk_label_new(buf);
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);

  /* add hbox with text and scrollbar to display the eula text */
  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); 
  gtk_widget_show(hbox);

#ifdef HAVE_GTK_TEXT_VIEW_H
  {
   GtkWidget *scrolled_window, *text_view;
   GtkTextBuffer *text_buffer;

    /* create a scrolled window to get a vertical scrollbar */
    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(hbox), scrolled_window);
    gtk_widget_show(scrolled_window);
 
    /* create the gtk_text_view widget */
    text_view = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view), GTK_WRAP_WORD);
    gtk_container_add(GTK_CONTAINER(scrolled_window), text_view);
    gtk_widget_show(text_view); 

    /* get the text_buffer widget and insert the text from file */
    text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));

    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-eula", 0, ".txt", XSANE_PATH_SYSTEM);
    infile = fopen(filename, "r");
 
    if (infile)
    {
      char buffer[4096];
      int nchars;
   
      while (!feof(infile))
      {
        nchars = fread(buffer, 1, 4096, infile);
        gtk_text_buffer_insert_at_cursor(text_buffer, buffer, nchars);
      }
 
      fclose(infile);
    } 
    else
    {
      DBG(DBG_error0, "ERROR: eula text not found. Looks like xsane is not installed correct.\n");
     return TRUE;
    }

  }
#else /* we do not have gtk_text_view, so we use gtk_text */
  {
   GtkWidget *text, *vscrollbar;

    /* Create the gtk_text widget */
    text = gtk_text_new(NULL, NULL);
    gtk_text_set_editable(GTK_TEXT(text), FALSE); /* text is not editable */
    gtk_text_set_word_wrap(GTK_TEXT(text), TRUE); /* wrap complete words */
    gtk_box_pack_start(GTK_BOX(hbox), text, TRUE, TRUE, 0); 
    gtk_widget_show(text);
 
    /* Add a vertical scrollbar to the GtkText widget */
    vscrollbar = gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
    gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0); 
    gtk_widget_show(vscrollbar);

    /* Freeze the text widget, ready for multiple updates */ 
    gtk_text_freeze(GTK_TEXT(text)); 

    /* Load the file text.c into the text window */
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-eula", 0, ".txt", XSANE_PATH_SYSTEM);
    infile = fopen(filename, "r");
 
    if (infile)
    {
      char buffer[4096];
      int nchars;
   
      while (!feof(infile))
      {
        nchars = fread(buffer, 1, 4096, infile);
        gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, buffer, nchars);
      }
 
      fclose(infile);
    } 
    else
    {
      DBG(DBG_error0, "ERROR: eula text not found. Looks like xsane is not installed correct.\n");
     return TRUE;
    }

    /* Thaw the text widget, allowing the updates to become visible */
    gtk_text_thaw(GTK_TEXT(text)); 
  }
#endif



  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); 

  if (ask_for_accept) /* show accept + not accept buttons */
  {
    button = gtk_button_new_with_label(BUTTON_ACCEPT);
    g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_eula_button_callback, (void *) 0 /* accept */);
    gtk_container_add(GTK_CONTAINER(hbox), button);
    gtk_widget_show(button);

    button = gtk_button_new_with_label(BUTTON_NOT_ACCEPT);
    gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, 0, DEF_GTK_ACCEL_LOCKED);
    GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
    g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_eula_button_callback, (void *) 1 /* not accept */);
    gtk_container_add(GTK_CONTAINER(hbox), button);
    gtk_widget_grab_default(button);
    gtk_widget_show(button);
  }
  else /* show close button */
  {
#ifdef HAVE_GTK2
    button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
#else
    button = gtk_button_new_with_label(BUTTON_CLOSE);
#endif
    gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, 0, DEF_GTK_ACCEL_LOCKED);
    GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
    g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_eula_button_callback, (void *) 0 /* ok = accept */);
    gtk_container_add(GTK_CONTAINER(hbox), button);
    gtk_widget_grab_default(button);
    gtk_widget_show(button);
  }

  gtk_widget_show(hbox);
  gtk_widget_show(vbox);
  gtk_widget_show(eula_dialog);

  if (ask_for_accept == 0) /* do not ask for accept */
  {
    return 0;
  }
  
  eula_accept_flag = -255;

  while(eula_accept_flag == -255)
  {
    gtk_main_iteration(); /* allow gtk to react to user action */
  }

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }
 
 return eula_accept_flag;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static GtkWidget *license_dialog = NULL;

static void xsane_close_license_dialog_callback(GtkWidget *widget, gpointer data)
{
  gtk_widget_destroy(license_dialog);
  license_dialog = NULL;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_display_gpl(void)
/* returns FALSE if accepted, TRUE if not accepted */
{
 GtkWidget *vbox, *hbox, *button, *label;
 GtkAccelGroup *accelerator_group;
 char buf[1024];
 char filename[PATH_MAX];
 FILE *infile;

  if (license_dialog) /* make sure the dialog is only opend once */
  {
    return 0;
  }

  license_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_size_request(license_dialog, 550, 580);
  gtk_window_set_position(GTK_WINDOW(license_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(license_dialog), TRUE);
  snprintf(buf, sizeof(buf), "%s: %s", xsane.prog_name, WINDOW_GPL);
  gtk_window_set_title(GTK_WINDOW(license_dialog), buf);
  xsane_set_window_icon(license_dialog, 0);

  accelerator_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(license_dialog), accelerator_group);

  vbox = gtk_vbox_new(FALSE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
  gtk_container_add(GTK_CONTAINER(license_dialog), vbox);
  gtk_widget_show(vbox); 

  /* display XSane copyright message */
  snprintf(buf, sizeof(buf), "XSane %s %s\n"
                             "%s %s\n"
                             "\n"
                             "%s\n"
                             "%s %s\n"
                             "%s %s\n",
                             TEXT_VERSION, XSANE_VERSION,
                             XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT,
                             TEXT_GPL,
                             TEXT_HOMEPAGE, XSANE_HOMEPAGE,
                             TEXT_EMAIL, XSANE_EMAIL);

  label = gtk_label_new(buf);
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);

  /* add hbox with text and scrollbar to display the license text */
  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); 
  gtk_widget_show(hbox);

#ifdef HAVE_GTK_TEXT_VIEW_H
  {
   GtkWidget *scrolled_window, *text_view;
   GtkTextBuffer *text_buffer;

    /* create a scrolled window to get a vertical scrollbar */
    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(hbox), scrolled_window);
    gtk_widget_show(scrolled_window);
 
    /* create the gtk_text_view widget */
    text_view = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view), GTK_WRAP_WORD);
    gtk_container_add(GTK_CONTAINER(scrolled_window), text_view);
    gtk_widget_show(text_view); 

    /* get the text_buffer widget and insert the text from file */
    text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));

    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-gpl", 0, ".txt", XSANE_PATH_SYSTEM);
    infile = fopen(filename, "r");
 
    if (infile)
    {
      char buffer[4096];
      int nchars;
   
      while (!feof(infile))
      {
        nchars = fread(buffer, 1, 4096, infile);
        gtk_text_buffer_insert_at_cursor(text_buffer, buffer, nchars);
      }
 
      fclose(infile);
    } 
    else
    {
      DBG(DBG_error0, "ERROR: license text not found. Looks like xsane is not installed correct.\n");
     return TRUE;
    }

  }
#else /* we do not have gtk_text_view, so we use gtk_text */
  {
   GtkWidget *text, *vscrollbar;

    /* Create the gtk_text widget */
    text = gtk_text_new(NULL, NULL);
    gtk_text_set_editable(GTK_TEXT(text), FALSE); /* text is not editable */
    gtk_text_set_word_wrap(GTK_TEXT(text), TRUE); /* wrap complete words */
    gtk_box_pack_start(GTK_BOX(hbox), text, TRUE, TRUE, 0); 
    gtk_widget_show(text);
 
    /* Add a vertical scrollbar to the GtkText widget */
    vscrollbar = gtk_vscrollbar_new(GTK_TEXT(text)->vadj);
    gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0); 
    gtk_widget_show(vscrollbar);

    /* Freeze the text widget, ready for multiple updates */ 
    gtk_text_freeze(GTK_TEXT(text)); 

    /* Load the file text.c into the text window */
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-gpl", 0, ".txt", XSANE_PATH_SYSTEM);
    infile = fopen(filename, "r");
 
    if (infile)
    {
      char buffer[4096];
      int nchars;
   
      while (!feof(infile))
      {
        nchars = fread(buffer, 1, 4096, infile);
        gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, buffer, nchars);
      }
 
      fclose(infile);
    } 
    else
    {
      DBG(DBG_error0, "ERROR: license text not found. Looks like xsane is not installed correct.\n");
     return TRUE;
    }

    /* Thaw the text widget, allowing the updates to become visible */
    gtk_text_thaw(GTK_TEXT(text)); 
  }
#endif



  hbox = gtk_hbox_new(FALSE, 2);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); 

#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
#else
  button = gtk_button_new_with_label(BUTTON_CLOSE);
#endif
  gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, 0, DEF_GTK_ACCEL_LOCKED);
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_close_license_dialog_callback, (void *) 0 /* ok = accept */);
  gtk_container_add(GTK_CONTAINER(hbox), button);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  gtk_widget_show(hbox);
  gtk_widget_show(vbox);
  gtk_widget_show(license_dialog);

  return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_window_get_position(GtkWidget *gtk_window, gint *x, gint *y)
{
#ifdef HAVE_GTK2
  gtk_window_get_position(gtk_window, x, y);
#else
  if (xsane.get_deskrelative_origin)
  {
    DBG(DBG_proc, "xsane_window_get_position(deskrelative)\n");
    gdk_window_get_deskrelative_origin(gtk_window->window, x, y);
  }
  else
  {
    DBG(DBG_proc, "xsane_window_get_position(root)\n");
    gdk_window_get_root_origin(gtk_window->window, x, y);
  }
#endif
}   

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_widget_test_uposition(GtkWidget *gtk_window)
{
#ifndef HAVE_GTK2
 gint x, y, x_orig, y_orig;

  DBG(DBG_proc, "xsane_widget_test_uposition\n");

  gtk_widget_realize(gtk_window);

  while (!GTK_WIDGET_REALIZED(gtk_window) || (gtk_events_pending()))
  {
    gtk_main_iteration();
  }

  xsane_window_get_position(gtk_window, &x, &y);
  xsane_window_get_position(gtk_window, &x, &y);

  DBG(DBG_info, "xsane_widget_test_uposition: original position = %d, %d\n", x, y);

  x_orig = x;
  y_orig = y;
  gtk_window_move(GTK_WINDOW(gtk_window), x, y);

  xsane_window_get_position(gtk_window, &x, &y);
  DBG(DBG_info, "xsane_widget_test_uposition: new position = %d, %d\n", x, y);

  if ( (x != x_orig) || (y != y_orig) )
  {
    DBG(DBG_proc, "xsane_widget_test_uposition: using deskrelative function\n");
    xsane.get_deskrelative_origin = 1;
  }
  else
  {
    DBG(DBG_proc, "xsane_widget_test_uposition: using root function\n");
  }
#endif
}

/* ---------------------------------------------------------------------------------------------------------------------- */
