/*
 * This file is part of YAD.
 *
 * YAD 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 3 of the License, or
 * (at your option) any later version.
 *
 * YAD 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 YAD. If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2008-2020, Victor Ananjevsky <ananasik@gmail.com>
 */

#include <ctype.h>
#include <stdlib.h>

#include "yad.h"

#include "calendar.xpm"

static GSList *fields = NULL;
static guint n_fields;

/* expand %N in command to fields values */
static GString *
expand_action (gchar * cmd)
{
  GString *xcmd;
  guint i = 0;

  xcmd = g_string_new ("");
  while (cmd[i])
    {
      if (cmd[i] == '%')
        {
          i++;
          if (g_ascii_isdigit (cmd[i]))
            {
              YadField *fld;
              gchar *buf, *arg;
              guint num, j = i;

              /* get field num */
              while (g_ascii_isdigit (cmd[j]))
                j++;
              buf = g_strndup (cmd + i, j - i);
              num = g_ascii_strtoll (buf, NULL, 10);
              g_free (buf);
              if (num > 0 && num <= n_fields)
                num--;
              else
                continue;

              /* get field value */
              arg = NULL;
              fld = g_slist_nth_data (options.form_data.fields, num);
              switch (fld->type)
                {
                case YAD_FIELD_SIMPLE:
                case YAD_FIELD_HIDDEN:
                case YAD_FIELD_READ_ONLY:
                case YAD_FIELD_COMPLETE:
                case YAD_FIELD_FILE_SAVE:
                case YAD_FIELD_DIR_CREATE:
                case YAD_FIELD_MFILE:
                case YAD_FIELD_MDIR:
                case YAD_FIELD_DATE:
                  buf = escape_char ((gchar *) gtk_entry_get_text (GTK_ENTRY (g_slist_nth_data (fields, num))), '"');
                  arg = g_shell_quote (buf ? buf : "");
                  g_free (buf);
                  break;
                case YAD_FIELD_NUM:
                  {
                    guint prec = gtk_spin_button_get_digits (GTK_SPIN_BUTTON (g_slist_nth_data (fields, num)));
                    arg = g_strdup_printf ("%.*f", prec, gtk_spin_button_get_value (GTK_SPIN_BUTTON (g_slist_nth_data (fields, num))));
                    break;
                  }
                case YAD_FIELD_CHECK:
                  arg = g_strdup (print_bool_val (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (g_slist_nth_data (fields, num)))));
                  break;
                case YAD_FIELD_COMBO:
                case YAD_FIELD_COMBO_ENTRY:
                  buf = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (g_slist_nth_data (fields, num)));
                  arg = g_shell_quote (buf ? buf : "");
                  g_free (buf);
                  break;
                case YAD_FIELD_SCALE:
                  arg = g_strdup_printf ("%d", (gint) gtk_range_get_value (GTK_RANGE (g_slist_nth_data (fields, num))));
                  break;
                case YAD_FIELD_FILE:
                case YAD_FIELD_DIR:
                  arg = g_shell_quote (gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (g_slist_nth_data (fields, num))));
                  break;
                case YAD_FIELD_FONT:
                  arg = g_shell_quote (gtk_font_chooser_get_font (GTK_FONT_CHOOSER (g_slist_nth_data (fields, num))));
                  break;
                case YAD_FIELD_LINK:
                  arg = g_shell_quote (gtk_link_button_get_uri (GTK_LINK_BUTTON (g_slist_nth_data (fields, num))));
                  break;
                case YAD_FIELD_APP:
                  {
                    GList *wl = gtk_container_get_children (GTK_CONTAINER (g_slist_nth_data (fields, num)));
                    GAppInfo *info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (wl->data));
                    arg =  g_shell_quote (g_app_info_get_executable (info));
                    g_object_unref (info);
                    break;
                  }
                case YAD_FIELD_COLOR:
                  {
                    GdkRGBA c;
                    GtkColorChooser *cb = GTK_COLOR_CHOOSER (g_slist_nth_data (fields, num));
                    gtk_color_chooser_get_rgba (cb, &c);
                    buf = get_color (&c);
                    arg = g_shell_quote (buf ? buf : "");
                    g_free (buf);
                    break;
                  }
                case YAD_FIELD_TEXT:
                  {
                    GtkTextBuffer *tb;
                    GtkTextIter b, e;
                    gchar *txt;

                    tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (g_slist_nth_data (fields, num)));
                    gtk_text_buffer_get_bounds (tb, &b, &e);
                    txt = gtk_text_buffer_get_text (tb, &b, &e, FALSE);

                    /* escape special chars */
                    buf = escape_str (txt);
                    g_free (txt);

                    /* escape quotes */
                    txt = escape_char (buf, '"');
                    g_free (buf);

                    arg = g_shell_quote (txt ? txt : "");
                    g_free (txt);
                  }
                default: ;
                }
              if (arg)
                {
                  g_string_append (xcmd, arg);
                  g_free (arg);
                }
              i = j;
            }
          else
            {
              g_string_append_c (xcmd, cmd[i]);
              i++;
            }
        }
      else
        {
          g_string_append_c (xcmd, cmd[i]);
          i++;
        }
    }
  g_string_append_c (xcmd, '\0');

  return xcmd;
}

