Logo Search packages:      
Sourcecode: gretl version File versions  Download package

gpt_dialog.c

/* 
 *  gretl -- Gnu Regression, Econometrics and Time-series Library
 *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
 * 
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 * 
 */

/* gpt_dialog.c for gretl -- GUI gnuplot controller dialog */

#include "gretl.h"
#include "plotspec.h"
#include "gpt_control.h"
#include "session.h"
#include "dlgutils.h"
#include "fileselect.h"
#include "calculator.h"
#include "gpt_dialog.h"

#include "../pixmaps/mouse.xpm"
#include "gppoints.h"

#ifdef G_OS_WIN32
# include "gretlwin32.h"
#endif

struct gpt_titles_t {
    char *desc;        /* How the field will show up in the options dialog */
    short tab;         /* which tab (if any) does the item fall under? */
    GtkWidget *widget;
};

struct gpt_range_t {
    gint ID;
    GtkWidget *isauto;
    GtkWidget *min;
    GtkWidget *max;
    GtkWidget *lbase;
};

#define MAX_AXES 3
#define NTITLES  4

/* apparatus for handling the case where lines and/or labels are
   added or removed via the gui dialog, but these changes are
   discarded (the user doesn't click "OK" or "Apply")
*/

#define old_lines_init(e) (e->old_n_lines = -1, e->old_lines = NULL)
#define old_labels_init(e) (e->old_n_labels = -1, e->old_labels = NULL)

#define lines_not_synced(e) (e->old_n_lines < 0)
#define labels_not_synced(e) (e->old_n_labels < 0)

#define restore_lines(e) (e->old_n_lines >= 0)
#define restore_labels(e) (e->old_n_labels >= 0)

typedef struct plot_editor_ plot_editor;

struct plot_editor_ {
    GtkWidget *dialog;
    GtkWidget *notebook;
    GPT_SPEC *spec;
    gchar *fontname;

    GtkWidget **linetitle;
    GtkWidget **lineformula;
    GtkWidget **stylecombo;
    GtkWidget **yaxiscombo;
    GtkWidget **linescale;
    GtkWidget **linewidth;

    GtkWidget *fitformula;
    GtkWidget *fitlegend;

    GtkWidget **labeltext;
    GtkWidget **labeljust;
    GtkWidget **labelpos;

    GtkWidget *keycombo;
    GtkWidget *fitcombo;
    GtkWidget *border_check;
    GtkWidget *y2_check;
    GtkWidget *ttfcombo;
    GtkWidget *ttfspin;

    int gui_nlines;
    int gui_nlabels;

    struct gpt_range_t axis_range[MAX_AXES];
    struct gpt_titles_t gpt_titles[NTITLES];

    int old_n_lines;
    int old_n_labels;

    GPT_LINE *old_lines;
    GPT_LABEL *old_labels;
};

#define PLOT_LABEL_POS_LEN 32

const gchar *fittype_strings[] = {
    N_("none"),
    N_("linear: y = a + b*x"),
    N_("quadratic: y = a + b*x + c*x^2"),
    N_("inverse: y = a + b*(1/x)"),
    N_("loess (locally weighted fit)"),
    NULL
};

enum {
    GUI_LINE,
    GUI_LABEL
};

static const char *get_font_filename (const char *showname);
static void gpt_tab_lines (plot_editor *ed, GPT_SPEC *spec, int ins);
static void gpt_tab_labels (plot_editor *ed, GPT_SPEC *spec, int ins);
static int add_line_widget (plot_editor *ed);
static int add_label_widget (plot_editor *ed);

static void widget_set_int (GtkWidget *w, const gchar *key, gint val)
{
    g_object_set_data(G_OBJECT(w), key, GINT_TO_POINTER(val));
}

static int widget_get_int (GtkWidget *w, const char *key)
{
    return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), key));
}

/* graph color selection apparatus */

#define XPMROWS 19
#define XPMCOLS 17

#define scale_round(v) ((v) * 255.0 / 65535.0)

static GtkWidget *get_image_for_color (const gretlRGB *color)
{
    static char **xpm = NULL;
    GdkPixbuf *icon;
    GtkWidget *image;
    char colstr[8] = {0};
    int i;

    if (color == NULL) {
      return NULL;
    }

    if (xpm == NULL) {
      xpm = strings_array_new_with_length(XPMROWS, XPMCOLS);
      if (xpm == NULL) {
          return NULL;
      }

      /* common set-up */
      strcpy(xpm[0], "16 16 2 1");
      strcpy(xpm[1], "X      c #000000");
      strcpy(xpm[2], ".      c #000000");
      strcpy(xpm[3], "................");

      for (i=4; i<XPMROWS-1; i++) {
          strcpy(xpm[i], ".XXXXXXXXXXXXXX.");
      }

      strcpy(xpm[XPMROWS-1], "................");
    }

    /* write in the specific color we want */
    print_rgb_hash(colstr, color);
    for (i=0; i<6; i++) {
      xpm[1][10+i] = colstr[i+1];
    }    

    icon = gdk_pixbuf_new_from_xpm_data((const char **) xpm);
    image = gtk_image_new_from_pixbuf(icon);
    
    return image;
}

static void color_select_callback (GtkWidget *button, GtkWidget *w)
{
    GtkWidget *csel;
    GtkWidget *color_button, *image;
    GdkColor gcolor;
    gpointer data;
    gretlRGB rgb;
    gint i;

    color_button = g_object_get_data(G_OBJECT(w), "color_button");
    csel = GTK_COLOR_SELECTION_DIALOG(w)->colorsel;

    gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(csel), &gcolor);

    rgb.r = (unsigned char) (scale_round(gcolor.red));
    rgb.g = (unsigned char) (scale_round(gcolor.green));
    rgb.b = (unsigned char) (scale_round(gcolor.blue));

    i = widget_get_int(w, "colnum");
    data = g_object_get_data(G_OBJECT(color_button), "plotspec");
    
    if (data != NULL) {
      gretlRGB *prgb = malloc(sizeof *prgb);

      if (prgb != NULL) {
          prgb->r = rgb.r;
          prgb->g = rgb.g;
          prgb->b = rgb.b;
          g_object_set_data_full(G_OBJECT(color_button), "rgb",
                           prgb, free);
      }
    } else {
      set_graph_palette(i, rgb);
      update_persistent_graph_colors();
    } 

    /* update the "image" widget */
    image = g_object_get_data(G_OBJECT(color_button), "image");
    gtk_widget_destroy(image);
    image = get_image_for_color(&rgb);
    gtk_widget_show(image);
    gtk_container_add(GTK_CONTAINER(color_button), image);
    g_object_set_data(G_OBJECT(color_button), "image", image);
  
    gtk_widget_destroy(w);
}

static void color_cancel (GtkWidget *button, GtkWidget *w)
{
    gtk_widget_destroy(w);
}

/* reset color patch button after selecting the option to
   restore the default plot colors */

static void color_patch_button_reset (GtkWidget *button, int cnum)
{
    GtkWidget *image;

    image = g_object_get_data(G_OBJECT(button), "image");
    gtk_widget_destroy(image);
    image = get_image_for_color(get_graph_color(cnum));
    gtk_widget_show(image);
    gtk_container_add(GTK_CONTAINER(button), image);
    g_object_set_data(G_OBJECT(button), "image", image);

    if (cnum == BOXCOLOR || cnum == BOXCOLOR - 1) {
      update_persistent_graph_colors();
    }
}

static void graph_color_selector (GtkWidget *w, gpointer p)
{
    GPT_SPEC *spec;
    GtkWidget *cdlg;
    GtkWidget *button;
    gint i = GPOINTER_TO_INT(p);
    char colstr[8];
    GdkColor gcolor;

    spec = g_object_get_data(G_OBJECT(w), "plotspec");

    if (spec != NULL && spec->lines[i].rgb[0] != '\0') {
      strcpy(colstr, spec->lines[i].rgb);
    } else {
      const gretlRGB *rgb = get_graph_color(i);

      if (rgb == NULL) {
          fprintf(stderr, "graph_color_selector: got NULL rgb\n");
          return;
      }
      print_rgb_hash(colstr, rgb);
    }

    gdk_color_parse(colstr, &gcolor);

    cdlg = gtk_color_selection_dialog_new(_("gretl: graph color selection"));

    widget_set_int(cdlg, "colnum", i);
    g_object_set_data(G_OBJECT(cdlg), "color_button", w);

    gtk_color_selection_set_current_color(GTK_COLOR_SELECTION
                                (GTK_COLOR_SELECTION_DIALOG(cdlg)->colorsel),
                                &gcolor);                           

    button = GTK_COLOR_SELECTION_DIALOG(cdlg)->ok_button;
    g_signal_connect(G_OBJECT(button), "clicked", 
                 G_CALLBACK(color_select_callback), cdlg);

    button = GTK_COLOR_SELECTION_DIALOG(cdlg)->cancel_button;
    g_signal_connect(G_OBJECT(button), "clicked", 
                 G_CALLBACK(color_cancel), cdlg);

    gtk_widget_show(cdlg);
    gtk_window_set_modal(GTK_WINDOW(cdlg), TRUE);
}

static GtkWidget *color_patch_button (int i)
{
    GtkWidget *image, *button;

    image = get_image_for_color(get_graph_color(i));

    if (image == NULL) {
      button = gtk_button_new_with_label(_("Select color"));
    } else {
      button = gtk_button_new();
      gtk_container_add(GTK_CONTAINER(button), image);
      g_object_set_data(G_OBJECT(button), "image", image);
      g_signal_connect(G_OBJECT(button), "clicked", 
                   G_CALLBACK(graph_color_selector), 
                   GINT_TO_POINTER(i));
    } 

    return button;
}

static int style_from_line_number (GPT_SPEC *spec, int i)
{
    int j, lt = spec->lines[i].type;

    if (lt > 0) {
      j = lt - 1;
    } else if (lt == LT_NONE) {
      j = i;
    } else {
      j = LT_NONE;
    }

    return j;
}

static GtkWidget *line_color_button (GPT_SPEC *spec, int i)
{
    GtkWidget *image, *button;
    int j = style_from_line_number(spec, i);

    if (j == LT_NONE) {
      return NULL;
    }

    if (spec->lines[j].rgb[0] != '\0') {
      gretlRGB color;

      gretl_rgb_get(&color, spec->lines[j].rgb); 
      image = get_image_for_color(&color);
    } else {
      image = get_image_for_color(get_graph_color(j));
    }

    if (image == NULL) {
      /* failsafe -- shouldn't happen */
      button = gtk_button_new_with_label(_("Select color"));
    } else {
      button = gtk_button_new();
      gtk_container_add(GTK_CONTAINER(button), image);
      g_object_set_data(G_OBJECT(button), "image", image);
      g_object_set_data(G_OBJECT(button), "plotspec", spec);
      g_signal_connect(G_OBJECT(button), "clicked", 
                   G_CALLBACK(graph_color_selector), 
                   GINT_TO_POINTER(j));
    } 

    return button;
}

static void maybe_apply_line_color (GtkWidget *w, GPT_SPEC *spec,
                            int i)
{
    gretlRGB *rgb = NULL;
    GtkWidget *cb;
    int j;

    cb = g_object_get_data(G_OBJECT(w), "colorsel");

    if (cb != NULL) {
      rgb = g_object_get_data(G_OBJECT(cb), "rgb");
    }

    if (rgb != NULL) {
      j = style_from_line_number(spec, i);
      print_rgb_hash(spec->lines[j].rgb, rgb);
    }
}

/* end graph color selection apparatus */