static void
set_field_value (guint num, gchar * value)
{
  GtkWidget *w;
  gchar **s;
  YadField *fld = g_slist_nth_data (options.form_data.fields, num);

  w = GTK_WIDGET (g_slist_nth_data (fields, num));
  if (g_ascii_strcasecmp (value, "@disabled@") == 0)
    {
      gtk_widget_set_sensitive (w, FALSE);
      return;
    }
  else
    gtk_widget_set_sensitive (w, TRUE);

  switch (fld->type)
    {
    case YAD_FIELD_READ_ONLY:
      gtk_widget_set_sensitive (w, FALSE);
    case YAD_FIELD_SIMPLE:
    case YAD_FIELD_HIDDEN:
    case YAD_FIELD_MFILE:
    case YAD_FIELD_MDIR:
    case YAD_FIELD_FILE_SAVE:
    case YAD_FIELD_DIR_CREATE:
    case YAD_FIELD_DATE:
      gtk_entry_set_text (GTK_ENTRY (w), value);
      break;

    case YAD_FIELD_NUM:
      s = g_strsplit (value, options.common_data.item_separator, -1);
      if (s[0])
        {
          gdouble val = g_ascii_strtod (s[0], NULL);
          w = g_slist_nth_data (fields, num);
          if (s[1])
            {
              gchar **s1 = g_strsplit (s[1], "..", 2);
              if (s[0] && s[1])
                {
                  gdouble min, max;
                  min = g_ascii_strtod (s1[0], NULL);
                  max = g_ascii_strtod (s1[1], NULL);
                  gtk_spin_button_set_range (GTK_SPIN_BUTTON (w), min, max);
                }
              g_strfreev (s1);
              if (s[2])
                {
                  gdouble step = g_ascii_strtod (s[2], NULL);
                  gtk_spin_button_set_increments (GTK_SPIN_BUTTON (w), step, step * 10);
                  if (s[3])
                    {
                      guint prec = (guint) g_ascii_strtoull (s[3], NULL, 0);
                      if (prec > 20)
                        prec = 20;
                      gtk_spin_button_set_digits (GTK_SPIN_BUTTON (w), prec);
                    }
                }
            }
          /* set initial value must be after setting range and step */
          gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), val);
        }
      g_strfreev (s);
      break;

    case YAD_FIELD_CHECK:
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), get_bool_val (value));
      break;

    case YAD_FIELD_COMPLETE:
      {
        GtkEntryCompletion *c;
        GtkTreeModel *m;
        GtkTreeIter it;
        gint i = 0, def = -1;

        c = gtk_entry_get_completion (GTK_ENTRY (w));
        m = gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (c));
        gtk_list_store_clear (GTK_LIST_STORE (m));

        s = g_strsplit (value, options.common_data.item_separator, -1);
        while (s[i])
          {
            gtk_list_store_append (GTK_LIST_STORE (m), &it);
            if (s[i][0] == '^')
              {
                gtk_list_store_set (GTK_LIST_STORE (m), &it, 0, g_strcompress (s[i] + 1), -1);
                def = i;
              }
            else
              gtk_list_store_set (GTK_LIST_STORE (m), &it, 0, g_strcompress (s[i]), -1);

            i++;
          }
        if (def >= 0)
          gtk_entry_set_text (GTK_ENTRY (w), s[def] + 1);
        else
          gtk_entry_set_text (GTK_ENTRY (w), "");
        g_strfreev (s);
        break;
      }

    case YAD_FIELD_COMBO:
    case YAD_FIELD_COMBO_ENTRY:
      {
        GtkTreeModel *m;
        gint i = 0, def = 0;

        /* cleanup previous values */
        m = gtk_combo_box_get_model (GTK_COMBO_BOX (w));
        gtk_list_store_clear (GTK_LIST_STORE (m));

        s = g_strsplit (value, options.common_data.item_separator, -1);
        while (s[i])
          {
            gchar *buf;

            if (s[i][0] == '^')
              {
                buf = g_strcompress (s[i] + 1);
                def = i;
              }
            else
              buf = g_strcompress (s[i]);
            gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (w), buf);
            g_free (buf);
            i++;
          }
        gtk_combo_box_set_active (GTK_COMBO_BOX (w), def);
        g_strfreev (s);
        break;
      }

    case YAD_FIELD_DIR:
      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (w), value);
    case YAD_FIELD_FILE:
      gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (w), value);
      break;

    case YAD_FIELD_FONT:
      gtk_font_chooser_set_font (GTK_FONT_CHOOSER (w), value);
      break;

    case YAD_FIELD_SCALE:
      gtk_range_set_value (GTK_RANGE (w), atoi (value));
      break;

    case YAD_FIELD_APP:
      {
        GtkWidget *b;
        GList *wl;

        for (wl = gtk_container_get_children (GTK_CONTAINER (w)); wl; wl = wl->next)
          {
            GtkWidget *cw = GTK_WIDGET (wl->data);
            gtk_container_remove (GTK_CONTAINER (w), cw);
            gtk_widget_destroy (cw);
          }

        if (value && value[0])
          b = gtk_app_chooser_button_new (value);
        else
          b = gtk_app_chooser_button_new ("text/plain");
        gtk_widget_set_name (b, "yad-form-app");
        gtk_box_pack_start (GTK_BOX (w), b, TRUE, TRUE, 0);
        gtk_widget_show_all (w);
        break;
      }

    case YAD_FIELD_COLOR:
      {
        GdkRGBA c;
        gdk_rgba_parse (&c, value);
        gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (w), &c);
        break;
      }

    case YAD_FIELD_BUTTON:
    case YAD_FIELD_FULL_BUTTON:
      g_object_set_data_full (G_OBJECT (w), "cmd", g_strdup (value), g_free);
      break;

    case YAD_FIELD_LINK:
      gtk_link_button_set_uri (GTK_LINK_BUTTON (w), value);
      break;

    case YAD_FIELD_TEXT:
      {
        GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
        gchar *txt = g_strcompress (value);
        gtk_text_buffer_set_text (tb, txt, -1);
        g_free (txt);
        break;
      }

    default:;
    }
}

static void
button_clicked_cb (GtkButton * b, gpointer data)
{
  gchar *action = (gchar *) g_object_get_data (G_OBJECT (b), "cmd");

  if (action && action[0])
    {
      if (action[0] == '@')
        {
          gchar *data;
          gint exit = 1;
          GString *cmd = expand_action (action + 1);
          exit = run_command_sync (cmd->str, &data);
          if (exit == 0)
            {
              guint i = 0;
              gchar **lines = g_strsplit (data, "\n", 0);
              while (lines[i] && lines[i][0])
                {
                  gint fn;
                  gchar *ptr = lines[i];

                  while (isblank (*ptr)) ptr++;

                  if (isdigit (*ptr))
                    {
                      gchar **ln = g_strsplit (ptr, ":", 2);
                      fn = g_ascii_strtoll (ln[0], NULL, 10);
                      if (fn && ln[1])
                        set_field_value (fn - 1, ln[1]);
                      g_strfreev (ln);
                    }
                  i++;
                }
            }
          g_free (data);
          g_string_free (cmd, TRUE);
        }
      else
        {
          GString *cmd = expand_action (action);
          run_command_async (cmd->str);
          g_string_free (cmd, TRUE);
        }
    }

  /* set focus to specified field */
  if (options.form_data.focus_field > 0 && options.form_data.focus_field <= n_fields)
    gtk_widget_grab_focus (GTK_WIDGET (g_slist_nth_data (fields, options.form_data.focus_field - 1)));
}

static void
form_activate_cb (GtkEntry * entry, gpointer data)
{
  if (options.plug == -1)
    yad_exit (options.data.def_resp);
}

static void
select_files_cb (GtkEntry * entry, GtkEntryIconPosition pos, GdkEventButton * event, gpointer data)
{
  GtkWidget *dlg;
  GList *filt;
  static gchar *path = NULL;

  if (event->button == 1)
    {
      YadFieldType type = GPOINTER_TO_INT (data);

      if (!path)
        {
          const gchar *val = gtk_entry_get_text (entry);

          if (g_file_test (val, G_FILE_TEST_IS_DIR))
            path = g_strdup (val);
          else
            path = val ? g_path_get_dirname (val) : g_get_current_dir ();
        }

      if (type == YAD_FIELD_MFILE)
        {
          dlg = gtk_file_chooser_dialog_new (_("Select files"),
                                             GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                             GTK_FILE_CHOOSER_ACTION_OPEN,
                                             _("Cancel"), GTK_RESPONSE_CANCEL,
                                             _("OK"), GTK_RESPONSE_ACCEPT, NULL);
        }
      else
        {
          dlg = gtk_file_chooser_dialog_new (_("Select folders"),
                                             GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                             GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
                                             _("Cancel"), GTK_RESPONSE_CANCEL,
                                             _("OK"), GTK_RESPONSE_ACCEPT, NULL);
        }
      gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE);
      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg), path);

      g_signal_connect (dlg, "map", G_CALLBACK (gtk_file_chooser_set_show_hidden), GINT_TO_POINTER (options.common_data.show_hidden));

      /* add preview */
      if (options.common_data.preview)
        {
          GtkWidget *p = gtk_image_new ();
          gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dlg), p);
          g_signal_connect (dlg, "update-preview", G_CALLBACK (update_preview), p);
        }

      /* add filters */
      for (filt = options.common_data.filters; filt; filt = filt->next)
        gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), GTK_FILE_FILTER (filt->data));

      if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
        {
          GSList *files, *ptr;
          GString *str;

          str = g_string_new ("");
          files = ptr = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg));

          while (ptr)
            {
              if (ptr->data)
                {
                  gchar *fn = g_filename_from_uri ((gchar *) ptr->data, NULL, NULL);
                  g_string_append (str, fn);
                  g_string_append (str, options.common_data.item_separator);
                  g_free (fn);
                }
              ptr = ptr->next;
            }

          str->str[str->len - 1] = '\0';        // remove last item separator
          gtk_entry_set_text (entry, str->str);

          g_slist_free (files);
          g_string_free (str, TRUE);
        }

      g_free (path);
      path = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg));
      gtk_widget_destroy (dlg);
    }
}