static GdkPixbuf *get_pixbuf_for_line (int dots)
{
    static char **xpm = NULL;
    int i;

    if (xpm == NULL) {
      xpm = strings_array_new_with_length(14, 31);
      if (xpm == NULL) {
          return NULL;
      }

      /* common set-up */
      strcpy(xpm[0], "30 11 2 1");
      strcpy(xpm[1], "  c None");
      strcpy(xpm[2], ". c #000000");

      for (i=3; i<14; i++) {
          strcpy(xpm[i], "                              ");
      }
    }

    /* write in the specific pattern we want */
    if (dots) {
      strcpy(xpm[8], ".  .  .  .  .  .  .  .  .  .  ");
    } else {
      strcpy(xpm[8], "............................  ");
    }    

    return gdk_pixbuf_new_from_xpm_data((const char **) xpm);
}

static void flip_manual_range (GtkWidget *widget, plot_editor *ed)
{
    gint i = widget_get_int(widget, "axis");
    gboolean s = GTK_TOGGLE_BUTTON(ed->axis_range[i].isauto)->active;

    gtk_widget_set_sensitive(ed->axis_range[i].min, !s);
    gtk_widget_set_sensitive(ed->axis_range[i].max, !s);
}

/* Take text from a gtkentry and write to gnuplot spec string */

static void entry_to_gp_string (GtkWidget *w, char *targ, size_t n)
{
    const gchar *s;

    *targ = '\0';

    g_return_if_fail(GTK_IS_ENTRY(w));

    s = gtk_entry_get_text(GTK_ENTRY(w));
    if (s != NULL && *s != '\0') {
      strncat(targ, s, n - 1);
    }
}

static void combo_to_gp_style (GtkWidget *w, int *sty)
{
    gchar *s;

    g_return_if_fail(GTK_IS_COMBO_BOX(w));

    s = gtk_combo_box_get_active_text(GTK_COMBO_BOX(w));

    if (s != NULL && *s != '\0') {
      *sty = gp_style_from_translation(s);
    }

    g_free(s);
}

static void fittype_from_combo (GtkComboBox *box, GPT_SPEC *spec)
{
    int oldfit = widget_get_int(GTK_WIDGET(box), "oldfit");
    FitType f = gtk_combo_box_get_active(box);

    if (f == oldfit) {
      /* no change */
      return;
    }

    if (f == PLOT_FIT_OLS || f == PLOT_FIT_QUADRATIC || 
      f == PLOT_FIT_INVERSE || f == PLOT_FIT_LOESS) {
      plotspec_add_fit(spec, f);
      spec->flags &= ~GPT_FIT_HIDDEN;
    } else if (f == PLOT_FIT_NONE) {
      if (spec->n_lines >= 2) {
          spec->flags |= GPT_FIT_HIDDEN;
      }
      spec->fit = f;
    }

    widget_set_int(GTK_WIDGET(box), "oldfit", f);
}

static gboolean fit_type_changed (GtkComboBox *box, plot_editor *ed)
{
    GPT_SPEC *spec = ed->spec;
    const char *s1 = spec->yvarname;
    const char *s2 = spec->xvarname;
    gchar *title = NULL;
    FitType f = PLOT_FIT_NONE;

    if (*s1 == '\0' || *s2 == '\0') {
      return FALSE;
    }

    f = gtk_combo_box_get_active(box);

    if (f == PLOT_FIT_OLS) {
      title = g_strdup_printf(_("%s versus %s (with least squares fit)"),
            s1, s2);
    } else if (f == PLOT_FIT_QUADRATIC) {
      title = g_strdup_printf(_("%s versus %s (with quadratic fit)"),
            s1, s2);
    } else if (f == PLOT_FIT_INVERSE) {
      title = g_strdup_printf(_("%s versus %s (with inverse fit)"),
            s1, s2);
    } else if (f == PLOT_FIT_LOESS) {
      title = g_strdup_printf(_("%s versus %s (with loess fit)"),
            s1, s2);
    }

    if (title != NULL) {
      gtk_entry_set_text(GTK_ENTRY(ed->gpt_titles[0].widget), title);
      g_free(title);
    }

    /* also re-jig the "Lines" tab entries for the fitted
       line */

    if (ed->fitformula != NULL && ed->fitlegend != NULL) {
      fittype_from_combo(box, spec);
      gtk_entry_set_text(GTK_ENTRY(ed->fitformula), spec->lines[1].formula);
      gtk_entry_set_text(GTK_ENTRY(ed->fitlegend), spec->lines[1].title);
    }
    
    return FALSE;
}

static void dot_callback (GtkComboBox *box, GPT_SPEC *spec)
{
    int i = widget_get_int(GTK_WIDGET(box), "linenum");
    int d = gtk_combo_box_get_active(box);
    GtkWidget *w;

    /* flip between colored solid line and black dotted */

    spec->lines[i].type = (d > 0)? 0 : LT_NONE;

    w = g_object_get_data(G_OBJECT(box), "colorsel");
    gtk_widget_set_sensitive(w, d == 0);
    w = g_object_get_data(G_OBJECT(box), "color-label");
    gtk_widget_set_sensitive(w, d == 0);
}

/* take a double (which might be NA) and format it for
   a gtkentry widget */

static void double_to_gp_entry (double x, GtkWidget *w)
{
    if (w != NULL && GTK_IS_ENTRY(w)) {
      gchar *numstr;

      if (na(x)) {
          numstr = g_strdup("*");
      } else {
          numstr = g_strdup_printf("%.10g", x);
      }
      gtk_entry_set_text(GTK_ENTRY(w), numstr);
      g_free(numstr);
    }
}

/* read a double from a gtkentry, with error checking */

static void entry_to_gp_double (GtkWidget *w, double *val)
{
    double x = NADBL;

    if (w != NULL && GTK_IS_ENTRY(w)) {
      const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));

#ifdef ENABLE_NLS
      if (s != NULL && *s != '\0') {
          gchar *tmp = g_strdup(s);

          charsub(tmp, ',', '.');
          gretl_push_c_numeric_locale();      
          if (check_atof(tmp)) {
            errbox(gretl_errmsg_get());
          } else {
            x = atof(tmp);
          }
          gretl_pop_c_numeric_locale();
          g_free(tmp);
      }
#else
      if (s != NULL && *s != '\0') {
          if (check_atof(s)) {
            errbox(gretl_errmsg_get());
          } else {
            x = atof(s);
          }
      }
#endif
    }

    if (!na(x)) {
      *val = x;
    }    
}

#define gp_string_to_entry(w,s) do { \
      gtk_entry_set_text(GTK_ENTRY(w),s); } while (0)

static int
get_label_pos_from_entry (GtkWidget *w, double *pos)
{
    int err = 0;

    if (GTK_IS_ENTRY(w)) {
      const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
      int chk;
    
      chk = sscanf(s, "%lf %lf", &pos[0], &pos[1]);
      if (chk != 2) {
          errbox(_("Invalid label position, must be X Y"));
          gtk_editable_select_region(GTK_EDITABLE(w), 0, strlen(s));
          pos[0] = pos[1] = NADBL;
          err = 1;
      } 
    } else {
      err = 1;
    }

    return err;
}

static int validate_range (double *r)
{
    int err = 0;

    if (na(r[0]) || na(r[1])) {
      r[0] = r[1] = NADBL;
      err = 1;
    } else if (r[1] <= r[0]) {
      r[0] = r[1] = NADBL;
      err = 1;
    }

    return err;
}

static int 
set_logscale_from_entry (GPT_SPEC *spec, int i, GtkWidget *entry)
{
    double base;
    int err = 0;

    base = entry_get_numeric_value(entry, C_POS_DBL);
    if (na(base)) {
      err = 1;
    } else if (!na(base) && base < 1.1) {
      err = 1;
      errbox("bad base");
    } else {
      spec->logbase[i] = base;
      if (i == 0) {
          *spec->xfmt = '\0';
          *spec->xtics = '\0';
      } 
    }

    return err;
}

static void maybe_set_point_type (GPT_LINE *line, GtkWidget *w, int i)
{
    GtkWidget *ptsel = g_object_get_data(G_OBJECT(w), "pointsel");

    if (ptsel != NULL && GTK_WIDGET_SENSITIVE(ptsel)) {
      int pt = gtk_combo_box_get_active(GTK_COMBO_BOX(ptsel));
      int ptdef = (line->type == LT_NONE)? i : line->type - 1;

      if (pt != ptdef) {
          /* point-type is not just the default */
          line->ptype = pt + 1;
      }
    }
}

static void plot_editor_sync (plot_editor *ed)
{
    ed->spec->flags &= ~(GPT_FILL_SWITCH | GPT_ERR_SWITCH);

    free(ed->old_lines);
    old_lines_init(ed);

    free(ed->old_labels);
    old_labels_init(ed);
}

static void set_graph_font_from_widgets (plot_editor *ed)
{
    gchar *tmp = gtk_combo_box_get_active_text(GTK_COMBO_BOX(ed->ttfcombo));
    int ptsize = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ed->ttfspin));

    if (tmp != NULL && *tmp != '\0') {
      const char *fname = get_font_filename(tmp);
      char pngfont[128];

      if (fname != NULL && ptsize > 5 && ptsize < 25) {
          sprintf(pngfont, "%s %d", fname, ptsize);
      } else {
          *pngfont = '\0';
      }
      set_gretl_png_font(pngfont, &paths);
    }
    g_free(tmp);
}

/* Respond to "OK" or "Apply", or to hitting the Enter key in
   some parts of the plot editor dialog */