static void
create_files_cb (GtkEntry * entry, GtkEntryIconPosition pos, GdkEventButton * event, gpointer data)
{
  GtkWidget *dlg;
  GList *filt;
  static gchar *path = NULL;

  if (event->button == 1)
    {
      YadFieldType type = GPOINTER_TO_INT (data);

      if (!path)
        {
          const gchar *val = gtk_entry_get_text (entry);

          if (g_file_test (val, G_FILE_TEST_IS_DIR))
            path = g_strdup (val);
          else
            path = val ? g_path_get_dirname (val) : g_get_current_dir ();
        }

      if (type == YAD_FIELD_FILE_SAVE)
        {
          dlg = gtk_file_chooser_dialog_new (_("Select or create file"),
                                             GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                             GTK_FILE_CHOOSER_ACTION_SAVE,
                                             _("Cancel"), GTK_RESPONSE_CANCEL,
                                             _("OK"), GTK_RESPONSE_ACCEPT, NULL);
        }
      else
        {
          dlg = gtk_file_chooser_dialog_new (_("Select or create folder"),
                                             GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                             GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER,
                                             _("Cancel"), GTK_RESPONSE_CANCEL,
                                             _("OK"), GTK_RESPONSE_ACCEPT, NULL);
        }
      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg), path);

      g_signal_connect (dlg, "map", G_CALLBACK (gtk_file_chooser_set_show_hidden), GINT_TO_POINTER (options.common_data.show_hidden));

      /* add preview */
      if (options.common_data.preview)
        {
          GtkWidget *p = gtk_image_new ();
          gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dlg), p);
          g_signal_connect (dlg, "update-preview", G_CALLBACK (update_preview), p);
        }

      /* add filters */
      for (filt = options.common_data.filters; filt; filt = filt->next)
        gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), GTK_FILE_FILTER (filt->data));

      if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
        {
          gchar *file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));

          gtk_entry_set_text (entry, file);
          g_free (file);
        }

      g_free (path);
      path = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg));
      gtk_widget_destroy (dlg);
    }
}

static void
date_selected_cb (GtkCalendar * c, gpointer data)
{
  gtk_dialog_response (GTK_DIALOG (data), GTK_RESPONSE_ACCEPT);
}

static void
select_date_cb (GtkEntry * entry, GtkEntryIconPosition pos, GdkEventButton * event, gpointer data)
{
  GtkWidget *dlg, *cal;

  if (event->button == 1)
    {
      GDate *d;

      dlg = gtk_dialog_new_with_buttons (_("Select date"),
                                         GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (entry))),
                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                                         _("Cancel"), GTK_RESPONSE_CANCEL,
                                         _("OK"), GTK_RESPONSE_ACCEPT, NULL);
      cal = gtk_calendar_new ();
      gtk_widget_show (cal);
      g_signal_connect (G_OBJECT (cal), "day-selected-double-click", G_CALLBACK (date_selected_cb), dlg);
      gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))), cal, TRUE, TRUE, 5);

      d = g_date_new ();
      g_date_set_parse (d, gtk_entry_get_text (entry));
      if (g_date_valid (d))
        {
          gtk_calendar_select_day (GTK_CALENDAR (cal), g_date_get_day (d));
          gtk_calendar_select_month (GTK_CALENDAR (cal), g_date_get_month (d) - 1, g_date_get_year (d));
        }
      g_date_free (d);

      if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
        {
          guint day, month, year;
          gchar *format = options.common_data.date_format;
          gchar time_string[128];

          gtk_calendar_get_date (GTK_CALENDAR (cal), &day, &month, &year);
          d = g_date_new_dmy (year, month + 1, day);
          g_date_strftime (time_string, 127, format, d);
          gtk_entry_set_text (entry, time_string);
          g_date_free (d);
        }
      gtk_widget_destroy (dlg);
    }

  gtk_widget_grab_focus (GTK_WIDGET (entry));
}

static gboolean
link_clicked_cb (GtkLinkButton *btn, gpointer data)
{
  const gchar *uri = gtk_link_button_get_uri (btn);
  open_uri (uri);
  return TRUE;
}

static gboolean
handle_stdin (GIOChannel * ch, GIOCondition cond, gpointer data)
{
  static guint cnt = 0;

  if ((cond == G_IO_IN) || (cond == G_IO_IN + G_IO_HUP))
    {
      GError *err = NULL;
      GString *string = g_string_new (NULL);

      while (ch->is_readable != TRUE);

      do
        {
          gint status;

          if (cnt == n_fields)
            {
              if (options.form_data.cycle_read)
                cnt = 0;
              else
                {
                  g_io_channel_shutdown (ch, TRUE, NULL);
                  return FALSE;
                }
            }

          do
            {
              status = g_io_channel_read_line_string (ch, string, NULL, &err);

              while (gtk_events_pending ())
                gtk_main_iteration ();
            }
          while (status == G_IO_STATUS_AGAIN);

          if (status != G_IO_STATUS_NORMAL)
            {
              if (err)
                {
                  g_printerr ("yad_form_handle_stdin(): %s\n", err->message);
                  g_error_free (err);
                  err = NULL;
                }
              /* stop handling */
              g_io_channel_shutdown (ch, TRUE, NULL);
              return FALSE;
            }

          strip_new_line (string->str);
          if (string->str[0])
            {
              if (string->str[0] == '\014')
                {
                  gint i;
                  /* clear the form and reset fields counter */
                  for (i = 0; i < n_fields; i++)
                    set_field_value (i, "");
                  cnt = -1; /* must be -1 due to next increment */
                }
              else
                set_field_value (cnt, string->str);
            }
          cnt++;
        }
      while (g_io_channel_get_buffer_condition (ch) == G_IO_IN);
      g_string_free (string, TRUE);
    }

  if ((cond != G_IO_IN) && (cond != G_IO_IN + G_IO_HUP))
    {
      g_io_channel_shutdown (ch, TRUE, NULL);
      return FALSE;
    }

  return TRUE;
}