static void apply_gpt_changes (GtkWidget *w, plot_editor *ed) 
{
    GPT_SPEC *spec = ed->spec;
    GPT_LINE *line;
    int suppress_y2 = 0;
    gchar *s;
    int i, k, err = 0;

    for (i=0; i<NTITLES; i++) {
      if (ed->gpt_titles[i].widget != NULL) {
          entry_to_gp_string(ed->gpt_titles[i].widget, spec->titles[i], 
                         sizeof spec->titles[0]);
      }
    }

    if (ed->keycombo != NULL) {
        s = gtk_combo_box_get_active_text(GTK_COMBO_BOX(ed->keycombo));
      spec->keyspec = gp_keypos_from_translation(s);
      g_free(s);
    }

    spec->flags &= ~GPT_Y2AXIS;

    if (ed->y2_check != NULL && GTK_TOGGLE_BUTTON(ed->y2_check)->active) {
      suppress_y2 = 1;
    } 

    for (i=0; i<ed->gui_nlines; i++) {
      line = &spec->lines[i];
      line->yaxis = 1;
      if (!suppress_y2 && ed->yaxiscombo[i] != NULL) {
          s = gtk_combo_box_get_active_text(GTK_COMBO_BOX(ed->yaxiscombo[i]));
          if (!strcmp(s, _("right"))) {
            line->yaxis = 2;  
          }
          if (line->yaxis == 2) {
            spec->flags |= GPT_Y2AXIS;
          }
          g_free(s);
      }
    }

    if (spec->code == PLOT_REGULAR) {
      k = (spec->flags & GPT_Y2AXIS)? 3 : 2;
      for (i=0; i<k; i++) {
          if (ed->axis_range[i].isauto != NULL) {
            if (GTK_TOGGLE_BUTTON(ed->axis_range[i].isauto)->active) {
                spec->range[i][0] = NADBL;
                spec->range[i][1] = NADBL;
            } else {
                entry_to_gp_double(ed->axis_range[i].min, &spec->range[i][0]);
                entry_to_gp_double(ed->axis_range[i].max, &spec->range[i][1]);
                err = validate_range(spec->range[i]);
            }
          }
          if (ed->axis_range[i].lbase != NULL) {
            if (GTK_WIDGET_SENSITIVE(ed->axis_range[i].lbase)) {
                err = set_logscale_from_entry(spec, i, ed->axis_range[i].lbase);
            } else {
                spec->logbase[i] = 0.0;
            }
          }
      }
    }

    for (i=0; i<ed->gui_nlines && !err; i++) {
      line = &spec->lines[i];
      if (ed->stylecombo[i] != NULL) {
          int oldalt = 0;

          if (line->style == GP_STYLE_FILLEDCURVE ||
            line->style == GP_STYLE_ERRORBARS) {
            oldalt = line->style;
          }
          combo_to_gp_style(ed->stylecombo[i], &line->style);
          if (oldalt == GP_STYLE_FILLEDCURVE &&
            line->style == GP_STYLE_ERRORBARS) {
            spec->flags |= GPT_ERR_SWITCH;
            spec->flags &= ~GPT_FILL_SWITCH;
          } else if (oldalt == GP_STYLE_ERRORBARS &&
                   line->style == GP_STYLE_FILLEDCURVE) {
            spec->flags |= GPT_FILL_SWITCH;
            spec->flags &= ~GPT_ERR_SWITCH;
          }
          maybe_set_point_type(line, ed->stylecombo[i], i);
      }
      if (ed->linetitle[i] != NULL) {
          entry_to_gp_string(ed->linetitle[i], line->title, 
                         sizeof spec->lines[0].title);
      }
      if (ed->lineformula[i] != NULL && 
          GTK_WIDGET_IS_SENSITIVE(ed->lineformula[i])) {
          entry_to_gp_string(ed->lineformula[i], line->formula, 
                         sizeof spec->lines[0].formula);
      }
      if (ed->linescale[i] != NULL) {
          entry_to_gp_double(ed->linescale[i], &line->scale);
      }
      if (ed->linewidth[i] != NULL) {
          line->width = 
            gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ed->linewidth[i]));
          maybe_apply_line_color(ed->linewidth[i], spec, i);
      }
    }

    for (i=0; i<ed->gui_nlabels && !err; i++) {
      entry_to_gp_string(ed->labeltext[i], spec->labels[i].text, 
                     sizeof spec->labels[0].text);
      if (string_is_blank(spec->labels[i].text)) {
          continue;
      }
      err = get_label_pos_from_entry(ed->labelpos[i], spec->labels[i].pos);
      if (!err) {
          spec->labels[i].just = 
            gtk_combo_box_get_active(GTK_COMBO_BOX(ed->labeljust[i]));
      }
    } 

    if (!err && ed->border_check != NULL) {
      if (GTK_TOGGLE_BUTTON(ed->border_check)->active) {
          /* full border */
          spec->border = GP_BORDER_DEFAULT;
      } else {
          /* left and bottom only */
          spec->border = 3;
      }
    } 

    if (!err) {
      if (ed->fontname != NULL) {
          set_gretl_png_font(ed->fontname, &paths);
      } else if (ed->ttfcombo != NULL && ed->ttfspin != NULL) {
          set_graph_font_from_widgets(ed);
      }
    }

    if (!err && ed->fitcombo != NULL && GTK_WIDGET_IS_SENSITIVE(ed->fitcombo)) {
      fittype_from_combo(GTK_COMBO_BOX(ed->fitcombo), spec);
    }

    if (!err) {
      png_plot *plot = (png_plot *) spec->ptr;

      set_plot_has_y2_axis(plot, spec->flags & GPT_Y2AXIS);
      redisplay_edited_plot(plot);
      mark_session_changed();
    }

    plot_editor_sync(ed);
}

static void set_keyspec_sensitivity (plot_editor *ed)
{
    gboolean state = FALSE;
    const char *p;
    int i;

    for (i=0; i<ed->gui_nlines; i++) {
      p = gtk_entry_get_text(GTK_ENTRY(ed->linetitle[i]));
      if (p != NULL && *p != 0) {
          state = TRUE;
          break;
      }
    }

    gtk_widget_set_sensitive(ed->keycombo, state);
}

#define TAB_MAIN_COLS 3

struct font_info {
    const char *fname;
    const char *showname;
};

static struct font_info ttf_fonts[] = {
    { "arial", "Arial", },
    { "georgia", "Georgia", },
#ifndef G_OS_WIN32
    { "luxirr", "Luxi Serif" },
    { "luxisr", "Luxi Sans" },
    { "Vera", "Vera" },
    { "FreeSans", "Free Sans" },
    { "DejaVu", "DejaVu Sans" },
#endif
    { "tahoma", "Tahoma" },
    { "trebuc", "Trebuchet" },
    { "verdana", "Verdana" }
};

static const char *get_font_filename (const char *showname)
{
    int i, nfonts = sizeof ttf_fonts / sizeof ttf_fonts[0];

    for (i=0; i<nfonts; i++) {
      if (!strcmp(ttf_fonts[i].showname, showname)) {
          return ttf_fonts[i].fname;
      }
    }
    return NULL;
}

#ifdef G_OS_WIN32

static void graph_font_selector (GtkButton *button, plot_editor *ed)
{
    char fontname[128];

    strcpy(fontname, gretl_png_font());
    win32_font_selector(fontname, APP_FONT_SELECTION);

    if (*fontname != '\0') {
      gchar *title = g_strdup_printf(_("font: %s"), fontname);

      gtk_button_set_label(button, title);
      g_free(title);
      g_free(ed->fontname);
      ed->fontname = g_strdup(fontname);
    }
}

#else /* !G_OS_WIN32 */

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 14
#define gtk_font_selection_dialog_get_ok_button(f) (f->ok_button)
#define gtk_font_selection_dialog_get_cancel_button(f) (f->cancel_button)
#endif

static void graph_font_selection_ok (GtkWidget *w, GtkFontSelectionDialog *fs)
{
    gchar *fontname;

    fontname = gtk_font_selection_dialog_get_font_name(fs);

    if (fontname != NULL && *fontname != '\0') {
      GtkWidget *b = g_object_get_data(G_OBJECT(fs), "launcher");
      plot_editor *ed = g_object_get_data(G_OBJECT(fs), "editor");
      gchar *title = g_strdup_printf(_("font: %s"), fontname);

      gtk_button_set_label(GTK_BUTTON(b), title);
      g_free(title);
      g_free(ed->fontname);
      ed->fontname = fontname;
      fontname = NULL;
    }

    g_free(fontname);
    gtk_widget_destroy(GTK_WIDGET(fs));
}

static void graph_font_selector (GtkButton *button, plot_editor *ed)
{
    static GtkWidget *fontsel = NULL;
    GtkWidget *b;

    if (fontsel != NULL) {
      gtk_window_present(GTK_WINDOW(fontsel));
        return;
    }

    fontsel = gtk_font_selection_dialog_new(_("Font for graphs"));
    gtk_font_selection_dialog_set_font_name(GTK_FONT_SELECTION_DIALOG(fontsel), 
                                  gretl_png_font());
    g_object_set_data(G_OBJECT(fontsel), "launcher", button);
    g_object_set_data(G_OBJECT(fontsel), "editor", ed);

    gtk_window_set_position(GTK_WINDOW(fontsel), GTK_WIN_POS_MOUSE);

    g_signal_connect(G_OBJECT(fontsel), "destroy",
                 G_CALLBACK(gtk_widget_destroyed),
                 &fontsel);
    g_signal_connect(G_OBJECT(fontsel), "destroy",
                 G_CALLBACK(gtk_main_quit), NULL);
    b = gtk_font_selection_dialog_get_ok_button(GTK_FONT_SELECTION_DIALOG(fontsel));
    g_signal_connect(G_OBJECT(b), "clicked", G_CALLBACK(graph_font_selection_ok), 
                 fontsel);
    b = gtk_font_selection_dialog_get_cancel_button(GTK_FONT_SELECTION_DIALOG(fontsel));
    g_signal_connect(G_OBJECT(b), "clicked", G_CALLBACK(delete_widget), 
                 fontsel);

    gtk_widget_show(fontsel);

    gtk_main();
}

static int font_is_ok (const char *fname)
{
    static int pngterm;
    gchar *cmd;
    int err;

    if (pngterm == 0) {
      pngterm = gnuplot_png_terminal();
    }

    if (pngterm == GP_PNG_CAIRO) {
      cmd = g_strdup_printf("set term pngcairo font \"%s,10\"", fname);
    } else {
      cmd = g_strdup_printf("set term png font %s 10", fname);
    }

    err = gnuplot_test_command(cmd);
    g_free(cmd);

    return err == 0;
}

#endif

static struct font_info *get_gnuplot_ttf_list (int *nf)
{
    static struct font_info *retlist = NULL;
    static int goodfonts = -1;

    if (goodfonts >= 0) {
      *nf = goodfonts;
    } else {
      int i, j, nfonts = sizeof ttf_fonts / sizeof ttf_fonts[0];

      retlist = malloc(nfonts * sizeof *retlist);
      if (retlist == NULL) return NULL;

      j = 0;
      for (i=0; i<nfonts; i++) {
#ifdef G_OS_WIN32
          retlist[j++] = ttf_fonts[i];
#else
          if (font_is_ok(ttf_fonts[i].fname)) {
            retlist[j++] = ttf_fonts[i];
          }
#endif
      }
      goodfonts = j;
      *nf = goodfonts;
    }

    return retlist;
}

static int font_match (const char *ttfname, const char *pngfont)
{
    return !strncmp(ttfname, pngfont, strlen(ttfname));
}

static int get_point_size (const char *font)
{
    int pts;

    if (sscanf(font, "%*s %d\n", &pts) == 1) {
      return pts;
    } else {
      return 10;
    }
}

static void strip_lr (gchar *txt)
{
    gchar test[16];
    gchar *p;

    sprintf(test, "(%s)", _("left"));
    p = strstr(txt, test);
    if (p != NULL) {
      *p = '\0';
    } else {
      sprintf(test, "(%s)", _("right"));
      p = strstr(txt, test);
      if (p != NULL) {
         *p = '\0';
      }
    } 
}

static void toggle_axis_selection (GtkWidget *w, plot_editor *ed)
{
    int no_y2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
    int i;

    for (i=0; i<ed->gui_nlines; i++) {
      if (ed->yaxiscombo[i] != NULL) {
          gtk_widget_set_sensitive(ed->yaxiscombo[i], !no_y2);
      }
    }
}

/* re-establish the default plot colors and reset the
   color selection buttons accordingly */

static void color_default_callback (GtkWidget *w, GtkWidget *book)
{
    GtkWidget *button;
    char id[32];
    int i;

    sprintf(id, "color-button%d", BOXCOLOR);
    button = g_object_get_data(G_OBJECT(book), id);

    if (button != NULL) {
      graph_palette_reset(BOXCOLOR);
      color_patch_button_reset(button, BOXCOLOR);
    } else {
      for (i=0; i<BOXCOLOR; i++) {
          sprintf(id, "color-button%d", i);
          button = g_object_get_data(G_OBJECT(book), id);
          if (button != NULL) {
            if (i == 0) {
                graph_palette_reset(i);
            }
            color_patch_button_reset(button, i);
          }
      }
    }
}

static void table_add_row (GtkWidget *tbl, int *rows, int cols)
{
    *rows += 1;
    gtk_table_resize(GTK_TABLE(tbl), *rows, cols);    
}

static void add_color_selector (int i, GtkWidget *tbl, int cols,
                        int *rows, GtkWidget *notebook)
{
    static int r0;
    int collen = (N_GP_COLORS - 1) / 2;
    GtkWidget *button, *hbox;
    GtkWidget *label;
    int row, cmin, cmax;
    char str[32];

    if (i == 0) {
      /* get baseline table row for color selectors */
      r0 = *rows;
    }

    if (i < collen || i == BOXCOLOR) {
      table_add_row(tbl, rows, cols);
      cmin = 0;
      cmax = 1;
      row = *rows;
    } else {
      /* place selector to the right */
      row = r0 + 1 + i - collen;
      cmin = 1;
      cmax = 2;
    }

    hbox = gtk_hbox_new(FALSE, 2);

    if (i == BOXCOLOR) {
      strcpy(str, _("Fill color"));
    } else {
      sprintf(str, _("Color %d"), i + 1);
    }

    label = gtk_label_new(str);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_widget_show(label);

    button = color_patch_button(i);
    gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
    gtk_widget_show_all(button);
    gtk_table_attach_defaults(GTK_TABLE(tbl), hbox, cmin, cmax, 
                        row - 1, row);
    gtk_widget_show(hbox);

    sprintf(str, "color-button%d", i);
    g_object_set_data(G_OBJECT(notebook), str, button);

    if (i == BOXCOLOR || i == BOXCOLOR - 1) {
      table_add_row(tbl, rows, TAB_MAIN_COLS);
      hbox = gtk_hbox_new(FALSE, 2);
      button = gtk_button_new_with_label(_("Reset to default"));
      gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 10);
      g_signal_connect(G_OBJECT(button), "clicked", 
                   G_CALLBACK(color_default_callback), 
                   notebook);
      gtk_table_attach(GTK_TABLE(tbl), hbox, 0, 2, *rows - 1, *rows,
                   GTK_FILL, 0, 0, 5);
      gtk_widget_show(button);
      gtk_widget_show(hbox);
    }
}

/* PNG anti-aliasing switch */

static void set_aa_status (GtkWidget *w, int *ok)
{
    *ok = GTK_TOGGLE_BUTTON(w)->active;

    gnuplot_png_set_use_aa(*ok);
}

static GtkWidget *gp_dialog_table (int rows, int cols, 
                           GtkWidget *vbox)
{
    GtkWidget *tbl;

    tbl = gtk_table_new(rows, cols, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(tbl), 5);
    gtk_table_set_col_spacings(GTK_TABLE(tbl), 5);
    gtk_box_pack_start(GTK_BOX(vbox), tbl, FALSE, FALSE, 0);

    return tbl;
}

static GtkWidget *gp_dialog_vbox (void)
{
    GtkWidget *vbox;

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);

    return vbox;
}

static GtkWidget *gp_page_vbox (GtkWidget *notebook, char *str)
{
    GtkWidget *vbox;
    GtkWidget *label;

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
    gtk_widget_show(vbox);
    label = gtk_label_new(str);
    gtk_widget_show(label);
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, label); 

    return vbox;
}

static void add_gd_font_selector (plot_editor *ed, GtkWidget *tbl, int *rows)
{
    GtkWidget *ebox, *entry, *label;
    GList *fontnames = NULL;
    struct font_info *ttflist;
    const char *default_font = NULL;
    int i, nfonts = 0;

    ttflist = get_gnuplot_ttf_list(&nfonts);

    for (i=0; i<nfonts; i++) {
      fontnames = g_list_append(fontnames, (gpointer) ttflist[i].showname);
      if (font_match(ttflist[i].fname, gretl_png_font())) {
          default_font = ttflist[i].showname;
      }
    }

    fontnames = g_list_append(fontnames, _("None"));
    if (default_font == NULL) {
      default_font = _("None");
    }

    table_add_row(tbl, rows, TAB_MAIN_COLS);
    ebox = gtk_event_box_new();
    label = gtk_label_new(_("TrueType font"));
    gtk_container_add(GTK_CONTAINER(ebox), label);
    gtk_table_attach_defaults(GTK_TABLE (tbl), ebox, 0, 1, 
                        *rows-1, *rows);
    gtk_widget_show(label);
    gtk_widget_show(ebox);

    /* FIXME max length of font name? */

    ed->ttfcombo = gtk_combo_box_entry_new_text();
    entry = gtk_bin_get_child(GTK_BIN(ed->ttfcombo));
    gtk_entry_set_max_length(GTK_ENTRY(entry), 15);
    gtk_table_attach_defaults(GTK_TABLE(tbl), ed->ttfcombo, 1, 2, 
                        *rows-1, *rows);
    set_combo_box_strings_from_list(GTK_COMBO_BOX(ed->ttfcombo), fontnames); 
    gtk_entry_set_text(GTK_ENTRY(entry), default_font);
    gtk_entry_set_width_chars(GTK_ENTRY(entry), 15);
    g_signal_connect(G_OBJECT(entry), "activate", 
                 G_CALLBACK(apply_gpt_changes), 
                 ed);
    gtk_widget_show(ed->ttfcombo);
    g_list_free(fontnames);

    ed->ttfspin = gtk_spin_button_new_with_range(6, 24, 1);
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(ed->ttfspin), 
                        get_point_size(gretl_png_font()));
    gtk_table_attach_defaults(GTK_TABLE(tbl), ed->ttfspin, 2, 3, 
                        *rows-1, *rows);
    gtk_widget_show(ed->ttfspin);
}

static void gpt_tab_main (plot_editor *ed, GPT_SPEC *spec) 
{
    static int aa_ok = 1;
    static int show_aa_check = -1;
    static int pngcairo = -1;
    GtkWidget *label, *vbox, *tbl;
    int i, rows = 1;
    int kactive = 0;

    if (pngcairo < 0) {
      pngcairo = (gnuplot_png_terminal() == GP_PNG_CAIRO);
    }

    vbox = gp_page_vbox(ed->notebook, _("Main"));

    tbl = gp_dialog_table(rows, TAB_MAIN_COLS, vbox);
    gtk_widget_show(tbl);
   
    for (i=0; i<NTITLES; i++) {
      if (ed->gpt_titles[i].tab == 0) {
          GtkWidget *entry;

          if (i > 0) {
            table_add_row(tbl, &rows, TAB_MAIN_COLS);
          }

          label = gtk_label_new(_(ed->gpt_titles[i].desc));
          gtk_table_attach_defaults(GTK_TABLE (tbl), 
                              label, 0, 1, rows-1, rows);
          gtk_widget_show(label);

          entry = gtk_entry_new();
          gtk_table_attach_defaults(GTK_TABLE(tbl), 
                              entry, 1, TAB_MAIN_COLS, 
                              rows-1, rows);
                              
            if (spec->titles[i] != NULL && *spec->titles[i] != '\0') {
            gp_string_to_entry(entry, spec->titles[i]);
            }           

          g_signal_connect(G_OBJECT(entry), "activate", 
                       G_CALLBACK(apply_gpt_changes), 
                       ed);

          gtk_widget_show(entry);
          ed->gpt_titles[i].widget = entry;
      }
    }

    /* specify position of plot key or legend */
    table_add_row(tbl, &rows, TAB_MAIN_COLS);
    label = gtk_label_new(_("key position"));
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 0, 1, rows-1, rows);
    gtk_widget_show(label);

    ed->keycombo = gtk_combo_box_new_text();
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        ed->keycombo, 1, TAB_MAIN_COLS, rows-1, rows);
    for (i=0; ; i++) {
      gp_style_spec *kp = get_keypos_spec(i);

      if (kp == NULL) {
          break;
      }
      gtk_combo_box_append_text(GTK_COMBO_BOX(ed->keycombo), _(kp->str));
      if (kp->sty == spec->keyspec) {
          kactive = i;
      }
    }
    gtk_combo_box_set_active(GTK_COMBO_BOX(ed->keycombo), kactive);
    gtk_widget_show(ed->keycombo);

    /* choice of fitted line type, if appropriate */
    if (spec->fit != PLOT_FIT_NA) {
      table_add_row(tbl, &rows, TAB_MAIN_COLS);

      label = gtk_label_new(_("fitted line"));
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          label, 0, 1, rows-1, rows);
      gtk_widget_show(label);

      ed->fitcombo = gtk_combo_box_new_text();
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->fitcombo, 1, TAB_MAIN_COLS, rows-1, rows);
      for (i=0; fittype_strings[i] != NULL; i++) {
          gtk_combo_box_append_text(GTK_COMBO_BOX(ed->fitcombo), 
                              _(fittype_strings[i]));
      }
      gtk_combo_box_set_active(GTK_COMBO_BOX(ed->fitcombo), spec->fit);
      widget_set_int(ed->fitcombo, "oldfit", spec->fit);
      g_signal_connect(G_OBJECT(ed->fitcombo), "changed",
                   G_CALLBACK(fit_type_changed), ed);
      gtk_widget_show(ed->fitcombo);
    } 

    /* give option of removing/adding top & right border? */
    if (!(spec->flags & GPT_Y2AXIS)) { 
      if (spec->border == GP_BORDER_DEFAULT || spec->border == 3) {
          table_add_row(tbl, &rows, TAB_MAIN_COLS);
          ed->border_check = gtk_check_button_new_with_label(_("Show full border"));
          gtk_table_attach_defaults(GTK_TABLE(tbl), 
                              ed->border_check, 0, TAB_MAIN_COLS, 
                              rows-1, rows);
          gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ed->border_check),
                               spec->border == GP_BORDER_DEFAULT);
          gtk_widget_show(ed->border_check);
      }
    } else {
      table_add_row(tbl, &rows, TAB_MAIN_COLS);
      ed->y2_check = gtk_check_button_new_with_label(_("Use only one y axis"));
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->y2_check, 0, TAB_MAIN_COLS, 
                          rows-1, rows);
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ed->y2_check),
                             FALSE);
      g_signal_connect(G_OBJECT(ed->y2_check), "clicked", 
                   G_CALLBACK(toggle_axis_selection), ed);
      gtk_widget_show(ed->y2_check);
    }

    if (show_aa_check < 0) {
      show_aa_check = (gnuplot_png_terminal() == GP_PNG_GD2);
    }

    /* give option of suppressing anti-aliasing for PNGs */
    if (show_aa_check) {
      GtkWidget *aa_check;

      table_add_row(tbl, &rows, TAB_MAIN_COLS);
      aa_check = gtk_check_button_new_with_label(_("Allow anti-aliasing of lines"));
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          aa_check, 0, TAB_MAIN_COLS, 
                          rows-1, rows);
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(aa_check),
                             aa_ok);
      g_signal_connect(G_OBJECT(aa_check), "clicked", 
                   G_CALLBACK(set_aa_status), &aa_ok);
      gtk_widget_show(aa_check);
    }

    /* setting of graph font, if gnuplot uses freetype */
    if (pngcairo || gnuplot_has_ttf(0)) {
      GtkWidget *hsep;

      /* first a separator */
      table_add_row(tbl, &rows, TAB_MAIN_COLS); 
      hsep = gtk_hseparator_new();
      gtk_table_attach_defaults(GTK_TABLE(tbl), hsep, 0, TAB_MAIN_COLS, 
                          rows-1, rows);  
      gtk_widget_show(hsep);

      if (pngcairo) {
          gchar *title = g_strdup_printf(_("font: %s"), gretl_png_font());
          GtkWidget *b = gtk_button_new_with_label(title);

          table_add_row(tbl, &rows, TAB_MAIN_COLS);
          gtk_table_attach_defaults(GTK_TABLE(tbl), b, 0, TAB_MAIN_COLS, 
                              rows - 1, rows); 
          g_signal_connect(G_OBJECT(b), "clicked", 
                       G_CALLBACK(graph_font_selector), 
                       ed);
          gtk_widget_show(b);
          g_free(title);
      } else {
          add_gd_font_selector(ed, tbl, &rows);
      }
    } 

    if (gnuplot_png_terminal() != GP_PNG_OLD && frequency_plot_code(spec->code)) {
      GtkWidget *hsep = gtk_hseparator_new();

      table_add_row(tbl, &rows, TAB_MAIN_COLS);
      gtk_table_attach_defaults(GTK_TABLE(tbl), hsep, 0, TAB_MAIN_COLS, 
                          rows - 1, rows);  
      gtk_widget_show(hsep);

      add_color_selector(BOXCOLOR, tbl, TAB_MAIN_COLS, &rows, 
                     ed->notebook);
    }
}

static void linetitle_callback (GtkWidget *w, plot_editor *ed)
{
    set_keyspec_sensitivity(ed);
}

struct new_line_info_ {
    plot_editor *editor;
    GtkWidget *formula_entry;
    GtkWidget *dlg;
};

typedef struct new_line_info_ new_line_info;