GtkWidget *
form_create_widget (GtkWidget * dlg)
{
  GtkWidget *tbl, *w = NULL;
  GList *filt;

  if (options.form_data.fields)
    {
      GtkWidget *l, *e;
      GdkPixbuf *pb;
      guint i, col, row, rows;

      n_fields = g_slist_length (options.form_data.fields);

      row = col = 0;
      rows = n_fields / options.form_data.columns;
      if (n_fields % options.form_data.columns > 0)
        rows++;

      tbl = gtk_grid_new ();
      gtk_grid_set_row_spacing (GTK_GRID (tbl), 5);
      gtk_grid_set_column_spacing (GTK_GRID (tbl), 5);

      if (options.form_data.scroll)
        {
          GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL);
          gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
          gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), options.hscroll_policy, options.vscroll_policy);
          gtk_container_add (GTK_CONTAINER (sw), tbl);
          w = sw;
        }
      else
        w = tbl;

      /* create form */
      for (i = 0; i < n_fields; i++)
        {
          YadField *fld = g_slist_nth_data (options.form_data.fields, i);

          /* add field label */
          l = NULL;
          if (fld->type != YAD_FIELD_CHECK && fld->type != YAD_FIELD_BUTTON &&
              fld->type != YAD_FIELD_FULL_BUTTON && fld->type != YAD_FIELD_LINK &&
              fld->type != YAD_FIELD_LABEL && fld->type != YAD_FIELD_TEXT)
            {
              gchar *buf = g_strcompress (fld->name);
              l = gtk_label_new (NULL);
              if (!options.data.no_markup)
                {
                  gtk_label_set_markup_with_mnemonic (GTK_LABEL (l), buf);
                  if (fld->tip)
                    gtk_widget_set_tooltip_markup (l, fld->tip);
                }
              else
                {
                  gtk_label_set_text_with_mnemonic (GTK_LABEL (l), buf);
                  if (fld->tip)
                    gtk_widget_set_tooltip_text (l, fld->tip);
                }
              gtk_widget_set_name (l, "yad-form-flabel");
              gtk_label_set_xalign (GTK_LABEL (l), options.common_data.align);
              gtk_grid_attach (GTK_GRID (tbl), l, col * 2, row, 1, 1);
              g_free (buf);
            }

          /* add field entry */
          switch (fld->type)
            {
            case YAD_FIELD_SIMPLE:
            case YAD_FIELD_HIDDEN:
            case YAD_FIELD_READ_ONLY:
            case YAD_FIELD_COMPLETE:
              e = gtk_entry_new ();
              gtk_widget_set_name (e, "yad-form-entry");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
             g_signal_connect (G_OBJECT (e), "activate", G_CALLBACK (form_activate_cb), dlg);
              if (fld->type == YAD_FIELD_HIDDEN)
                gtk_entry_set_visibility (GTK_ENTRY (e), FALSE);
              else if (fld->type == YAD_FIELD_READ_ONLY)
                gtk_widget_set_sensitive (e, FALSE);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);

              if (fld->type == YAD_FIELD_COMPLETE)
                {
                  GtkEntryCompletion *c = gtk_entry_completion_new ();
                  GtkListStore *m = gtk_list_store_new (1, G_TYPE_STRING);

                  gtk_entry_set_completion (GTK_ENTRY (e), c);
                  gtk_entry_completion_set_model (c, GTK_TREE_MODEL (m));
                  gtk_entry_completion_set_text_column (c, 0);

                  if (options.common_data.complete != YAD_COMPLETE_SIMPLE)
                    gtk_entry_completion_set_match_func (c, check_complete, NULL, NULL);

                  g_object_unref (m);
                  g_object_unref (c);
                }

              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_NUM:
              e = gtk_spin_button_new_with_range (0.0, 65525.0, 1.0);
              gtk_widget_set_name (e, "yad-form-spin");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_entry_set_alignment (GTK_ENTRY (e), 1.0);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_CHECK:
              {
                gchar *buf = g_strcompress (fld->name);
                e = gtk_check_button_new_with_label (buf);
                gtk_widget_set_name (e, "yad-form-check");
                if (fld->tip)
                  {
                    if (!options.data.no_markup)
                      gtk_widget_set_tooltip_markup (e, fld->tip);
                    else
                      gtk_widget_set_tooltip_text (e, fld->tip);
                  }
                gtk_grid_attach (GTK_GRID (tbl), e, col * 2, row, 2, 1);
                gtk_widget_set_hexpand (e, TRUE);
                fields = g_slist_append (fields, e);
                g_free (buf);
              }
              break;

            case YAD_FIELD_COMBO:
              e = gtk_combo_box_text_new ();
              gtk_widget_set_name (e, "yad-form-combo");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_COMBO_ENTRY:
              e = gtk_combo_box_text_new_with_entry ();
              gtk_widget_set_name (e, "yad-form-edit-combo");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_FILE:
              e = gtk_file_chooser_button_new (_("Select file"), GTK_FILE_CHOOSER_ACTION_OPEN);
              gtk_widget_set_name (e, "yad-form-file");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (e), g_get_current_dir ());
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);

              /* add preview */
              if (options.common_data.preview)
                {
                  GtkWidget *p = gtk_image_new ();
                  gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (e), p);
                  g_signal_connect (e, "update-preview", G_CALLBACK (update_preview), p);
                }

              /* add filters */
              for (filt = options.common_data.filters; filt; filt = filt->next)
                gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (e), GTK_FILE_FILTER (filt->data));

              break;

            case YAD_FIELD_DIR:
              e = gtk_file_chooser_button_new (_("Select folder"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
              gtk_widget_set_name (e, "yad-form-file");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (e), g_get_current_dir ());
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_FONT:
              e = gtk_font_button_new ();
              gtk_widget_set_name (e, "yad-form-font");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_APP:
              e = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_COLOR:
              e = gtk_color_button_new ();
              gtk_widget_set_name (e, "yad-form-color");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_MFILE:
            case YAD_FIELD_MDIR:
              e = gtk_entry_new ();
              gtk_widget_set_name (e, "yad-form-entry");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_entry_set_icon_from_icon_name (GTK_ENTRY (e), GTK_ENTRY_ICON_SECONDARY, "document-open");
              g_signal_connect (G_OBJECT (e), "icon-press", G_CALLBACK (select_files_cb), GINT_TO_POINTER (fld->type));
              g_signal_connect (G_OBJECT (e), "activate", G_CALLBACK (form_activate_cb), dlg);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_FILE_SAVE:
            case YAD_FIELD_DIR_CREATE:
              e = gtk_entry_new ();
              gtk_widget_set_name (e, "yad-form-entry");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_entry_set_icon_from_icon_name (GTK_ENTRY (e), GTK_ENTRY_ICON_SECONDARY, "document-open");
              g_signal_connect (G_OBJECT (e), "icon-press", G_CALLBACK (create_files_cb), GINT_TO_POINTER (fld->type));
              g_signal_connect (G_OBJECT (e), "activate", G_CALLBACK (form_activate_cb), dlg);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_DATE:
              e = gtk_entry_new ();
              gtk_widget_set_name (e, "yad-form-entry");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              pb = gdk_pixbuf_new_from_xpm_data (calendar_xpm);
              gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (e), GTK_ENTRY_ICON_SECONDARY, pb);
              g_object_unref (pb);
              g_signal_connect (G_OBJECT (e), "icon-press", G_CALLBACK (select_date_cb), e);
              g_signal_connect (G_OBJECT (e), "activate", G_CALLBACK (form_activate_cb), dlg);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_SCALE:
              e = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0.0, 100.0, 1.0);
              gtk_widget_set_name (e, "yad-form-scale");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              gtk_scale_set_value_pos (GTK_SCALE (e), GTK_POS_LEFT);
              gtk_grid_attach (GTK_GRID (tbl), e, 1 + col * 2, row, 1, 1);
              gtk_widget_set_hexpand (e, TRUE);
              gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_BUTTON:
            case YAD_FIELD_FULL_BUTTON:
              e = gtk_button_new ();
              gtk_widget_set_name (e, "yad-form-button");
              if (fld->tip)
                {
                  if (!options.data.no_markup)
                    gtk_widget_set_tooltip_markup (e, fld->tip);
                  else
                    gtk_widget_set_tooltip_text (e, fld->tip);
                }
              g_signal_connect (G_OBJECT (e), "clicked", G_CALLBACK (button_clicked_cb), NULL);
              gtk_container_add (GTK_CONTAINER (e), get_label (fld->name, 2, e));
              if (fld->type == YAD_FIELD_BUTTON)
                gtk_button_set_relief (GTK_BUTTON (e), GTK_RELIEF_NONE);
              gtk_grid_attach (GTK_GRID (tbl), e, col * 2, row, 2, 1);
              gtk_widget_set_hexpand (e, TRUE);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_LINK:
              {
                gchar *buf = g_strcompress (fld->name[0] ? fld->name : _("Link"));
                e = gtk_link_button_new_with_label ("", buf);
                gtk_widget_set_name (e, "yad-form-link");
                if (fld->tip)
                  {
                    if (!options.data.no_markup)
                      gtk_widget_set_tooltip_markup (e, fld->tip);
                    else
                      gtk_widget_set_tooltip_text (e, fld->tip);
                  }
                g_signal_connect (G_OBJECT (e), "activate-link", G_CALLBACK (link_clicked_cb), NULL);
                gtk_grid_attach (GTK_GRID (tbl), e, col * 2, row, 2, 1);
                gtk_widget_set_hexpand (e, TRUE);
                fields = g_slist_append (fields, e);
                break;
              }

            case YAD_FIELD_LABEL:
              if (fld->name[0])
                {
                  gchar *buf = g_strcompress (fld->name);
                  e = gtk_label_new (NULL);
                  gtk_widget_set_name (e, "yad-form-label");
                  if (fld->tip)
                    {
                      if (!options.data.no_markup)
                        gtk_widget_set_tooltip_markup (e, fld->tip);
                      else
                        gtk_widget_set_tooltip_text (e, fld->tip);
                    }
                  if (options.data.no_markup)
                    gtk_label_set_text (GTK_LABEL (e), buf);
                  else
                    gtk_label_set_markup (GTK_LABEL (e), buf);
                  gtk_label_set_line_wrap (GTK_LABEL (e), TRUE);
                  gtk_label_set_selectable (GTK_LABEL (e), options.data.selectable_labels);
                  gtk_label_set_xalign (GTK_LABEL (e), options.common_data.align);
                  g_free (buf);
                }
              else
                {
                  e = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
                  gtk_widget_set_name (e, "yad-form-separator");
                }
              gtk_grid_attach (GTK_GRID (tbl), e, col * 2, row, 2, 1);
              gtk_widget_set_hexpand (e, TRUE);
              fields = g_slist_append (fields, e);
              break;

            case YAD_FIELD_TEXT:
              {
                GtkWidget *l, *sw, *b;
                gchar *ltxt = g_strcompress (fld->name);

                b = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
                l = gtk_label_new ("");
                gtk_label_set_xalign (GTK_LABEL (l), 0.0);
                if (options.data.no_markup)
                  gtk_label_set_text (GTK_LABEL (l), ltxt);
                else
                  gtk_label_set_markup (GTK_LABEL (l), ltxt);
                g_free (ltxt);
                gtk_box_pack_start (GTK_BOX (b), l, FALSE, FALSE, 0);

                sw = gtk_scrolled_window_new (NULL, NULL);
                gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN);
                gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), options.hscroll_policy, options.vscroll_policy);
                gtk_box_pack_start (GTK_BOX (b), sw, TRUE, TRUE, 0);

                e = gtk_text_view_new ();
                gtk_widget_set_name (e, "yad-form-text");
                if (fld->tip)
                  {
                    if (!options.data.no_markup)
                      gtk_widget_set_tooltip_markup (e, fld->tip);
                    else
                      gtk_widget_set_tooltip_text (e, fld->tip);
                  }
                gtk_text_view_set_editable (GTK_TEXT_VIEW (e), TRUE);
                gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (e), GTK_WRAP_WORD_CHAR);
                gtk_container_add (GTK_CONTAINER (sw), e);