static void gpt_tab_new_line (plot_editor *ed, new_line_info *nlinfo) 
{
    GtkWidget *label, *tbl;
    GtkWidget *vbox, *hbox;
    int tbl_len, tbl_num, tbl_col;
    char label_text[32];

    vbox = GTK_DIALOG(nlinfo->dlg)->vbox;
    hbox = gtk_hbox_new(FALSE, 5);

    tbl_len = 1;
    tbl = gtk_table_new(tbl_len, 3, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(tbl), 5);
    gtk_table_set_col_spacings(GTK_TABLE(tbl), 5);
    gtk_box_pack_start(GTK_BOX(hbox), tbl, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    gtk_widget_show(hbox);
    gtk_widget_show(tbl);
   
    tbl_num = tbl_col = 0;

    /* identifier and formula text */
    tbl_len++;
    gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
    sprintf(label_text, _("line %d: "), ed->gui_nlines + 1);
    label = gtk_label_new(label_text);
    gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 0, 1, tbl_len-1, tbl_len);
    gtk_widget_show(label);

    label = gtk_label_new(_("formula"));
    gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 1, 2, tbl_len-1, tbl_len);
    gtk_widget_show(label);

    nlinfo->formula_entry = gtk_entry_new();
    gtk_entry_set_text(GTK_ENTRY(nlinfo->formula_entry), "");
    gtk_entry_set_width_chars(GTK_ENTRY(nlinfo->formula_entry), 32);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        nlinfo->formula_entry, 2, 3, tbl_len-1, tbl_len);
    gtk_entry_set_activates_default(GTK_ENTRY(nlinfo->formula_entry), TRUE);
    gtk_widget_show(nlinfo->formula_entry);
}

/* back up exiting spec lines / labels in case changes are not
   applied and we need to restore the prior state */

static int make_labels_backup (plot_editor *ed)
{
    int err = 0;

    if (labels_not_synced(ed)) {
      ed->old_labels = plotspec_clone_labels(ed->spec, &err);
      if (!err) {
          ed->old_n_labels = ed->spec->n_labels;
      }
    }

    return err;
}

static int make_lines_backup (plot_editor *ed)
{
    int err = 0;

    if (lines_not_synced(ed)) {
      ed->old_lines = plotspec_clone_lines(ed->spec, &err);
      if (!err) {
          ed->old_n_lines = ed->spec->n_lines;
      }
    }

    return err;
}

static int allocate_label (plot_editor *ed)
{
    int err;

    err = make_labels_backup(ed);

    if (!err) {
      err = plotspec_add_label(ed->spec);
    }

    if (!err) {
      err = add_label_widget(ed);
      if (err) {
          ed->spec->n_labels -= 1;
      }
    }

    if (err) {
      nomem();
    }

    return err;
}

static int allocate_line (plot_editor *ed)
{
    int err;

    err = make_lines_backup(ed);

    if (!err) {
      err = plotspec_add_line(ed->spec);
    }

    if (!err) {
      err = add_line_widget(ed);
      if (err) {
          ed->spec->n_lines -= 1;
      }
    }

    if (err) {
      nomem();
    }

    return err;
}

static void add_label_callback (GtkWidget *w, plot_editor *ed)
{
    gint pgnum;
    int err;

    err = allocate_label(ed);

    if (!err) {
      /* re-fill the "labels" notebook page */
      pgnum = widget_get_int(ed->notebook, "labels_page");
      gtk_notebook_remove_page(GTK_NOTEBOOK(ed->notebook), pgnum);
      gpt_tab_labels(ed, ed->spec, pgnum);
      gtk_notebook_set_current_page(GTK_NOTEBOOK(ed->notebook), pgnum);
    }
}

static void real_add_line (GtkWidget *w, new_line_info *nlinfo)
{
    plot_editor *ed = nlinfo->editor;
    GPT_SPEC *spec = ed->spec;
    GPT_LINE *line;
    const gchar *s;
    gint pgnum;
    int err = 0;

    s = gtk_entry_get_text(GTK_ENTRY(nlinfo->formula_entry));
    if (s == NULL || *s == '\0') {
      errbox(_("No formula was given"));
      return;
    }

    err = allocate_line(ed);
    if (err) {
      gtk_widget_destroy(nlinfo->dlg);
      return;
    }

    line = &spec->lines[spec->n_lines - 1];

    entry_to_gp_string(nlinfo->formula_entry, line->formula, GP_MAXFORMULA);

    line->style = GP_STYLE_LINES;
    line->type = spec->n_lines; /* assign next line style */
    line->scale = NADBL;        /* mark as a non-data line */
    line->flags = GP_LINE_USER;

    /* re-fill the "lines" notebook page */
    pgnum = widget_get_int(ed->notebook, "lines_page");
    gtk_notebook_remove_page(GTK_NOTEBOOK(ed->notebook), pgnum);
    gpt_tab_lines(ed, spec, pgnum);
    gtk_notebook_set_current_page(GTK_NOTEBOOK(ed->notebook), pgnum);
    if (spec->n_lines == 2) {
      /* user-defined line has taken the place of a potential fitted line */
      spec->fit = PLOT_FIT_NA;
      if (ed->fitcombo != NULL) {
          gtk_widget_set_sensitive(ed->fitcombo, FALSE);
      }
    }

    gtk_widget_destroy(nlinfo->dlg);
}

static void add_line_callback (GtkWidget *w, plot_editor *ed)
{
    new_line_info *nlinfo;
    GtkWidget *hbox;
    GtkWidget *button;

    nlinfo = mymalloc(sizeof *nlinfo);
    if (nlinfo == NULL) {
      return;
    }

    nlinfo->editor = ed;

    nlinfo->dlg = gretl_dialog_new(NULL, ed->dialog, GRETL_DLG_BLOCK);

    gpt_tab_new_line(ed, nlinfo);

    hbox = GTK_DIALOG(nlinfo->dlg)->action_area;

    button = cancel_button(hbox);
    g_signal_connect(G_OBJECT(button), "clicked", 
                 G_CALLBACK(delete_widget), nlinfo->dlg);
    gtk_widget_show(button);

    button = ok_button(hbox);
    g_signal_connect(G_OBJECT(button), "clicked",
                 G_CALLBACK(real_add_line), nlinfo);
    gtk_widget_grab_default(button);
    gtk_widget_show(button);

    context_help_button(hbox, GPT_ADDLINE);

    gtk_widget_show(nlinfo->dlg);

    free(nlinfo);
}

static void remove_line (GtkWidget *w, plot_editor *ed)
{
    if (make_lines_backup(ed) != 0) {
      nomem();
    } else {
      int pgnum = widget_get_int(ed->notebook, "lines_page");

      plotspec_delete_line(ed->spec, widget_get_int(w, "linenum"));
      ed->gui_nlines -= 1;

      /* refresh the associated notebook page */
      gtk_notebook_remove_page(GTK_NOTEBOOK(ed->notebook), pgnum);
      gpt_tab_lines(ed, ed->spec, pgnum);
      gtk_notebook_set_current_page(GTK_NOTEBOOK(ed->notebook), pgnum);
    }
}

static void remove_label (GtkWidget *w, plot_editor *ed)
{
    if (make_labels_backup(ed) != 0) {
      nomem();
    } else {
      int pgnum = widget_get_int(ed->notebook, "labels_page");

      plotspec_delete_label(ed->spec, widget_get_int(w, "labelnum"));
      ed->gui_nlabels -= 1;

      /* refresh the associated notebook page */
      gtk_notebook_remove_page(GTK_NOTEBOOK(ed->notebook), pgnum);
      gpt_tab_labels(ed, ed->spec, pgnum);
      gtk_notebook_set_current_page(GTK_NOTEBOOK(ed->notebook), pgnum);
    }
}

static void item_remove_button (GtkWidget *tbl, int row, 
                        plot_editor *ed, int i,
                        int j)
{
    GtkWidget *button; 
    const gchar *key[] = {
      "linenum",
      "labelnum"
    };
    GCallback cb[] = {
      G_CALLBACK(remove_line),
      G_CALLBACK(remove_label)
    };

    button = gtk_button_new_with_label(_("Remove"));
    widget_set_int(button, key[j], i);
    g_signal_connect(G_OBJECT(button), "clicked",
                 G_CALLBACK(cb[j]), ed);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        button, 0, 1, row - 1, row);
    gtk_widget_show(button);
}

static void print_line_label (GtkWidget *tbl, int row, GPT_SPEC *spec, 
                        int i)
{
    char label_text[32];
    GtkWidget *label; 

    if (spec->code == PLOT_BOXPLOTS) {
      if (i == 0) {
          sprintf(label_text, "%s: ", _("quartiles"));
      } else if (i == 1) {
          sprintf(label_text, "%s: ", _("median"));
      } else if (i == 2 && spec->n_lines == 3) {
          sprintf(label_text, "%s: ", _("mean"));
      } else if (i == 2) {
          sprintf(label_text, "%s: ", _("lower bound"));
      } else if (i == 3) {
          sprintf(label_text, "%s: ", _("upper bound"));
      }
    } else {
      sprintf(label_text, _("line %d: "), i+1);
    }

    label = gtk_label_new(label_text);
    gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 0, 1, row - 1, row);
    gtk_widget_show(label);
}

static void print_label_label (GtkWidget *tbl, int row, GPT_SPEC *spec, 
                         int i)
{
    char label_text[32];
    GtkWidget *label; 

    sprintf(label_text, _("label %d: "), i + 1);
    label = gtk_label_new(label_text);
    gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 0, 1, row - 1, row);
    gtk_widget_show(label);
}

static void print_field_label (GtkWidget *tbl, int row,
                         const gchar *text)
{
    GtkWidget *label; 

    label = gtk_label_new(text);
    gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 1, 2, row - 1, row);
    gtk_widget_show(label);
}

static GtkWidget *dash_types_combo (void)
{
    GtkWidget *dotsel;
    GtkCellRenderer *cell;
    GtkListStore *store;
    GtkTreeIter iter;
    GdkPixbuf *pbuf;
    int i;

    store = gtk_list_store_new(1, GDK_TYPE_PIXBUF);

    for (i=0; i<2; i++) {
      gtk_list_store_append(store, &iter);
      pbuf = get_pixbuf_for_line(i);
      gtk_list_store_set(store, &iter, 0, pbuf, -1);
      g_object_unref(pbuf);
    }

    dotsel = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
    cell = gtk_cell_renderer_pixbuf_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dotsel), cell, FALSE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dotsel), cell,
                           "pixbuf", 0, NULL);
    
    return dotsel;
}

static GtkWidget *point_types_combo (void)
{
    GtkWidget *ptsel;
    GtkCellRenderer *cell;
    GtkListStore *store;
    GtkTreeIter iter;
    GdkPixbuf *pbuf;
    int i;

    store = gtk_list_store_new(1, GDK_TYPE_PIXBUF);

    for (i=0; i<13; i++) {
      gtk_list_store_append(store, &iter);
      pbuf = gdk_pixbuf_from_pixdata(gppoints[i], FALSE, NULL);
      gtk_list_store_set(store, &iter, 0, pbuf, -1);
      g_object_unref(pbuf);
    }

    ptsel = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
    cell = gtk_cell_renderer_pixbuf_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ptsel), cell, FALSE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ptsel), cell,
                           "pixbuf", 0, NULL);
    
    return ptsel;
}

static int line_get_point_style (GPT_LINE *line, int i)
{
    if (line->ptype > 0) {
      /* a specific point-style has been selected: convert
         to zero-based */
      return line->ptype - 1;
    } else if (line->type == LT_NONE) {
      /* line type is set by placement of line in plot */
      return i;
    } else {
      /* a specific line-type has been selected: give the 
         associated point-style */
      return line->type - 1;
    }
}

#define has_point(s) (s == GP_STYLE_POINTS || s == GP_STYLE_LINESPOINTS)

static void flip_pointsel (GtkWidget *box, GtkWidget *targ)
{
    GtkWidget *label = g_object_get_data(G_OBJECT(targ), "label");
    gchar *s = gtk_combo_box_get_active_text(GTK_COMBO_BOX(box));
    int hp = has_point(gp_style_from_translation(s));

    gtk_widget_set_sensitive(targ, hp);
    gtk_widget_set_sensitive(label, hp);
}

static GtkWidget *scroller_page (GtkWidget *vbox)
{
    GtkWidget *scroller;
      
    scroller = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
                           GTK_POLICY_NEVER, 
                           GTK_POLICY_AUTOMATIC);
    gtk_widget_show(scroller);
    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroller), 
                                vbox);
    return scroller;
}

static int show_axis_chooser (GPT_SPEC *spec)
{
    int i, s = 0;

    if (spec->code == PLOT_REGULAR && (spec->flags & GPT_TS)) {
      s = 1;
    } else {
      for (i=0; i<spec->n_lines; i++) {
          if (spec->lines[i].yaxis == 2) {
            s = 1;
            break;
          }
      }
    }

    return s;
}

static int gp_style_index (int sty, GList *list)
{
    gp_style_spec *spec;
    GList *mylist = list;
    int i = 0;

    while (mylist != NULL) {
      spec = (gp_style_spec *) mylist->data;
      if (sty == spec->sty) {
          return i;
      }
      i++;
      mylist = mylist->next;
    }

    return 0;
}

void set_combo_box_strings_from_stylist (GtkComboBox *box, GList *list)
{
    gp_style_spec *spec;
    GList *mylist = list;

    while (mylist != NULL) {
      spec = (gp_style_spec *) mylist->data;
      gtk_combo_box_append_text(box, _(spec->str));
      mylist = mylist->next;
    }
}

static GList *add_style_spec (GList *list, int t)
{
    return g_list_append(list, get_style_spec(t));
}

#define line_is_formula(l) (l->formula[0] != '\0' || (l->flags & GP_LINE_USER))

static void gpt_tab_lines (plot_editor *ed, GPT_SPEC *spec, int ins)
{
    GtkWidget *notebook = ed->notebook;
    GtkWidget *label, *tbl;
    GtkWidget *vbox, *hbox, *sep;
    GtkWidget *button, *page;
    int i, tbl_len, tbl_num, tbl_col;
    GList *stylist = NULL;
    int axis_chooser;
    int pgnum = -1;

    axis_chooser = show_axis_chooser(spec);

    if (frequency_plot_code(spec->code)) {
      stylist = add_style_spec(stylist, GP_STYLE_BOXES);
    }

    if (spec->flags & GPT_TS) {
      stylist = add_style_spec(stylist, GP_STYLE_LINES);
      stylist = add_style_spec(stylist, GP_STYLE_POINTS);
    } else {
      stylist = add_style_spec(stylist, GP_STYLE_POINTS);
      stylist = add_style_spec(stylist, GP_STYLE_LINES);
    }

    stylist = add_style_spec(stylist, GP_STYLE_LINESPOINTS); 
    stylist = add_style_spec(stylist, GP_STYLE_IMPULSES);
    stylist = add_style_spec(stylist, GP_STYLE_DOTS);
    stylist = add_style_spec(stylist, GP_STYLE_STEPS);

    if (!frequency_plot_code(spec->code)) {
      stylist = add_style_spec(stylist, GP_STYLE_BOXES);
    }

    vbox = gp_dialog_vbox();
    gtk_widget_show(vbox);

    label = gtk_label_new(_("Lines"));
    gtk_widget_show(label);

    if (ed->gui_nlines > 4) {
      page = scroller_page(vbox);
    } else {
      page = vbox;
    }

    if (ins > 0) {
      pgnum = gtk_notebook_insert_page(GTK_NOTEBOOK(notebook), page, label, ins); 
    } else {
      pgnum = gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);
    }  

    widget_set_int(notebook, "lines_page", pgnum);

    tbl_len = 1;
    tbl = gp_dialog_table(tbl_len, 3, vbox);
    gtk_widget_show(tbl);
   
    tbl_num = tbl_col = 0;

    for (i=0; i<ed->gui_nlines; i++) {
      GPT_LINE *line = &spec->lines[i];
      int label_done = 0;

      if (line_is_formula(line)) {
          tbl_len++;
          gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);

          print_line_label(tbl, tbl_len, spec, i);
          label_done = 1;

          print_field_label(tbl, tbl_len, _("formula"));

          ed->lineformula[i] = gtk_entry_new();
          gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->lineformula[i], 2, 3, tbl_len-1, tbl_len);
          strip_lr(line->formula);
          gp_string_to_entry(ed->lineformula[i], line->formula);
          if (i == 1 && (spec->flags & GPT_AUTO_FIT)) {
            /* fitted formula: not GUI-editable */
            gtk_widget_set_sensitive(ed->lineformula[i], FALSE);
            ed->fitformula = ed->lineformula[i];
          } else {
            g_signal_connect(G_OBJECT(ed->lineformula[i]), "activate", 
                         G_CALLBACK(apply_gpt_changes), 
                         ed);
          }
          gtk_widget_show(ed->lineformula[i]);
      }

      /* identifier and key or legend text */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);

      if (!label_done) {
          print_line_label(tbl, tbl_len, spec, i);
      }

      if (line->flags & GP_LINE_USER) {
          item_remove_button(tbl, tbl_len, ed, i, GUI_LINE);
      }

      print_field_label(tbl, tbl_len, _("legend"));

      ed->linetitle[i] = gtk_entry_new();
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->linetitle[i], 2, 3, tbl_len-1, tbl_len);
      strip_lr(line->title);
      gp_string_to_entry(ed->linetitle[i], line->title);
      g_signal_connect(G_OBJECT(ed->linetitle[i]), "changed", 
                   G_CALLBACK(linetitle_callback), 
                   ed);
      g_signal_connect(G_OBJECT(ed->linetitle[i]), "activate", 
                   G_CALLBACK(apply_gpt_changes), 
                   ed);
      gtk_widget_show(ed->linetitle[i]);
      if (i == 1 && (spec->flags & GPT_AUTO_FIT)) {
          ed->fitlegend = ed->linetitle[i];
      }

      if (line_is_formula(line) || 
          line->style == GP_STYLE_CANDLESTICKS) {
          goto line_width_adj;
      }

      /* line type (lines, points, etc.) */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      label = gtk_label_new(_("type"));
      gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          label, 1, 2, tbl_len-1, tbl_len);
      gtk_widget_show(label);

      hbox = gtk_hbox_new(FALSE, 5);
      ed->stylecombo[i] = gtk_combo_box_new_text();
      gtk_box_pack_start(GTK_BOX(hbox), ed->stylecombo[i], FALSE, FALSE, 0);

      /* the errorbars and filledcurves styles are not exchangeable
         with the others */
      if (line->style == GP_STYLE_ERRORBARS && !gnuplot_has_style_fill()) {
          set_combo_box_default_text(GTK_COMBO_BOX(ed->stylecombo[i]), 
                               _(gp_line_style_string(line->style)));
          gtk_widget_set_sensitive(ed->stylecombo[i], FALSE);
      } else if (line->style == GP_STYLE_ERRORBARS ||
               line->style == GP_STYLE_FILLEDCURVE) {
          GList *altsty = NULL;

          altsty = add_style_spec(altsty, GP_STYLE_ERRORBARS); 
          altsty = add_style_spec(altsty, GP_STYLE_FILLEDCURVE);
          set_combo_box_strings_from_stylist(GTK_COMBO_BOX(ed->stylecombo[i]), 
                                     altsty);
          gtk_combo_box_set_active(GTK_COMBO_BOX(ed->stylecombo[i]),
                             gp_style_index(line->style, altsty));
          g_list_free(altsty);
      } else {
          GtkWidget *ptsel = point_types_combo();
          int lt = gp_style_index(line->style, stylist);
          int hp, pt = line_get_point_style(line, i);

          set_combo_box_strings_from_stylist(GTK_COMBO_BOX(ed->stylecombo[i]), 
                                     stylist);
          gtk_combo_box_set_active(GTK_COMBO_BOX(ed->stylecombo[i]), lt);
          hp = has_point(line->style);
          label = gtk_label_new(_("point"));
          gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
          gtk_box_pack_start(GTK_BOX(hbox), ptsel, FALSE, FALSE, 0);
          g_object_set_data(G_OBJECT(ed->stylecombo[i]), "pointsel", ptsel);
          g_object_set_data(G_OBJECT(ptsel), "label", label);
          gtk_combo_box_set_active(GTK_COMBO_BOX(ptsel), pt);
          gtk_widget_set_sensitive(label, hp);
          gtk_widget_set_sensitive(ptsel, hp);
          g_signal_connect(G_OBJECT(ed->stylecombo[i]), "changed",
                       G_CALLBACK(flip_pointsel), ptsel);
      } 

      gtk_table_attach_defaults(GTK_TABLE(tbl), hbox, 2, 3, tbl_len-1, tbl_len);
      gtk_widget_show_all(hbox);    

      if (axis_chooser) {
          /* scale factor for data? */
          tbl_len++;
          gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
          print_field_label(tbl, tbl_len, _("scale"));

          ed->linescale[i] = gtk_entry_new();
          gtk_entry_set_max_length(GTK_ENTRY(ed->linescale[i]), 6);
          double_to_gp_entry(line->scale, ed->linescale[i]);
          gtk_entry_set_width_chars(GTK_ENTRY(ed->linescale[i]), 6);
          g_signal_connect(G_OBJECT(ed->linescale[i]), "activate", 
                       G_CALLBACK(apply_gpt_changes), 
                       ed);
          gtk_table_attach_defaults(GTK_TABLE(tbl), 
                              ed->linescale[i], 2, 3, tbl_len-1, tbl_len);
          gtk_widget_show(ed->linescale[i]);

          /* use left or right y axis? */
          tbl_len++;
          gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
          print_field_label(tbl, tbl_len, _("y axis"));

          ed->yaxiscombo[i] = gtk_combo_box_new_text();
          gtk_table_attach_defaults(GTK_TABLE(tbl), 
                              ed->yaxiscombo[i], 2, 3, tbl_len-1, tbl_len);
          gtk_combo_box_append_text(GTK_COMBO_BOX(ed->yaxiscombo[i]), _("left"));
          gtk_combo_box_append_text(GTK_COMBO_BOX(ed->yaxiscombo[i]), _("right"));
          gtk_combo_box_set_active(GTK_COMBO_BOX(ed->yaxiscombo[i]), 
                             (line->yaxis == 1)? 0 : 1);
          gtk_widget_show(ed->yaxiscombo[i]);
      }

    line_width_adj:

      /* line-width adjustment */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      print_field_label(tbl, tbl_len, _("line width"));

      hbox = gtk_hbox_new(FALSE, 5);
      ed->linewidth[i] = gtk_spin_button_new_with_range(1, 6, 1);
      if (line->width > 1) {
          gtk_spin_button_set_value(GTK_SPIN_BUTTON(ed->linewidth[i]),
                              line->width);
      }
      g_signal_connect(G_OBJECT(ed->linewidth[i]), "activate", 
                   G_CALLBACK(apply_gpt_changes), 
                   ed);
      gtk_box_pack_start(GTK_BOX(hbox), ed->linewidth[i], FALSE, FALSE, 0);
      gtk_widget_show(ed->linewidth[i]);
      gtk_table_attach_defaults(GTK_TABLE(tbl), hbox, 2, 3, 
                          tbl_len-1, tbl_len);
      /* line color adjustment */
      if (i < 6 && !frequency_plot_code(spec->code)) {
          button = line_color_button(spec, i);
          if (button != NULL) {
            label = gtk_label_new(_("color"));
            gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
            gtk_widget_show(label);
            gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
            g_object_set_data(G_OBJECT(ed->linewidth[i]), "colorsel",
                          button);
            gtk_widget_show_all(button);
          }
      } else {
          label = NULL;
          button = NULL;
      }

      if (line->flags & GP_LINE_USER) {
          /* dotted option */
          GtkWidget *dotcombo = dash_types_combo();

          gtk_combo_box_set_active(GTK_COMBO_BOX(dotcombo), 0); 
          gtk_widget_show(dotcombo);
          widget_set_int(dotcombo, "linenum", i);
          g_object_set_data(G_OBJECT(dotcombo), "colorsel", button);
          g_object_set_data(G_OBJECT(dotcombo), "color-label", label);
          g_signal_connect(G_OBJECT(dotcombo), "changed", 
                       G_CALLBACK(dot_callback), spec);
          gtk_box_pack_start(GTK_BOX(hbox), dotcombo, FALSE, FALSE, 5);
      }

      gtk_widget_show(hbox);

      /* separator */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      sep = gtk_hseparator_new();
      gtk_table_attach_defaults(GTK_TABLE(tbl), sep, 0, 3, 
                          tbl_len-1, tbl_len);
      gtk_widget_show(sep);
    }

    if ((spec->code == PLOT_REGULAR || spec->code == PLOT_CURVE)
      && spec->n_lines < 8) {
      /* button for adding a line (formula) */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      button = gtk_button_new_with_label(_("Add line..."));
      g_signal_connect(G_OBJECT(button), "clicked", 
                   G_CALLBACK(add_line_callback), 
                   ed);
      gtk_widget_show(button);
      gtk_table_attach_defaults(GTK_TABLE(tbl), button, 0, 1, 
                          tbl_len-1, tbl_len);
    }

    g_list_free(stylist);
}