#ifdef HAVE_SPELL
                if (options.common_data.enable_spell)
                  {
                    GspellTextView *spell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (e));
                    gspell_text_view_basic_setup (spell_view);
                  }
#endif

                gtk_grid_attach (GTK_GRID (tbl), b, col * 2, row, 2, 1);
                gtk_widget_set_hexpand (b, TRUE);
                gtk_widget_set_vexpand (b, TRUE);
                gtk_label_set_mnemonic_widget (GTK_LABEL (l), e);
                fields = g_slist_append (fields, e);

                break;
              }
            }

          /* increase row and column */
          row++;
          if (row >= rows)
            {
              row = 0;
              col++;
            }
        }

      /* fill entries with data */
      if (options.extra_data)
        {
          i = 0;
          while (options.extra_data[i] && i < n_fields)
            {
              set_field_value (i, options.extra_data[i]);
              i++;
            }
        }
      else
        {
          GIOChannel *channel = g_io_channel_unix_new (0);
          g_io_channel_set_encoding (channel, NULL, NULL);
          g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
          g_io_add_watch (channel, G_IO_IN | G_IO_HUP, handle_stdin, NULL);
        }
    }

  if (options.form_data.focus_field > 0 && options.form_data.focus_field <= n_fields)
    gtk_widget_grab_focus (GTK_WIDGET (g_slist_nth_data (fields, options.form_data.focus_field - 1)));

  return w;
}