static void label_pos_to_entry (double *pos, GtkWidget *w)
{
    if (!na(pos[0]) && !na(pos[1])) {
      gchar *s = g_strdup_printf("%.10g %.10g", pos[0], pos[1]);

      gtk_entry_set_text(GTK_ENTRY(w), s);
      g_free(s);
    } else {
      gtk_entry_set_text(GTK_ENTRY(w), "");
    }
}

static void gpt_tab_labels (plot_editor *ed, GPT_SPEC *spec, int ins) 
{
    GtkWidget *notebook = ed->notebook;
    GtkWidget *label, *tbl;
    GtkWidget *vbox, *page, *sep;
    int i, j, tbl_len, tbl_num, tbl_col;
    png_plot *plot = (png_plot *) spec->ptr;
    int pgnum = -1;

    vbox = gp_dialog_vbox();
    gtk_widget_show(vbox);

    label = gtk_label_new(_("Labels"));
    gtk_widget_show(label);

    if (ed->gui_nlabels > 4) {
      page = scroller_page(vbox);
    } else {
      page = vbox;
    }

    if (ins > 0) {
      pgnum = gtk_notebook_insert_page(GTK_NOTEBOOK(notebook), page, label, ins); 
    } else {
      pgnum = gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);
    }  

    widget_set_int(notebook, "labels_page", pgnum);
 
    tbl_len = 1;
    tbl = gp_dialog_table(tbl_len, 3, vbox);
    gtk_widget_show(tbl);
   
    tbl_num = tbl_col = 0;

    for (i=0; i<ed->gui_nlabels; i++) {
      GtkWidget *hbox, *button, *image;
      GdkPixbuf *icon;

      /* label text */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      print_label_label(tbl, tbl_len, spec, i);
      print_field_label(tbl, tbl_len, _("text"));
      ed->labeltext[i] = gtk_entry_new();
      gtk_entry_set_max_length(GTK_ENTRY(ed->labeltext[i]), PLOT_LABEL_TEXT_LEN);
      gp_string_to_entry(ed->labeltext[i], spec->labels[i].text);
      g_signal_connect(G_OBJECT(ed->labeltext[i]), "activate", 
                   G_CALLBACK(apply_gpt_changes), 
                   ed);
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->labeltext[i], 2, 3, tbl_len-1, tbl_len);
      gtk_widget_show(ed->labeltext[i]);

      /* label placement */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);

      item_remove_button(tbl, tbl_len, ed, i, GUI_LABEL);

      print_field_label(tbl, tbl_len, _("position (X Y)"));

      /* holder for entry and button */
      hbox = gtk_hbox_new(FALSE, 5);

      /* entry for coordinates */
      ed->labelpos[i] = gtk_entry_new();
      gtk_entry_set_max_length(GTK_ENTRY(ed->labelpos[i]), PLOT_LABEL_POS_LEN);
      label_pos_to_entry(spec->labels[i].pos, ed->labelpos[i]);
      g_signal_connect(G_OBJECT(ed->labelpos[i]), "activate", 
                   G_CALLBACK(apply_gpt_changes), 
                   ed);
      gtk_container_add(GTK_CONTAINER(hbox), ed->labelpos[i]);
      gtk_widget_show(ed->labelpos[i]);

      if (plot_is_mouseable(plot)) {
          /* button to invoke mouse-assisted placement */
          button = gtk_button_new();
          g_object_set_data(G_OBJECT(button), "labelpos_entry", ed->labelpos[i]);
          g_signal_connect(G_OBJECT(button), "clicked",
                       G_CALLBACK(plot_label_position_click), spec->ptr);
          icon = gdk_pixbuf_new_from_xpm_data((const char **) mini_mouse_xpm);
          image = gtk_image_new_from_pixbuf(icon);
          gtk_widget_set_size_request(button, 32, 26);
          gtk_container_add(GTK_CONTAINER(button), image);
          gtk_container_add(GTK_CONTAINER(hbox), button);
          gtk_widget_show_all(button);
      }

      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          hbox, 2, 3, tbl_len-1, tbl_len);
      gtk_widget_show(hbox);

      /* label justification */
      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);

      print_field_label(tbl, tbl_len, _("justification"));

      ed->labeljust[i] = gtk_combo_box_new_text();
      for (j=0; j<3; j++) {
          gtk_combo_box_append_text(GTK_COMBO_BOX(ed->labeljust[i]),
                              _(gp_justification_string(j)));
      }
      gtk_combo_box_set_active(GTK_COMBO_BOX(ed->labeljust[i]), 
                         spec->labels[i].just);
      gtk_table_attach_defaults(GTK_TABLE(tbl), 
                          ed->labeljust[i], 2, 3, tbl_len-1, tbl_len);
      gtk_widget_show_all(ed->labeljust[i]);

      if (i < ed->gui_nlabels - 1 || spec->n_labels < 8) {
          /* separator */
          tbl_len++;
          gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
          sep = gtk_hseparator_new();
          gtk_table_attach_defaults(GTK_TABLE(tbl), sep, 0, 3, 
                              tbl_len-1, tbl_len);
          gtk_widget_show(sep);
      }
    }

    if (spec->n_labels < 8) {
      /* button for adding a label */
      GtkWidget *button;

      tbl_len++;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 3);
      button = gtk_button_new_with_label(_("Add..."));
      g_signal_connect(G_OBJECT(button), "clicked", 
                   G_CALLBACK(add_label_callback), 
                   ed);
      gtk_widget_show(button);
      gtk_table_attach_defaults(GTK_TABLE(tbl), button, 0, 1, 
                          tbl_len-1, tbl_len);
    }
}

static void gpt_tab_XY (plot_editor *ed, GPT_SPEC *spec, gint axis) 
{
    png_plot *plot = (png_plot *) spec->ptr;
    GtkWidget *notebook = ed->notebook;
    GtkWidget *b1, *b2, *entry, *vbox, *tbl;
    GtkWidget *label = NULL;
    char *labelstr = NULL;
    int i, tbl_len;
   
    if (axis == 0) {
      labelstr = _("X-axis");
    } else if (axis == 1) {
      labelstr = _("Y-axis");
    } else if (axis == 2) {
      labelstr = _("Y2-axis");
    } else {
      return;
    }

    vbox = gp_page_vbox(notebook, labelstr);

    tbl_len = 1;
    tbl = gp_dialog_table(tbl_len, 2, vbox);
    gtk_widget_show(tbl);
   
    for (i=0; i<NTITLES; i++) {
      if (ed->gpt_titles[i].tab == 1 + axis) {
          tbl_len++;
          gtk_table_resize(GTK_TABLE(tbl), tbl_len, 2);
            
          label = gtk_label_new(_(ed->gpt_titles[i].desc));
          gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
          gtk_table_attach_defaults(GTK_TABLE(tbl), label, 0, 1, 
                              tbl_len-1, tbl_len);
          gtk_widget_show(label);

          entry = gtk_entry_new();
          gtk_table_attach_defaults(GTK_TABLE(tbl), entry, 1, 2, 
                              tbl_len-1, tbl_len);
          gp_string_to_entry(entry, spec->titles[i]);
          g_signal_connect(G_OBJECT(entry), "activate", 
                       G_CALLBACK(apply_gpt_changes), 
                       ed);
          gtk_widget_show(entry);
          ed->gpt_titles[i].widget = entry;
      }
    } 

    if (spec->code != PLOT_REGULAR) {
      return;
    }

    ed->axis_range[axis].ID = axis;

    /* axis range: "auto" button */
    tbl_len += 3;
    gtk_table_resize(GTK_TABLE(tbl), tbl_len, 2);

    label = gtk_label_new("");
    gtk_table_attach_defaults(GTK_TABLE(tbl), label, 0, 1, 
                        tbl_len-3, tbl_len-2);
    gtk_widget_show(label);
    b1 = gtk_radio_button_new_with_label(NULL, _("auto axis range"));
    widget_set_int(b1, "axis", axis);
    g_signal_connect(G_OBJECT(b1), "clicked",
                 G_CALLBACK(flip_manual_range), ed);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
    gtk_table_attach_defaults(GTK_TABLE(tbl), b1, 0, 1, 
                        tbl_len-2, tbl_len-1);
    gtk_widget_show(b1);
    ed->axis_range[axis].isauto = b1;

    /* axis range: manual range button */
    b2 = gtk_radio_button_new_with_label(gtk_radio_button_get_group 
                               (GTK_RADIO_BUTTON(b1)),
                               _("manual range:")); 
    widget_set_int(b2, "axis", axis);
    g_signal_connect(G_OBJECT(b2), "clicked",
                 G_CALLBACK(flip_manual_range), ed);
    gtk_table_attach_defaults(GTK_TABLE(tbl), b2, 0, 1, 
                        tbl_len-1, tbl_len);
    gtk_widget_show(b2);

    /* axis range min. entry */
    tbl_len++;
    label = gtk_label_new(_("minimum"));
    gtk_table_attach_defaults(GTK_TABLE(tbl), 
                        label, 0, 1, tbl_len-1, tbl_len);
    gtk_widget_show(label);
    gtk_table_resize(GTK_TABLE(tbl), tbl_len, 2);
    entry = gtk_entry_new();
    gtk_table_attach_defaults(GTK_TABLE(tbl), entry, 1, 2, 
                        tbl_len-1, tbl_len);
    gtk_entry_set_text(GTK_ENTRY(entry), "");
    g_signal_connect(G_OBJECT(entry), "activate", 
                 G_CALLBACK(apply_gpt_changes), 
                 ed);
    gtk_widget_show(entry);
    ed->axis_range[axis].min = entry;

    /* axis range max. entry */
    tbl_len++;
    label = gtk_label_new(_("maximum"));
    gtk_table_attach_defaults(GTK_TABLE(tbl), label, 0, 1, 
                        tbl_len-1, tbl_len);
    gtk_widget_show(label);
    gtk_table_resize(GTK_TABLE(tbl), tbl_len, 2);
    entry = gtk_entry_new();
    gtk_table_attach_defaults(GTK_TABLE(tbl), entry, 1, 2, 
                        tbl_len-1, tbl_len);
    gtk_entry_set_text(GTK_ENTRY(entry), "");
    g_signal_connect(G_OBJECT(entry), "activate", 
                 G_CALLBACK(apply_gpt_changes), 
                 ed);
    gtk_widget_show(entry);
    ed->axis_range[axis].max = entry;

    if (na(spec->range[axis][0])) {
      gtk_widget_set_sensitive(ed->axis_range[axis].min, FALSE);
      gtk_widget_set_sensitive(ed->axis_range[axis].max, FALSE);
    } else {
      double_to_gp_entry(spec->range[axis][0], ed->axis_range[axis].min);
      double_to_gp_entry(spec->range[axis][1], ed->axis_range[axis].max);
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ed->axis_range[axis].isauto), 
                             FALSE);
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b2), TRUE);
    }

    /* axis scale: linear vs log? */

    if ((axis == 0 && plot_get_xmin(plot) >= 0.0) ||
      (axis == 1 && plot_get_ymin(plot) >= 0.0)) {
      GtkWidget *combo;
      GList *strs = NULL;

      strs = g_list_append(strs, "e");
      strs = g_list_append(strs, "2");
      strs = g_list_append(strs, "10");

      tbl_len += 3;
      gtk_table_resize(GTK_TABLE(tbl), tbl_len, 2);

      label = gtk_label_new("");
      gtk_table_attach_defaults(GTK_TABLE(tbl), label, 0, 1, 
                          tbl_len-3, tbl_len-2);
      gtk_widget_show(label);
      b1 = gtk_radio_button_new_with_label(NULL, _("linear scale"));
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
      gtk_table_attach_defaults(GTK_TABLE(tbl), b1, 0, 1, 
                          tbl_len-2, tbl_len-1);
      gtk_widget_show(b1);

      b2 = gtk_radio_button_new_with_label(gtk_radio_button_get_group 
                                   (GTK_RADIO_BUTTON(b1)),
                                   _("logarithmic scale, base:")); 
      gtk_table_attach_defaults(GTK_TABLE(tbl), b2, 0, 1, 
                          tbl_len-1, tbl_len);
      gtk_widget_show(b2);    

      combo = gtk_combo_box_entry_new_text();
      set_combo_box_strings_from_list(GTK_COMBO_BOX(combo), strs);
      g_list_free(strs);
      gtk_table_attach_defaults(GTK_TABLE(tbl), combo, 1, 2, 
                          tbl_len-1, tbl_len);
      entry = gtk_bin_get_child(GTK_BIN(combo));
      g_signal_connect(G_OBJECT(entry), "activate", 
                   G_CALLBACK(apply_gpt_changes), 
                   ed);
      gtk_widget_show(combo);
      ed->axis_range[axis].lbase = entry;

      if (spec->logbase[axis] > 1.1) {
          gchar *txt = g_strdup_printf("%g", spec->logbase[axis]);

          gtk_entry_set_text(GTK_ENTRY(entry), txt);
          g_free(txt);
          gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b2), TRUE);
      } else {
          gtk_widget_set_sensitive(entry, FALSE);
      }

      desensitize_widget_from_check(b1, entry);
      sensitize_widget_from_check(b2, entry);
    }
}

static void gpt_tab_palette (GtkWidget *notebook) 
{
    GtkWidget *vbox, *label, *tbl;
    int i, rows = 1;

    vbox = gp_page_vbox(notebook, _("Palette"));
    tbl = gp_dialog_table(1, 2, vbox);

    label = gtk_label_new(_("These colors will be used unless overridden\n"
                      "by graph-specific choices\n"));
    gtk_table_attach(GTK_TABLE(tbl), label, 0, 2, 0, 1, 0, 0, 0, 0);
    gtk_widget_show(label);

    for (i=0; i<BOXCOLOR; i++) {
      add_color_selector(i, tbl, 2, &rows, notebook);
    }

    gtk_widget_show(tbl);
}

static void plot_editor_destroy (plot_editor *ed)
{
    if (restore_lines(ed)) {
      /* undo any unapplied changes to lines */
      free(ed->spec->lines);
      ed->spec->lines = ed->old_lines;
      ed->spec->n_lines = ed->old_n_lines;
    }

    if (restore_labels(ed)) {
      /* undo any unapplied changes to labels */
      free(ed->spec->labels);
      ed->spec->labels = ed->old_labels;
      ed->spec->n_labels = ed->old_n_labels;
    }  

    free(ed->fontname);

    free(ed->linetitle);
    free(ed->lineformula);
    free(ed->stylecombo);
    free(ed->yaxiscombo);
    free(ed->linescale);
    free(ed->linewidth);

    free(ed->labeltext);
    free(ed->labelpos);
    free(ed->labeljust);

    free(ed);
}

static GtkWidget **widget_array_new (int n, int *err)
{
    GtkWidget **pw;
    int i;

    pw = malloc(n * sizeof *pw);

    if (pw != NULL) {
      for (i=0; i<n; i++) {
          pw[i] = NULL;
      }
    } else {
      *err = E_ALLOC;
    }

    return pw;
}

static GtkWidget **widget_array_expand (GtkWidget ***ppw, int n, int *err)
{
    GtkWidget **tmp;
    int i;

    tmp = realloc(*ppw, n * sizeof *tmp);

    if (tmp != NULL) {
      for (i=0; i<n; i++) {
          tmp[i] = NULL;
      }
      *ppw = tmp;
    } else {
      *err = E_ALLOC;
    }

    return *ppw;
}

static int add_line_widget (plot_editor *ed)
{
    int n = ed->gui_nlines + 1;
    int err = 0;

    ed->linetitle = widget_array_expand(&ed->linetitle, n, &err);
    ed->lineformula = widget_array_expand(&ed->lineformula, n, &err);
    ed->stylecombo = widget_array_expand(&ed->stylecombo, n, &err);
    ed->yaxiscombo = widget_array_expand(&ed->yaxiscombo, n, &err);
    ed->linescale = widget_array_expand(&ed->linescale, n, &err);
    ed->linewidth = widget_array_expand(&ed->linewidth, n, &err);
    
    if (!err) {
      ed->gui_nlines = n;
    }

    return err;
}

static int allocate_line_widgets (plot_editor *ed, int n)
{
    int err = 0;

    if (n > 0) {
      ed->linetitle = widget_array_new(n, &err);
      ed->lineformula = widget_array_new(n, &err);
      ed->stylecombo = widget_array_new(n, &err);
      ed->yaxiscombo = widget_array_new(n, &err);
      ed->linescale = widget_array_new(n, &err);
      ed->linewidth = widget_array_new(n, &err);
      if (!err) {
          ed->gui_nlines = n;
      }
    }

    return err;
}

static int add_label_widget (plot_editor *ed)
{
    int n = ed->gui_nlabels + 1;
    int err = 0;

    ed->labeltext = widget_array_expand(&ed->labeltext, n, &err);
    ed->labelpos = widget_array_expand(&ed->labelpos, n, &err);
    ed->labeljust = widget_array_expand(&ed->labeljust, n, &err);
    
    if (!err) {
      ed->gui_nlabels = n;
    }

    return err;
}

static int allocate_label_widgets (plot_editor *ed, int n)
{
    int err = 0;

    if (n > 0) {
      ed->labeltext = widget_array_new(n, &err);
      ed->labelpos = widget_array_new(n, &err);
      ed->labeljust = widget_array_new(n, &err);
      if (!err) {
          ed->gui_nlabels = n;
      }
    }

    return err;
}

static plot_editor *plot_editor_new (GPT_SPEC *spec)
{
    plot_editor *ed;
    int i;

    ed = malloc(sizeof *ed);
    if (ed == NULL) {
      return NULL;
    }

    ed->spec = spec;
    ed->fontname = NULL;

    ed->dialog = NULL;

    ed->linetitle = NULL;
    ed->lineformula = NULL;
    ed->stylecombo = NULL;
    ed->yaxiscombo = NULL;
    ed->linescale = NULL;
    ed->linewidth = NULL;

    ed->labeltext = NULL;
    ed->labelpos = NULL;
    ed->labeljust = NULL;

    /* also set to NULL the widgets which may or may not
       actually be used in constructing the plot editor 
       dialog 
    */
    ed->fitformula = NULL;
    ed->fitlegend = NULL;
    ed->keycombo = NULL;
    ed->fitcombo = NULL;
    ed->border_check = NULL;
    ed->y2_check = NULL;
    ed->ttfcombo = NULL;
    ed->ttfspin = NULL;

    ed->gui_nlines = ed->gui_nlabels = 0;

    if (allocate_line_widgets(ed, spec->n_lines)) {
      plot_editor_destroy(ed);
      return NULL;
    }    

    if (allocate_label_widgets(ed, spec->n_labels)) {
      plot_editor_destroy(ed);
      return NULL;
    }   

    for (i=0; i<MAX_AXES; i++) {
      ed->axis_range[i].isauto = NULL;
      ed->axis_range[i].lbase = NULL;
    }

    for (i=0; i<NTITLES; i++) {
      ed->gpt_titles[i].desc = (i == 0)? N_("Title of plot"):
          N_("Title for axis");
      ed->gpt_titles[i].tab = i;
      ed->gpt_titles[i].widget = NULL;
    }

    old_lines_init(ed);
    old_labels_init(ed);

    return ed;
} 

GtkWidget *plot_add_editor (png_plot *plot) 
{
    GPT_SPEC *spec = plot_get_spec(plot);
    GtkWidget *plotshell = plot_get_shell(plot);
    plot_editor *editor;
    GtkWidget *dialog, *hbox;
    GtkWidget *button, *notebook;

    editor = plot_editor_new(spec);
    if (editor == NULL) {
      nomem();
      return NULL;
    }

    dialog = gretl_dialog_new(_("gretl plot controls"), plotshell, 
                        GRETL_DLG_RESIZE);
    gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 2);
    gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
    
    if (plot != NULL) {
      gtk_window_set_transient_for(GTK_WINDOW(dialog), 
                             GTK_WINDOW(plotshell));
    }

    editor->dialog = dialog;

    g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
                       G_CALLBACK(plot_editor_destroy), 
                       editor);
   
    editor->notebook = notebook = gtk_notebook_new();
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), 
                   notebook, TRUE, TRUE, 0);
    gtk_widget_show(notebook);

    gpt_tab_main(editor, spec);
    gpt_tab_XY(editor, spec, 0);
    gpt_tab_XY(editor, spec, 1);

    if (spec->flags & GPT_Y2AXIS) {
      gpt_tab_XY(editor, spec, 2);
    }

    if (spec->lines != NULL) {
      gpt_tab_lines(editor, spec, 0);
    }

    gpt_tab_labels(editor, spec, 0);

    if (!frequency_plot_code(spec->code)) {
      gpt_tab_palette(notebook);
    }

    hbox = GTK_DIALOG(dialog)->action_area;

    /* "Apply" button */
    button = apply_button(hbox);
    g_signal_connect(G_OBJECT(button), "clicked", 
                 G_CALLBACK(apply_gpt_changes), editor);
    gtk_widget_grab_default(button);
    gtk_widget_show(button);

    /* "OK" button (apply and close) */
    button = ok_button(hbox);
    g_signal_connect(G_OBJECT(button), "clicked", 
                 G_CALLBACK(apply_gpt_changes), editor);
    g_signal_connect_swapped(G_OBJECT(button), "clicked",
                       G_CALLBACK(gtk_widget_destroy), 
                       dialog);
    gtk_widget_show(button);

    /* Close button (do not apply changes) */
    button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
    GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
    gtk_container_add(GTK_CONTAINER(hbox), button);
    g_signal_connect_swapped(G_OBJECT(button), "clicked",
                       G_CALLBACK(gtk_widget_destroy), 
                       dialog);
    gtk_widget_show(button);

    /* Help button */
    context_help_button(hbox, GR_PLOT);

    set_keyspec_sensitivity(editor);

    gtk_widget_show(dialog);

    return dialog;
}

Generated by  Doxygen 1.6.0   Back to index