static void
form_print_field (guint fn)
{
  gchar *buf;
  YadField *fld = g_slist_nth_data (options.form_data.fields, fn);

  switch (fld->type)
    {
    case YAD_FIELD_SIMPLE:
    case YAD_FIELD_HIDDEN:
    case YAD_FIELD_READ_ONLY:
    case YAD_FIELD_COMPLETE:
    case YAD_FIELD_MFILE:
    case YAD_FIELD_MDIR:
    case YAD_FIELD_FILE_SAVE:
    case YAD_FIELD_DIR_CREATE:
    case YAD_FIELD_DATE:
      if (options.common_data.quoted_output)
        {
          buf = g_shell_quote (gtk_entry_get_text (GTK_ENTRY (g_slist_nth_data (fields, fn))));
          g_printf ("%s%s", buf, options.common_data.separator);
          g_free (buf);
        }
      else
        g_printf ("%s%s", gtk_entry_get_text (GTK_ENTRY (g_slist_nth_data (fields, fn))),
                  options.common_data.separator);
      break;
    case YAD_FIELD_NUM:
      {
        guint prec = gtk_spin_button_get_digits (GTK_SPIN_BUTTON (g_slist_nth_data (fields, fn)));
        if (options.common_data.quoted_output)
          g_printf ("'%.*f'%s", prec, gtk_spin_button_get_value (GTK_SPIN_BUTTON (g_slist_nth_data (fields, fn))),
                    options.common_data.separator);
        else
          g_printf ("%.*f%s", prec, gtk_spin_button_get_value (GTK_SPIN_BUTTON (g_slist_nth_data (fields, fn))),
                    options.common_data.separator);
        break;
      }
    case YAD_FIELD_CHECK:
      if (options.common_data.quoted_output)
        g_printf ("'%s'%s", print_bool_val (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (g_slist_nth_data (fields, fn)))),
                  options.common_data.separator);
      else
        g_printf ("%s%s", print_bool_val (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (g_slist_nth_data (fields, fn)))),
                  options.common_data.separator);
      break;
    case YAD_FIELD_COMBO:
    case YAD_FIELD_COMBO_ENTRY:
      if (options.common_data.num_output && fld->type == YAD_FIELD_COMBO)
        g_printf ("%d%s", gtk_combo_box_get_active (GTK_COMBO_BOX (g_slist_nth_data (fields, fn))) + 1,
                  options.common_data.separator);
      else if (options.common_data.quoted_output)
        {
          buf = g_shell_quote (gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (g_slist_nth_data (fields, fn))));
          g_printf ("%s%s", buf, options.common_data.separator);
          g_free (buf);
        }
      else
        g_printf ("%s%s", gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (g_slist_nth_data (fields, fn))),
                  options.common_data.separator);
      break;
    case YAD_FIELD_FILE:
    case YAD_FIELD_DIR:
      if (options.common_data.quoted_output)
        {
          gchar *fname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (g_slist_nth_data (fields, fn)));
          buf = g_shell_quote (fname ? fname : "");
          g_free (fname);
          g_printf ("%s%s", buf ? buf : "", options.common_data.separator);
          g_free (buf);
        }
      else
        {
          buf = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (g_slist_nth_data (fields, fn)));
          g_printf ("%s%s", buf ? buf : "", options.common_data.separator);
          g_free (buf);
        }
      break;
    case YAD_FIELD_FONT:
      {
        gchar *fname = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (g_slist_nth_data (fields, fn)));
        if (options.common_data.quoted_output)
          g_printf ("'%s'%s", fname ? fname : "", options.common_data.separator);
        else
          g_printf ("%s%s", fname ? fname : "", options.common_data.separator);
        g_free (fname);
        break;
      }
    case YAD_FIELD_APP:
      {
        gchar *exec;
        GAppInfo *info = NULL;
        GList *wl = gtk_container_get_children (GTK_CONTAINER (g_slist_nth_data (fields, fn)));

        if (wl)
          {
            info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (wl->data));
            if (info)
              exec = (gchar *) g_app_info_get_executable (info);
            else
              exec = "";
          }
        else
          exec = "";

        if (options.common_data.quoted_output)
          {
            buf = g_shell_quote (exec);
            g_printf ("%s%s", buf, options.common_data.separator);
            g_free (buf);
          }
        else
          g_printf ("%s%s", exec, options.common_data.separator);
        if (info)
          g_object_unref (info);
        break;
      }
    case YAD_FIELD_COLOR:
      {
        gchar *cs;
        GdkRGBA c;
        GtkColorChooser *cb = GTK_COLOR_CHOOSER (g_slist_nth_data (fields, fn));
        gtk_color_chooser_get_rgba (cb, &c);
        cs = get_color (&c);

        if (options.common_data.quoted_output)
          {
            buf = g_shell_quote (cs ? cs : "");
            g_printf ("%s%s", buf, options.common_data.separator);
            g_free (buf);
          }
        else
          g_printf ("%s%s", cs, options.common_data.separator);
        g_free (cs);
        break;
      }
    case YAD_FIELD_SCALE:
      if (options.common_data.quoted_output)
        g_printf ("'%d'%s", (gint) gtk_range_get_value (GTK_RANGE (g_slist_nth_data (fields, fn))),
                  options.common_data.separator);
      else
        g_printf ("%d%s", (gint) gtk_range_get_value (GTK_RANGE (g_slist_nth_data (fields, fn))),
                  options.common_data.separator);
      break;
    case YAD_FIELD_LINK:
      if (options.common_data.quoted_output)
        {
          buf = g_shell_quote (gtk_link_button_get_uri (GTK_LINK_BUTTON (g_slist_nth_data (fields, fn))));
          g_printf ("%s%s", buf, options.common_data.separator);
          g_free (buf);
        }
      else
        g_printf ("%s%s", gtk_link_button_get_uri (GTK_LINK_BUTTON (g_slist_nth_data (fields, fn))),
                  options.common_data.separator);
      break;
    case YAD_FIELD_BUTTON:
    case YAD_FIELD_FULL_BUTTON:
    case YAD_FIELD_LABEL:
      if (options.common_data.quoted_output)
        g_printf ("''%s", options.common_data.separator);
      else
        g_printf ("%s", options.common_data.separator);
      break;
    case YAD_FIELD_TEXT:
      {
        gchar *txt;
        GtkTextBuffer *tb;
        GtkTextIter b, e;

        tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (g_slist_nth_data (fields, fn)));
        gtk_text_buffer_get_bounds (tb, &b, &e);
        txt = escape_str (gtk_text_buffer_get_text (tb, &b, &e, FALSE));
        if (options.common_data.quoted_output)
          {
            buf = g_shell_quote (txt);
            g_printf ("%s%s", buf, options.common_data.separator);
            g_free (buf);
          }
        else
          g_printf ("%s%s", txt, options.common_data.separator);
        g_free (txt);
      }
    }
}

void
form_print_result (void)
{
  guint i;

  if (options.form_data.output_by_row)
    {
      guint j, rows;

      rows = n_fields / options.form_data.columns;
      rows += (n_fields % options.form_data.columns ? 1 : 0);
      for (i = 0; i < rows; i++)
        {
          for (j = 0; j < options.form_data.columns; j++)
            {
              guint fld = i + rows * j;
              if (fld < n_fields)
                form_print_field (fld);
            }
        }
    }
  else
    {
      for (i = 0; i < n_fields; i++)
        form_print_field (i);
    }
  g_printf ("\n");
}