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

gpt_control.c

/*
 *  Copyright (c) by Allin Cottrell
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* gpt_control.c for gretl -- gnuplot controller */

#include "gretl.h"
#include "gpt_control.h"
#include "session.h"
#include "gpt_dialog.h"
#include "fileselect.h"

#define GPDEBUG 0
#define POINTS_DEBUG 0

#ifdef G_OS_WIN32
# include <io.h>
# include "gretlwin32.h"
#endif

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkkeysyms.h>

#ifdef PNG_COMMENTS
# include <png.h>
#endif

enum {
    PLOT_SAVED          = 1 << 0,
    PLOT_HAS_CONTROLLER = 1 << 1,
    PLOT_ZOOMED         = 1 << 2,
    PLOT_ZOOMING        = 1 << 3,
    PLOT_NO_MARKERS     = 1 << 4,
    PLOT_PNG_COORDS     = 1 << 5,
    PLOT_HAS_XRANGE     = 1 << 6,
    PLOT_HAS_YRANGE     = 1 << 7,
    PLOT_DONT_ZOOM      = 1 << 8,
    PLOT_DONT_EDIT      = 1 << 9,
    PLOT_DONT_MOUSE     = 1 << 10,
    PLOT_POSITIONING    = 1 << 11
} plot_status_flags;

enum {
    PLOT_TITLE          = 1 << 0,
    PLOT_XLABEL         = 1 << 1,
    PLOT_YLABEL         = 1 << 2,
    PLOT_Y2AXIS         = 1 << 3,
    PLOT_Y2LABEL        = 1 << 4,
    PLOT_MARKERS_UP     = 1 << 5,
    PLOT_POLAR          = 1 << 6
} plot_format_flags;

#define MAX_MARKERS 100

#define plot_is_saved(p)        (p->status & PLOT_SAVED)
#define plot_has_controller(p)  (p->status & PLOT_HAS_CONTROLLER)
#define plot_is_zoomed(p)       (p->status & PLOT_ZOOMED)
#define plot_is_zooming(p)      (p->status & PLOT_ZOOMING)
#define plot_has_no_markers(p)  (p->status & PLOT_NO_MARKERS)
#define plot_has_png_coords(p)  (p->status & PLOT_PNG_COORDS)
#define plot_has_xrange(p)      (p->status & PLOT_HAS_XRANGE)
#define plot_has_yrange(p)      (p->status & PLOT_HAS_YRANGE)
#define plot_not_zoomable(p)    (p->status & PLOT_DONT_ZOOM)
#define plot_not_editable(p)    (p->status & PLOT_DONT_EDIT)
#define plot_doing_position(p)  (p->status & PLOT_POSITIONING)

#define plot_has_title(p)        (p->format & PLOT_TITLE)
#define plot_has_xlabel(p)       (p->format & PLOT_XLABEL)
#define plot_has_ylabel(p)       (p->format & PLOT_YLABEL)
#define plot_has_y2axis(p)       (p->format & PLOT_Y2AXIS)
#define plot_has_y2label(p)      (p->format & PLOT_Y2LABEL)
#define plot_has_data_markers(p) (p->format & PLOT_MARKERS_UP)
#define plot_is_polar(p)         (p->format & PLOT_POLAR)

#define plot_is_range_mean(p)   (p->spec->code == PLOT_RANGE_MEAN)
#define plot_is_hurst(p)        (p->spec->code == PLOT_HURST)
#define plot_is_roots(p)        (p->spec->code == PLOT_VAR_ROOTS)

#define plot_has_regression_list(p) (p->spec->reglist != NULL)
#define plot_show_all_markers(p)    (p->spec->flags & GPTSPEC_ALL_MARKERS)

enum {
    PNG_START,
    PNG_ZOOM,
    PNG_UNZOOM,
    PNG_REDISPLAY
} png_zoom_codes;

struct png_plot_t {
    GtkWidget *shell;
    GtkWidget *canvas;
    GtkWidget *popup;
    GtkWidget *statusarea;    
    GtkWidget *statusbar;
    GtkWidget *cursor_label;
    GtkWidget *labelpos_entry;
    GdkPixmap *pixmap;
    GdkGC *invert_gc;
    GPT_SPEC *spec;
    double xmin, xmax;
    double ymin, ymax;
    int pixel_width, pixel_height;
    int pixel_xmin, pixel_xmax;
    int pixel_ymin, pixel_ymax;
    int xint, yint;
    int pd;
    int err;
    guint cid;
    double zoom_xmin, zoom_xmax;
    double zoom_ymin, zoom_ymax;
    int screen_xmin, screen_ymin;
    unsigned long status; 
    unsigned char format;
};

static void render_pngfile (png_plot *plot, int view);
static int zoom_unzoom_png (png_plot *plot, int view);
static void create_selection_gc (png_plot *plot);
static int get_plot_ranges (png_plot *plot);

#ifdef PNG_COMMENTS

enum {
    GRETL_PNG_OK,
    GRETL_PNG_NO_OPEN,
    GRETL_PNG_NOT_PNG,
    GRETL_PNG_NO_COMMENTS,
    GRETL_PNG_BAD_COMMENTS,
    GRETL_PNG_NO_COORDS
};

typedef struct png_bounds_t png_bounds;

struct png_bounds_t {
    int xleft;
    int xright;
    int ybot;
    int ytop;
    double xmin;
    double xmax;
    double ymin;
    double ymax;
};

static int get_png_bounds_info (png_bounds *bounds);

#endif /* PNG_COMMENTS */

#define PLOTSPEC_DETAILS_IN_MEMORY(s)  (s->data != NULL)

static void terminate_plot_positioning (png_plot *plot)
{
    plot->status ^= PLOT_POSITIONING;
    plot->labelpos_entry = NULL;
    gdk_window_set_cursor(plot->canvas->window, NULL);
    gtk_statusbar_pop(GTK_STATUSBAR(plot->statusbar), plot->cid);
    raise_gpt_control_window();
}

void plot_remove_controller (png_plot *plot) 
{
    if (plot_has_controller(plot)) {
      plot->status ^= PLOT_HAS_CONTROLLER;
      if (plot_doing_position(plot)) {
          terminate_plot_positioning(plot);
      }
    }
}

GtkWidget *plot_get_shell (png_plot *plot) 
{
    return plot->shell;
}

int plot_is_mouseable (const png_plot *plot)
{
    return !(plot->status & PLOT_DONT_MOUSE);
}

void set_plot_has_y2_axis (png_plot *plot, gboolean s)
{
    if (s == TRUE) {
      plot->format |= PLOT_Y2AXIS;
    } else {
      plot->format &= ~PLOT_Y2AXIS;
    }
}

void plot_label_position_click (GtkWidget *w, GPT_SPEC *spec)
{
    png_plot *plot = (png_plot *) spec->ptr;

    if (plot != NULL) {
      GtkWidget *entry;
      GdkCursor* cursor;

      cursor = gdk_cursor_new(GDK_CROSSHAIR);
      gdk_window_set_cursor(plot->canvas->window, cursor);
      gdk_cursor_destroy(cursor);
      entry = g_object_get_data(G_OBJECT(w), "labelpos_entry");
      plot->labelpos_entry = entry;
      plot->status |= PLOT_POSITIONING;
      gtk_statusbar_push(GTK_STATUSBAR(plot->statusbar), plot->cid, 
                     _(" Click to set label position"));
    }
}

static void line_to_file (const char *s, FILE *fp, int l2)
{
#ifdef ENABLE_NLS
    if (l2 == -1) {
      print_as_html(s, fp);
    } else if (l2 == 1) {
      print_as_locale(s, fp);
    } else {
      fputs(s, fp);
    }
#else
    fputs(s, fp);
#endif
}

static FILE *open_gp_file (const char *fname, const char *mode)
{
    FILE *fp = gretl_fopen(fname, mode);

    if (fp == NULL) {
      if (*mode == 'w') {
          errbox(_("Couldn't write to %s"), fname);
      } else {
          errbox(_("Couldn't open %s"), fname);
      }
    }

    return fp;
}

static int commented_term_line (const char *s)
{
    return !strncmp(s, "# set term png", 14);
}

static int set_output_line (const char *s)
{
    return !strncmp(s, "set output", 10);
}

static int 
add_or_remove_png_term (const char *fname, int add, GPT_SPEC *spec)
{
    FILE *fsrc, *ftmp;
    char temp[MAXLEN], fline[MAXLEN];
    char restore_line[MAXLEN] = {0};
    int png_line_saved = 0;
#ifdef ENABLE_NLS
    int l2 = use_latin_2();
#else
    int l2 = 0;
#endif

    sprintf(temp, "%sgpttmp", paths.userdir);
    ftmp = gretl_tempfile_open(temp);
    if (ftmp == NULL) {
      return 1;
    }

    fsrc = open_gp_file(fname, "r");
    if (fsrc == NULL) {
      fclose(ftmp);
      return 1;
    }

    if (add && spec == NULL) {
      /* see if there's a commented out term setting to restore */
      while (fgets(fline, sizeof fline, fsrc)) {
          if (commented_term_line(fline)) {
            strcat(restore_line, fline + 2);
            break;
          }
      }
      rewind(fsrc);
    }

    if (add) {
      int need_term_line = 1;

      if (spec == NULL) {
          /* we're reconstituting a session graph from a 
             saved gnuplot command file */
          if (*restore_line) {
            /* found a saved png term specification */
            fputs(restore_line, ftmp);
            need_term_line = 0;
          }
      }
      if (need_term_line) {
          int ptype = (spec != NULL)? spec->code : PLOT_REGULAR;
          const char *pline = get_gretl_png_term_line(ptype);

          fprintf(ftmp, "%s\n", pline);
      }         
      fprintf(ftmp, "set output '%sgretltmp.png'\n", 
            paths.userdir);
    }

    /* now for the body of the plot file */
    while (fgets(fline, sizeof fline, fsrc)) {
      if (add) {
          if (!commented_term_line(fline) && !set_output_line(fline)) {
            line_to_file(fline, ftmp, -l2);
          }
      } else {
          /* we're removing the png term line */
          int printit = 1;

          if (!strncmp(fline, "set term png", 12)) {
            if (!png_line_saved) {
                /* comment it out, for future reference */
                fprintf(ftmp, "# %s", fline);
                png_line_saved = 1;
            } 
            printit = 0;
          }
          else if (commented_term_line(fline)) {
            printit = 0;
          } else if (set_output_line(fline)) {
            printit = 0;
          } else if (spec != NULL && (spec->flags & GPTSPEC_OLS_HIDDEN)
                   && is_auto_ols_string(fline)) {
            printit = 0;
          }
          if (printit) {
            line_to_file(fline, ftmp, l2);
          }
      }
    }

    fclose(fsrc);
    fclose(ftmp);

    remove(fname);
    return rename(temp, fname);
}

static int add_png_term_to_plotfile (const char *fname)
{
    return add_or_remove_png_term(fname, 1, NULL);
}

/* public because called from session.c when saving a graph file */

int remove_png_term_from_plotfile (const char *fname, GPT_SPEC *spec)
{
    return add_or_remove_png_term(fname, 0, spec);
}

void mark_plot_as_saved (GPT_SPEC *spec)
{
    png_plot *plot = (png_plot *) spec->ptr;

    plot->status |= PLOT_SAVED;
}

static int gnuplot_png_init (GPT_SPEC *spec, FILE **fpp)
{
    *fpp = gretl_fopen(spec->fname, "w");

    if (*fpp == NULL) {
      errbox(_("Couldn't write to %s"), spec->fname);
      return 1;
    }

    fprintf(*fpp, "%s\n", get_gretl_png_term_line(spec->code));
    fprintf(*fpp, "set output '%sgretltmp.png'\n", paths.userdir);

    return 0;
}

/* take saved plot source file and make PNG from it, then display
   the PNG */

void display_session_graph_png (const char *fname) 
{
    char fullname[MAXLEN];
    gchar *plotcmd;
    int err = 0;

    if (g_path_is_absolute(fname)) {
      strcpy(fullname, fname);
    } else {
      sprintf(fullname, "%s%s", paths.userdir, fname);
    }

    if (add_png_term_to_plotfile(fullname)) {
      return;
    }

    plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot, fullname);
    err = gretl_spawn(plotcmd);
    g_free(plotcmd);

    if (err) {
      errbox(_("Gnuplot error creating graph"));
    } else {
      gnuplot_show_png(fullname, NULL, 1);
    }
}

int maybe_switch_emf_point_style (char *s, PRN *prn)
{
    char *p = strstr(s, "w points");
    int do_pt2 = 0;

    if (p != NULL) {
      if (strncmp(p + 8, " pt", 3)) {
          do_pt2 = 1;
      }
    }

    if (do_pt2) {
      int i, len = p + 8 - s;

      for (i=0; i<len; i++) {
          pputc(prn, s[i]);
      }
      pputs(prn, " pt 2");
      pputs(prn, p + 8);
    } else {
      pputs(prn, s);
    }

    return do_pt2;
}

void save_this_graph (GPT_SPEC *plot, const char *fname)
{
    FILE *fq;
    PRN *prn;
    char plottmp[MAXLEN], plotline[MAXLEN], termstr[MAXLEN];
    gchar *plotcmd = NULL;
    int cmds, err;

    if (user_fopen("gptout.tmp", plottmp, &prn)) {
      return;
    }

    fq = gretl_fopen(plot->fname, "r");
    if (fq == NULL) {
      errbox(_("Couldn't access graph info"));
      gretl_print_destroy(prn);
      return;
    }
 
    cmds = get_termstr(plot, termstr);
  
    if (cmds) {
      if (copyfile(plot->fname, fname)) { 
          errbox(_("Failed to copy graph file"));
      }
      return;
    } else {
      int done_pt2 = strncmp(termstr, "emf", 3);

#ifdef ENABLE_NLS
      pprint_gnuplot_encoding(termstr, prn);
#endif
      pprintf(prn, "set term %s\n", termstr);
      pprintf(prn, "set output '%s'\n", fname);
      while (fgets(plotline, MAXLEN-1, fq)) {
          if (!done_pt2 && strstr(plotline, "using 1:2")) {
            done_pt2 = maybe_switch_emf_point_style(plotline, prn);
          } else if (strncmp(plotline, "set term", 8) && 
                   strncmp(plotline, "set output", 10)) {
            pputs(prn, plotline);
          }
      }
    }

    gretl_print_destroy(prn);
    fclose(fq);

    plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot, 
                        plottmp);
    err = gretl_spawn(plotcmd);

    remove(plottmp);
    g_free(plotcmd);

    if (err) {
      errbox(_("Gnuplot error creating graph"));
    } 
}

/* chop trailing comma, if present; return 1 if comma chopped,
   zero otherwise */

static int chop_comma (char *str)
{
    size_t i, n = strlen(str);

    for (i=n-1; i>0; i--) {
      if (isspace((unsigned char) str[i])) {
          continue;
      }
      if (str[i] == ',') {
          str[i] = 0;
          return 1;
      } else {
          break;
      }
    }
            
    return 0;
}

static int get_gpt_marker (const char *line, char *label)
{
    const char *p = strchr(line, '#');
    char format[6];

    if (p != NULL) {
      sprintf(format, "%%%ds", OBSLEN - 1);
      sscanf(p + 1, format, label);
#if GPDEBUG > 1
      fprintf(stderr, "read marker: '%s'\n", label);
#endif
      return 0;
    }

    return 1;
}

/* special graphs for which editing via GUI is not supported */

#define cant_edit(p) (p == PLOT_CORRELOGRAM || \
                      p == PLOT_LEVERAGE || \
                      p == PLOT_MULTI_IRF || \
                      p == PLOT_MULTI_SCATTER || \
                      p == PLOT_PANEL || \
                      p == PLOT_TRI_GRAPH || \
                      p == PLOT_BI_GRAPH || \
                      p == PLOT_VAR_ROOTS || \
                  p == PLOT_ELLIPSE)

static void plot_label_init (GPT_LABEL *lbl)
{
    lbl->text[0] = '\0';
    lbl->just = GP_JUST_LEFT;
    lbl->pos[0] = NADBL;
    lbl->pos[1] = NADBL;
}

static GPT_SPEC *plotspec_new (void)
{
    GPT_SPEC *spec;
    int i;

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

    spec->lines = mymalloc(MAX_PLOT_LINES * sizeof *spec->lines);

    if (spec->lines == NULL) {
      free(spec);
      return NULL;
    }

    for (i=0; i<MAX_PLOT_LINES; i++) {
      spec->lines[i].varnum = 0;
      spec->lines[i].title[0] = 0;
      spec->lines[i].formula[0] = 0;
      spec->lines[i].style[0] = 0;
      spec->lines[i].scale[0] = 0;
      spec->lines[i].yaxis = 1;
      spec->lines[i].type = 0;
      spec->lines[i].width = 1;
      spec->lines[i].ncols = 0;
    }

    for (i=0; i<4; i++) {
      spec->titles[i][0] = 0;
      spec->literal[i] = NULL;
    }

    for (i=0; i<MAX_PLOT_LABELS; i++) {
      plot_label_init(&(spec->labels[i]));
    }

    spec->xtics[0] = 0;
    spec->mxtics[0] = 0;
    spec->fname[0] = 0;
    strcpy(spec->keyspec, "left top");
    spec->xzeroaxis = 0;

    for (i=0; i<3; i++) {
      spec->range[i][0] = NADBL;
      spec->range[i][1] = NADBL;
    }

    spec->code = PLOT_REGULAR;
    spec->flags = 0;
    spec->fp = NULL;
    spec->data = NULL;
    spec->markers = NULL;
    spec->n_markers = 0;
    spec->labeled = NULL;
    spec->ptr = NULL;
    spec->reglist = NULL;
    spec->n_lines = 0;
    spec->nobs = 0;

    spec->termtype[0] = 0;

    return spec;
}

static int get_gpt_data (GPT_SPEC *spec, int do_markers, FILE *fp)
{
    char s[MAXLEN];
    char *got;
    double *x[4] = { NULL };
    char test[4][32];
    int started_data_lines = 0;
    int i, j, t;
    int err = 0;

    gretl_push_c_numeric_locale();

    for (i=0; i<spec->n_lines && !err; i++) {
      int offset = 1;

      if (spec->lines[i].ncols == 0) {
          continue;
      }

      if (!started_data_lines) {
          offset = 0;
          x[0] = spec->data;
          x[1] = x[0] + spec->nobs;
          started_data_lines = 1;
      } 

      x[2] = x[1] + spec->nobs;
      x[3] = x[2] + spec->nobs;     

      for (t=0; t<spec->nobs; t++) {
          int nf = 0;

          got = fgets(s, sizeof s, fp);
          if (got == NULL) {
            err = 1;
            break;
          }

          nf = 0;
          if (spec->lines[i].ncols == 4) {
            nf = sscanf(s, "%31s %31s %31s %31s", test[0], test[1], test[2], test[3]);
          } else if (spec->lines[i].ncols == 3) {
            nf = sscanf(s, "%31s %31s %31s", test[0], test[1], test[2]);
          } else if (spec->lines[i].ncols == 2) {
            nf = sscanf(s, "%31s %31s", test[0], test[1]);
          }

          if (nf != spec->lines[i].ncols) {
            err = 1;
          }

          for (j=offset; j<nf; j++) {
            if (test[j][0] == '?') {
                x[j][t] = NADBL;
            } else {
                x[j][t] = atof(test[j]);
            }
          }

          if (i == 0 && do_markers) {
            get_gpt_marker(s, spec->markers[t]);
          }
      }

      /* trailer line for data block */
      fgets(s, sizeof s, fp);

      /* shift 'y' writing location */
      x[1] += (spec->lines[i].ncols - 1) * spec->nobs;
    }

    gretl_pop_c_numeric_locale();

    return err;
}

/* read a gnuplot source line specifying a text label */

static int parse_label_line (GPT_SPEC *spec, const char *line, int i)
{
    const char *p, *s;
    double x, y;
    int nc, q2 = 0;
    int textread = 0;

    /* set label "this is a label" at 1998.26,937.557 left front */
    /* set label 'foobar' at 1500,350 left */

    if (i >= MAX_PLOT_LABELS) {
      return 1;
    }

    plot_label_init(&(spec->labels[i]));

    /* find first single or double quote */
    p = strchr(line, '\'');
    if (p == NULL) {
      p = strchr(line, '"');
      if (p == NULL) {
          return 1;
      }
      q2 = 1;
    }

    p++;
    s = p;

    /* get the label text */
    while (*s) {
      if (q2) {
          if (*s == '"' && *(s-1) != '\\') {
            textread = 1;
          }
      } else if (*s == '\'') {
          textread = 1;
      }

      if (textread) {
          int len = s - p;

          if (len > PLOT_LABEL_TEXT_LEN) {
            len = PLOT_LABEL_TEXT_LEN;
          }
          strncat(spec->labels[i].text, p, len);
          break;
      }

      s++;
    }

    if (!textread) {
      return 1;
    }

    /* get the position */
    p = strstr(s, "at");
    if (p == NULL) {
      spec->labels[i].text[0] = '\0';
      return 1;
    }

    p += 2;

    gretl_push_c_numeric_locale();
    nc = sscanf(p, "%lf,%lf", &x, &y);
    gretl_pop_c_numeric_locale();

    if (nc != 2) {
      spec->labels[i].text[0] = '\0';
      return 1;
    }

    spec->labels[i].pos[0] = x;
    spec->labels[i].pos[1] = y;

    /* justification */
    if (strstr(p, "right")) {
      spec->labels[i].just = GP_JUST_RIGHT;
    } else if (strstr(p, "center")) {
      spec->labels[i].just = GP_JUST_CENTER;
    } 

    return 0;
}

static int 
read_plotspec_range (const char *obj, const char *s, GPT_SPEC *spec)
{
    double r0, r1;
    int i = 0, err = 0;

    if (!strcmp(obj, "xrange")) {
      i = 0;
    } else if (!strcmp(obj, "yrange")) {
      i = 1;
    } else if (!strcmp(obj, "y2range")) {
      i = 2;
    } else {
      err = 1;
    }

    if (!strcmp(s, "[*:*]")) {
      r0 = r1 = NADBL;
    } else {
      gretl_push_c_numeric_locale();
      if (!err && sscanf(s, "[%lf:%lf]", &r0, &r1) != 2) {
          err = 1;
      }
      gretl_pop_c_numeric_locale();
    }

    if (!err) {
      spec->range[i][0] = r0;
      spec->range[i][1] = r1;
    }

    return err;
}

static int parse_gp_set_line (GPT_SPEC *spec, const char *s, int *labelno)
{
    char variable[16] = {0};
    char value[MAXLEN] = {0};

    if (strstr(s, "encoding") != NULL) {
      return 0;
    }

    if (sscanf(s + 4, "%11s", variable) != 1) {
      errbox(_("Failed to parse gnuplot file"));
      fprintf(stderr, "parse_gp_set_line: bad line '%s'\n", s);
      return 1;
    }

    if (strcmp(variable, "y2tics") == 0) {
      spec->flags |= GPTSPEC_Y2AXIS;
      return 0;
    } else if (strcmp(variable, "border 3") == 0) {
      spec->flags |= GPTSPEC_MINIMAL_BORDER;
      return 0;
    }    

    strcpy(value, s + 4 + strlen(variable));
    top_n_tail(value);

    if (strstr(variable, "range")) {
      if (read_plotspec_range(variable, value, spec)) {
          errbox(_("Failed to parse gnuplot file"));
          fprintf(stderr, "parse_gp_set_line: bad line '%s'\n", s);
          return 1;
      }
    } else if (!strcmp(variable, "title")) {
      strcpy(spec->titles[0], value);
    } else if (!strcmp(variable, "xlabel")) {
      strcpy(spec->titles[1], value);
    } else if (!strcmp(variable, "ylabel")) {
      strcpy(spec->titles[2], value);
    } else if (!strcmp(variable, "y2label")) {
      strcpy(spec->titles[3], value);
    } else if (!strcmp(variable, "key")) {
      strcpy(spec->keyspec, value);
    } else if (!strcmp(variable, "nokey")) {
      strcpy(spec->keyspec, "none");
    } else if (!strcmp(variable, "xtics")) { 
      safecpy(spec->xtics, value, 15);
    } else if (!strcmp(variable, "mxtics")) { 
      safecpy(spec->mxtics, value, 3);
    } else if (!strcmp(variable, "xzeroaxis")) {
      spec->xzeroaxis = 1;
    } else if (!strcmp(variable, "label")) {
      parse_label_line(spec, s, *labelno);
      *labelno += 1;
    }

    return 0;
}

/* allocate markers for identifying particular data points */

static int allocate_plotspec_markers (GPT_SPEC *spec)
{
    int i, j;

    spec->markers = mymalloc(spec->nobs * sizeof *spec->markers);
    if (spec->markers == NULL) {
      return 1;
    }

    for (i=0; i<spec->nobs; i++) {
      spec->markers[i] = malloc(OBSLEN);
      if (spec->markers[i] == NULL) {
          for (j=0; j<i; j++) {
            free(spec->markers[j]);
          }
          free(spec->markers);
          spec->n_markers = 0;
          return 1;
      }
      spec->markers[i][0] = 0;
    }

    spec->n_markers = spec->nobs;

    return 0;
}

/* Determine the number of data points in a plot (also the type
   of plot, and whether there are any data-point markers along with
   the data).
*/

static int get_plot_nobs (FILE *fp, PlotType *ptype, int *do_markers)
{
    int n = 0, started = -1;
    char line[MAXLEN], label[9];
    char *p;

    *ptype = PLOT_REGULAR;
    *do_markers = 0;

    while (fgets(line, MAXLEN - 1, fp)) {

      if (*line == '#' && *ptype == PLOT_REGULAR) {
          tailstrip(line);
          *ptype = plot_type_from_string(line);
      }

      if (!strncmp(line, "plot", 4)) {
          started = 0;
      }

      if (started == 0 && strchr(line, '\\') == NULL) {
          started = 1;
          continue;
      }

      if (started == 1) {
          if (*do_markers == 0 && (p = strchr(line, '#')) != NULL) {
            if (sscanf(p + 1, "%8s", label) == 1) {
                *do_markers = 1;
            }
          }
          if (*line == 'e') {
            break;
          }
          n++;
      }
    }

    return n;
}

/* parse the "using..." portion of plot specification for a
   given plot line: full form is like:
  
     using XX axes XX title XX w XX lt XX lw XX
*/

static int parse_gp_line_line (const char *s, GPT_SPEC *spec, int i)
{
    const char *p;
    int err = 0;

    if ((p = strstr(s, " using "))) {
      /* data column spec */
      p += 7;
      if (strstr(p, "1:2:3:4")) {
          spec->lines[i].ncols = 4;
      } else if (strstr(p, "1:2:3")) {
          spec->lines[i].ncols = 3;
      } else if ((p = strstr(s, "($2*"))) {
          sscanf(p + 4, "%7[^)]", spec->lines[i].scale);
          spec->lines[i].ncols = 2;
      } else {
          spec->lines[i].ncols = 2;
      }
      if (spec->lines[i].scale[0] == '\0') {
          strcpy(spec->lines[i].scale, "1.0");
      }
    } else {
      /* absence of "using" means the line plots a formula, not a
         set of data columns */
      strcpy(spec->lines[i].scale, "NA");
      /* get the formula: it runs up to "title" or "notitle" */
      p = strstr(s, " title");
      if (p == NULL) {
          p = strstr(s, " notitle");
      }
      if (p != NULL) {
          strncat(spec->lines[i].formula, s, p - s);
      }
    }

    if (strstr(s, "axes x1y2")) {
      spec->lines[i].yaxis = 2;
    } 

    if ((p = strstr(s, " title "))) {
      sscanf(p + 8, "%79[^']'", spec->lines[i].title);
    }

    if ((p = strstr(s, " w "))) {
      sscanf(p + 3, "%15[^, ]", spec->lines[i].style);
    } 

    if ((p = strstr(s, " lt "))) {
      sscanf(p + 4, "%d", &spec->lines[i].type);
    } 

    if ((p = strstr(s, " lw "))) {
      sscanf(p + 4, "%d", &spec->lines[i].width);
    } 

    if (spec->lines[i].ncols == 0 && spec->lines[i].formula[0] == '\0') {
      /* got neither data column spec nor formula */
      err = 1;
    }

    return err;
}

static int plot_ols_var_ok (const char *vname, int v)
{
    int vi = varindex(datainfo, vname);

    if (vi <= datainfo->v && !strcmp(datainfo->varname[vi], vname)) {
      return 1;
    }

    return 0;
}

static void maybe_set_all_markers_ok (GPT_SPEC *spec)
{
    if (spec->n_lines <= 2 &&
      spec->lines[0].ncols == 2 &&
      spec->lines[1].ncols == 0 &&
      spec->n_markers > 0) {
      spec->flags |= GPTSPEC_ALL_MARKERS_OK;
#if GPDEBUG > 1
      fprintf(stderr, "set GPTSPEC_ALL_MARKERS_OK\n");
#endif
    } else {
      spec->flags &= ~GPTSPEC_ALL_MARKERS_OK;
#if GPDEBUG > 1
      fprintf(stderr, "unset GPTSPEC_ALL_MARKERS_OK\n");
#endif
    }
}

static int 
plot_get_data_and_markers (GPT_SPEC *spec, FILE *fp, int datacols, 
                     int do_markers)
{
    int err = 0;

#if GPDEBUG
    fprintf(stderr, "allocating: nobs=%d, datacols=%d, size=%d\n", 
          spec->nobs, datacols, spec->nobs * datacols * sizeof *spec->data);
#endif  

    /* allocate for the plot data... */
    spec->data = mymalloc(spec->nobs * datacols * sizeof *spec->data);
    if (spec->data == NULL) {
      err = 1;
    }

    /* and markers if any */
    if (!err && do_markers) {
      if (allocate_plotspec_markers(spec)) {
          free(spec->data);
          spec->data = NULL;
          err = 1;
      }
    }

    /* Read the data (and perhaps markers) from the plot file */
    if (!err) {
      err = get_gpt_data(spec, do_markers, fp);
    }

#if GPDEBUG
    fprintf(stderr, "plot_get_data_and_markers:\n"
          " spec->data = %p, spec->markers = %p, spec->n_markers = %d, err = %d\n",
          spec->data, (void *) spec->markers, spec->n_markers, err);
#endif

    return err;
}

static int uneditable_get_markers (GPT_SPEC *spec, FILE *fp, int *polar)
{
    char line[256];
    long offset = 0;
    int gotit = 0;
    int err = 0;

    rewind(fp);

    /* advance to the right line (with data plus markers) */
    while (fgets(line, sizeof line, fp)) {
      if ((isdigit(*line) || *line == '-') && strchr(line, '#')) {
          gotit = 1;
          break;
      } else if (strstr(line, "set polar")) {
          *polar = 1;
      }
      offset = ftell(fp);
    }

    if (!gotit) {
      return 1;
    } else {
      fseek(fp, offset, SEEK_SET);
    }

    spec->n_lines = 1;
    spec->lines[0].ncols = 2;

    err = plot_get_data_and_markers(spec, fp, 2, 1);

    if (!err) {
      maybe_set_all_markers_ok(spec);
    }

    return err;
}

/* Read plotspec struct from gnuplot command file.  This is _not_ a
   general parser for gnuplot files; it is designed specifically for
   files auto-generated by gretl.
*/

static int read_plotspec_from_file (GPT_SPEC *spec, int *plot_pd, int *polar)
{
    int i, done, labelno;
    int do_markers = 0;
    int datacols = 0;
    int reglist[4] = {0};
    char gpline[MAXLEN];
    char *got = NULL;
    FILE *fp;
    int err = 0;

#if GPDEBUG
    fprintf(stderr, "read_plotspec_from_file: spec=%p\n", 
          (void *) spec);
#endif

    /* check: are we already done? */
    if (PLOTSPEC_DETAILS_IN_MEMORY(spec)) {
#if GPDEBUG
      fprintf(stderr, " info already in memory, returning 0\n");
#endif
      return 0;
    }

    /* open the plot file */
    fp = gretl_fopen(spec->fname, "r");
    if (fp == NULL) {
      errbox(_("Couldn't open graph file"));
      return 1;
    }

    /* get the number of data-points, plot type, and check for markers */
    spec->nobs = get_plot_nobs(fp, &spec->code, &do_markers);
    if (spec->nobs == 0 && spec->code != PLOT_ELLIPSE) {
      /* failed reading plot data */
#if GPDEBUG
      fprintf(stderr, " got spec->nobs = 0\n");
#endif
      fclose(fp);
      return 1;
    }

    if (spec->nobs > MAX_MARKERS && do_markers) {
      do_markers = 0;
    }

    if (cant_edit(spec->code)) {
      fprintf(stderr, "read_plotspec_from_file: plot is not editable\n");
      if (do_markers) {
          uneditable_get_markers(spec, fp, polar);
      }
      fclose(fp);
      return 0;
    }

    rewind(fp);

    /* get the preamble and "set" lines */
    labelno = 0;
    while ((got = fgets(gpline, sizeof gpline, fp))) {
      int v, litlines = 0;
      char vname[9];

      if (!strncmp(gpline, "# timeseries", 12)) {
          int pd;

          if (sscanf(gpline, "# timeseries %d", &pd)) {
            *plot_pd = pd;
          }
          spec->flags |= GPTSPEC_TS;
          continue;
      }

      if (sscanf(gpline, "# X = '%8[^\']' (%d)", vname, &v) == 2) {
          if (plot_ols_var_ok(vname, v)) {
            reglist[2] = v;
          }
          continue;
      } else if (sscanf(gpline, "# Y = '%8[^\']' (%d)", vname, &v) == 2) {
          if (reglist[2] > 0 && plot_ols_var_ok(vname, v)) {
            reglist[0] = 3;
            reglist[1] = v;
          }
          continue;
      }
      
      if (sscanf(gpline, "# literal lines = %d", &litlines)) {
          for (i=0; i<litlines; i++) {
            if (!fgets(gpline, MAXLEN - 1, fp)) {
                errbox(_("Plot file is corrupted"));
            } else {
                top_n_tail(gpline);
                spec->literal[i] = g_strdup(gpline);
            }
          }
          continue;
      }

      if (strstr(gpline, "automatic OLS")) {
          spec->flags |= GPTSPEC_AUTO_OLS;
          continue;
      }

      if (strstr(gpline, "printing data labels")) {
          spec->flags |= GPTSPEC_ALL_MARKERS;
          continue;
      }     

      if (!strncmp(gpline, "# ", 2)) {
          /* ignore unknown comment lines */
          continue;
      }

      if (strncmp(gpline, "set ", 4)) {
          /* done reading "set" lines */
          break;
      }

      if (parse_gp_set_line(spec, gpline, &labelno)) {
          err = 1;
          goto plot_bailout;
      }
    }

    if (got == NULL) {
      err = 1;
      goto plot_bailout;
    }

    for (i=0; i<4; i++) {
      if (spec->titles[i][0] != '\0') {
          delchar('\'', spec->titles[i]);
      }
    }

    if (*spec->keyspec == '\0') {
      strcpy(spec->keyspec, "none");
    }

    /* then get the "plot" lines */
    if (strncmp(gpline, "plot ", 5) ||
      (strlen(gpline) < 10 && fgets(gpline, MAXLEN - 1, fp) == NULL)) { 
      errbox(_("Failed to parse gnuplot file"));
      fprintf(stderr, "bad plotfile line: '%s'\n", gpline);
      err = 1;
      goto plot_bailout;
    }

    i = 0;
    done = 0;
    while (!err) {
      top_n_tail(gpline);

      if (!chop_comma(gpline)) {
          /* line did not end with comma -> no continuation of
             the plot command */
          done = 1;
      } 

      err = parse_gp_line_line(gpline, spec, i);

      if (done) {
          break;
      }

      i++;

      if ((got = fgets(gpline, MAXLEN - 1, fp)) == NULL) {
          break;
      }
    }

    if (err || got == NULL) {
      err = 1;
      goto plot_bailout;
    }

    spec->n_lines = i + 1; /* i is a zero-based index */

    /* free any unused lines */
    if (spec->n_lines < MAX_PLOT_LINES) {
      spec->lines = myrealloc(spec->lines, 
                        spec->n_lines * sizeof *spec->lines);
    }

    /* determine total number of required data columns */
    for (i=0; i<spec->n_lines; i++) {
      if (spec->lines[i].ncols == 0) {
          continue;
      }
      if (datacols == 0) {
          datacols = spec->lines[i].ncols;
      } else {
          datacols += spec->lines[i].ncols - 1;
      }
    }

    err = plot_get_data_and_markers(spec, fp, datacols, do_markers);

    if (!err && reglist[0] > 0) {
      spec->reglist = gretl_list_copy(reglist);
    }

    if (!err && spec->markers != NULL) {
      maybe_set_all_markers_ok(spec);
    }

 plot_bailout:

    fclose(fp);

    return err;
}

#ifdef USE_GNOME
extern void gnome_print_graph (const char *fname);
#endif

static int get_data_xy (png_plot *plot, int x, int y, 
                  double *data_x, double *data_y)
{
    double xmin, xmax;
    double ymin, ymax;
    double dx = NADBL;
    double dy = NADBL;
    int ok = 1;

    if (plot_is_zoomed(plot)) {
      xmin = plot->zoom_xmin;
      xmax = plot->zoom_xmax;
      ymin = plot->zoom_ymin;
      ymax = plot->zoom_ymax;
    } else {
      xmin = plot->xmin;
      xmax = plot->xmax;
      ymin = plot->ymin;
      ymax = plot->ymax;
    }

#if POINTS_DEBUG
    if (plot_doing_position(plot)) {
      fprintf(stderr, "get_data_xy:\n"
            " plot->xmin=%g, plot->xmax=%g, plot->ymin=%g, plot->ymax=%g\n",
            plot->xmin, plot->xmax, plot->ymin, plot->ymax);
    }
#endif

    if (xmin == 0.0 && xmax == 0.0) { 
      /* unknown x range */
      fprintf(stderr, "get_data_xy: unknown x range\n");
    } else {
      dx = xmin + ((double) x - plot->pixel_xmin) / 
          (plot->pixel_xmax - plot->pixel_xmin) * (xmax - xmin);
    }

    if (!na(dx)) {
      if (ymin == 0.0 && ymax == 0.0) { 
          /* unknown y range */
          fprintf(stderr, "get_data_xy: unknown y range\n");
      } else {
          dy = ymax - ((double) y - plot->pixel_ymin) / 
            (plot->pixel_ymax - plot->pixel_ymin) * (ymax - ymin);
      }
    }

    if (na(dx) || na(dx)) {
      ok = 0;
    } else if (plot_is_polar(plot)) {
      double px = atan2(dy, dx);
      double py = sqrt(dx * dx + dy * dy);

      dx = px;
      dy = py;
    }

    *data_x = dx;
    *data_y = dy;

    return ok;
}

static void x_to_date (double x, int pd, char *str)
{
    int yr = (int) x;
    double t, frac = 1.0 / pd;
    int subper = (int) ((x - yr + frac) * pd);
    static int decpoint;

    if (decpoint == 0) decpoint = get_local_decpoint();

    t = yr + subper / ((pd < 10)? 10.0 : 100.0);
    sprintf(str, "%.*f", (pd < 10)? 1 : 2, t);
    charsub(str, decpoint, ':');
}

static void create_selection_gc (png_plot *plot)
{
    if (plot->invert_gc == NULL) {
      plot->invert_gc = gdk_gc_new(plot->canvas->window);
      gdk_gc_set_function(plot->invert_gc, GDK_INVERT);
    }
}

static void draw_selection_rectangle (png_plot *plot,
                              int x, int y)
{
    int rx, ry, rw, rh;

    rx = (plot->screen_xmin < x)? plot->screen_xmin : x;
    ry = (plot->screen_ymin < y)? plot->screen_ymin : y;
    rw = x - plot->screen_xmin;
    rh = y - plot->screen_ymin;
    if (rw < 0) rw = -rw;
    if (rh < 0) rh = -rh;    

    /* draw one time to make the rectangle appear */
    gdk_draw_rectangle(plot->pixmap,
                   plot->invert_gc,
                   FALSE,
                   rx, ry, rw, rh);
    /* show the modified pixmap */
    gdk_window_copy_area(plot->canvas->window,
                   plot->canvas->style->fg_gc[GTK_STATE_NORMAL],
                   0, 0,
                   plot->pixmap,
                   0, 0,
                   plot->pixel_width, plot->pixel_height);
    /* draw (invert) again to erase the rectangle */
    gdk_draw_rectangle(plot->pixmap,
                   plot->invert_gc,
                   FALSE,
                   rx, ry, rw, rh);
}

static int make_alt_label (gchar *alt, const gchar *label)
{
    double x, y;
    int err = 0;

    gretl_push_c_numeric_locale();

    if (sscanf(label, "%lf,%lf", &x, &y) != 2) {
      err = 1;
    }

    gretl_pop_c_numeric_locale();

    if (!err) {
      if (get_local_decpoint() != '.') {
          sprintf(alt, "%.2f %.2f", x, y);
      } else {
          sprintf(alt, "%.2f,%.2f", x, y);
      }
    }

    return err;
}

static void
write_label_to_plot (png_plot *plot, int i, gint x, gint y)
{
    const gchar *label = plot->spec->markers[i];
    PangoContext *context;
    PangoLayout *pl;

    if (plot_is_roots(plot)) {
      gchar alt_label[12];
      
      if (make_alt_label(alt_label, label)) {
          return;
      }

      label = alt_label;
    }

    if (plot->invert_gc == NULL) {
      create_selection_gc(plot);
    }

    context = gtk_widget_get_pango_context(plot->shell);
    pl = pango_layout_new(context);
    pango_layout_set_text(pl, label, -1);

    /* draw the label */
    gdk_draw_layout(plot->pixmap, plot->invert_gc, x, y, pl);

    /* show the modified pixmap */
    gdk_window_copy_area(plot->canvas->window,
                   plot->canvas->style->fg_gc[GTK_STATE_NORMAL],
                   0, 0,
                   plot->pixmap,
                   0, 0,
                   plot->pixel_width, plot->pixel_height);

    /* trash the pango layout */
    g_object_unref(G_OBJECT(pl));

    /* record that a label is shown */
    plot->format |= PLOT_MARKERS_UP;
}

#define TOLDIST 0.01

static gint
identify_point (png_plot *plot, int pixel_x, int pixel_y,
            double x, double y) 
{
    const double *data_x = NULL;
    const double *data_y = NULL;
    double xrange, yrange;
    double xdiff, ydiff;
    double min_xdist, min_ydist;
    int best_match = -1;
    int t;

#if GPDEBUG > 2
    fprintf(stderr, "identify_point: pixel_x = %d (x=%g), pixel_y = %d (y=%g)\n",
          pixel_x, x, pixel_y, y);
#endif

    if (plot->err) {
      return TRUE;
    }

    /* no markers to show */
    if (plot->spec->markers == NULL) {
      plot->status |= PLOT_NO_MARKERS;    
      return TRUE;
    }

    /* need array to keep track of which points are labeled */
    if (plot->spec->labeled == NULL) {
      plot->spec->labeled = calloc(plot->spec->nobs, 1);
      if (plot->spec->labeled == NULL) {
          return TRUE;
      }
    }

    if (plot_is_zoomed(plot)) {
      min_xdist = xrange = plot->zoom_xmax - plot->zoom_xmin;
      min_ydist = yrange = plot->zoom_ymax - plot->zoom_ymin;
    } else {
      min_xdist = xrange = plot->xmax - plot->xmin;
      min_ydist = yrange = plot->ymax - plot->ymin;
    }

    data_x = plot->spec->data;
    data_y = data_x + plot->spec->nobs;

    if (plot_has_y2axis(plot)) {
      /* use first y-var that's on y1 axis, if any */
      int i, got_y = 0;

      for (i=0; i<plot->spec->n_lines; i++) {
          if (plot->spec->lines[i].yaxis == 1) {
            got_y = 1;
            break;
          }
          if (plot->spec->lines[i].ncols > 0) {
            data_y += (plot->spec->lines[i].ncols - 1) * plot->spec->nobs;
          }
      }
      if (!got_y) {
          data_y = NULL;
          plot->status |= PLOT_NO_MARKERS;      
          return TRUE;
      }
    } 

    /* try to find the best-matching data point */
    for (t=0; t<plot->spec->nobs; t++) {
      if (na(data_x[t]) || na(data_y[t])) {
          continue;
      }
#if GPDEBUG > 2
      fprintf(stderr, "considering t=%d: x=%g, y=%g\n", t, data_x[t], data_y[t]);
#endif
      xdiff = fabs(data_x[t] - x);
      ydiff = fabs(data_y[t] - y);
      if (xdiff <= min_xdist && ydiff <= min_ydist) {
          min_xdist = xdiff;
          min_ydist = ydiff;
          best_match = t;
      }
    }

    /* if the point is already labeled, skip */
    if (plot->spec->labeled[best_match]) {
      return TRUE;
    }

#if GPDEBUG > 2
    fprintf(stderr, " best_match=%d, with data_x[%d]=%g, data_y[%d]=%g\n", 
          best_match, best_match, data_x[best_match], 
          best_match, data_y[best_match]);
#endif

    /* if the match is good enough, show the label */
    if (best_match >= 0 && 
      min_xdist < TOLDIST * xrange &&
      min_ydist < TOLDIST * yrange) {
      write_label_to_plot(plot, best_match, pixel_x, pixel_y);
      /* flag the point as labeled already */
      plot->spec->labeled[best_match] = 1;
    }

    return TRUE;
}

static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event, png_plot *plot)
{
    GdkModifierType state;
    gchar label[32], label_y[16];
    int x, y;

    if (plot->err) {
      return TRUE;
    }

    if (event->is_hint) {
        gdk_window_get_pointer(event->window, &x, &y, &state);
    } else {
        x = event->x;
        y = event->y;
        state = event->state;
    }

    *label = 0;

    if (x > plot->pixel_xmin && x < plot->pixel_xmax && 
      y > plot->pixel_ymin && y < plot->pixel_ymax) {
      double data_x, data_y;

      get_data_xy(plot, x, y, &data_x, &data_y);
      if (na(data_x)) return TRUE;

      if (!plot_has_no_markers(plot) && !plot_show_all_markers(plot) &&
          !plot_is_zooming(plot) &&
          !na(data_y)) {
          identify_point(plot, x, y, data_x, data_y);
      }

      if (plot->pd == 4 || plot->pd == 12) {
          x_to_date(data_x, plot->pd, label);
      } else {
          sprintf(label, (plot->xint)? "%7.0f" : "%7.4g", data_x);
      }

      if (!na(data_y)) {
          if (plot_has_png_coords(plot)) {
            sprintf(label_y, (plot->yint)? " %-7.0f" : " %-7.4g", data_y);
          } else {
            /* pretty much guessing at y coordinate here */
            sprintf(label_y, (plot->yint)? " %-7.0f" : " %-6.3g", data_y);
          }
          strcat(label, label_y);
      }

      if (plot_is_zooming(plot) && (state & GDK_BUTTON1_MASK)) {
          draw_selection_rectangle(plot, x, y);
      }
    } 

    gtk_label_set_text(GTK_LABEL(plot->cursor_label), label);
  
    return TRUE;
}

static void set_plot_format_flags (png_plot *plot)
{
    plot->format = 0;

    if (!string_is_blank(plot->spec->titles[0])) {
      plot->format |= PLOT_TITLE;
    }
    if (!string_is_blank(plot->spec->titles[1])) {
      plot->format |= PLOT_XLABEL;
    }
    if (!string_is_blank(plot->spec->titles[2])) {
      plot->format |= PLOT_YLABEL;
    }
    if (!string_is_blank(plot->spec->titles[3])) {
      plot->format |= PLOT_Y2LABEL;
    }
    if (plot->spec->flags & GPTSPEC_Y2AXIS) {
      plot->format |= PLOT_Y2AXIS;
    }
}

/* called from png plot popup menu */

static void start_editing_png_plot (png_plot *plot)
{
#if GPDEBUG
    fprintf(stderr, "start_editing_png_plot: plot = %p\n", (void *) plot);
#endif

    if (!PLOTSPEC_DETAILS_IN_MEMORY(plot->spec)) {
      errbox(_("Couldn't access graph info"));
      plot->err = 1;
      return;
    }

    if (show_gnuplot_dialog(plot->spec) == 0) { /* OK */
      plot->status |= PLOT_HAS_CONTROLLER;
    }
}

#ifdef HAVE_AUDIO
static void audio_render_plot (png_plot *plot)
{
    void *handle;
    int (*midi_play_graph) (const char *, const char *, const char *);

    if (plot_not_editable(plot)) {
      return;
    }

    midi_play_graph = gui_get_plugin_function("midi_play_graph", 
                                    &handle);
    if (midi_play_graph == NULL) {
        return;
    }

# ifdef G_OS_WIN32
    (*midi_play_graph) (plot->spec->fname, paths.userdir, NULL);
# else
    (*midi_play_graph) (plot->spec->fname, paths.userdir, midiplayer);
# endif

    close_plugin(handle);
}
#endif

static gint color_popup_activated (GtkWidget *w, gpointer data)
{
    gchar *item = (gchar *) data;
    gpointer ptr = g_object_get_data(G_OBJECT(w), "plot");
    png_plot *plot = (png_plot *) ptr;
    gint color = strcmp(item, _("monochrome"));
    GtkWidget *parent = (GTK_MENU(w->parent))->parent_menu_item;
    gchar *parent_item = g_object_get_data(G_OBJECT(parent), "string");

    if (!strcmp(parent_item, _("Save as postscript (EPS)..."))) {
      strcpy(plot->spec->termtype, "postscript");
      if (color) {
          strcat(plot->spec->termtype, " color");
      } 
      file_selector(_("Save gnuplot graph"), SAVE_THIS_GRAPH, 
                  FSEL_DATA_MISC, plot->spec);
    } else if (!strcmp(parent_item, _("Save as Windows metafile (EMF)..."))) {
      strcpy(plot->spec->termtype, "emf");
      if (color) {
          strcat(plot->spec->termtype, " color");
      } else {
          strcat(plot->spec->termtype, " mono");
      }
      file_selector(_("Save gnuplot graph"), SAVE_THIS_GRAPH, 
                  FSEL_DATA_MISC, plot->spec);
    } 
#ifdef G_OS_WIN32
    else if (!strcmp(parent_item, _("Copy to clipboard"))) {
      win32_process_graph(plot->spec, color, WIN32_TO_CLIPBOARD);
    } else if (!strcmp(parent_item, _("Print"))) {
      win32_process_graph(plot->spec, color, WIN32_TO_PRINTER);
    }    
#endif   

    return TRUE;
}

static void show_numbers_from_markers (GPT_SPEC *spec)
{
    PRN *prn;
    double x, y;
    double mod, freq;
    int i, err = 0;

    if (bufopen(&prn)) {
      return;
    } 

    pputs(prn, _("VAR roots (real, imaginary, modulus, frequency)"));
    pputs(prn, "\n\n");

    if (get_local_decpoint() != '.') {
      gretl_push_c_numeric_locale();
      for (i=0; i<spec->n_markers; i++) {
          if (sscanf(spec->markers[i], "%lf,%lf", &x, &y) == 2) {
            freq = spec->data[i] / (2.0 * M_PI);
            mod = spec->data[spec->nobs + i];
            gretl_pop_c_numeric_locale();
            pprintf(prn, "%2d: (%7.4f  %7.4f  %7.4f  %7.4f)\n", i+1, 
                  x, y, mod, freq);
            gretl_push_c_numeric_locale();
          } else {
            err = E_DATA;
            break;
          }
      }
      gretl_pop_c_numeric_locale();
    } else {
      for (i=0; i<spec->n_markers; i++) {
          if (sscanf(spec->markers[i], "%lf,%lf", &x, &y) == 2) {
            freq = spec->data[i] / (2.0 * M_PI);
            mod = spec->data[spec->nobs + i];
            pprintf(prn, "%2d: (%7.4f, %7.4f, %7.4f, %7.4f)\n", i+1, 
                  x, y, mod, freq);
          } else {
            err = E_DATA;
            break;
          }
      }
    }

    if (err) {
      gui_errmsg(err);
      gretl_print_destroy(prn);
    } else {
      gchar *title = g_strdup_printf("gretl: %s", _("VAR roots"));

      view_buffer(prn, 72, 340, title, PRINT, NULL);
      g_free(title);    
    }
}

static gint plot_popup_activated (GtkWidget *w, gpointer data)
{
    gchar *item = (gchar *) data;
    gpointer ptr = g_object_get_data(G_OBJECT(w), "plot");
    png_plot *plot = (png_plot *) ptr;
    int killplot = 0;

    gtk_widget_destroy(plot->popup);
    plot->popup = NULL;

    if (!strcmp(item, _("Save as PNG..."))) {
      strcpy(plot->spec->termtype, "png");
        file_selector(_("Save gnuplot graph"), SAVE_THIS_GRAPH, 
                  FSEL_DATA_MISC, plot->spec);
    } else if (!strcmp(item, _("Save as PDF..."))) {
      strcpy(plot->spec->termtype, "PDF");
        file_selector(_("Save gnuplot graph"), SAVE_THIS_GRAPH, 
                  FSEL_DATA_MISC, plot->spec);
    } else if (!strcmp(item, _("Save to session as icon"))) { 
      add_graph_to_session(plot->spec, GRETL_OBJ_GRAPH, NULL);
    } else if (plot_is_range_mean(plot) && !strcmp(item, _("Help"))) { 
      context_help(NULL, GINT_TO_POINTER(RMPLOT));
    } else if (plot_is_hurst(plot) && !strcmp(item, _("Help"))) { 
      context_help(NULL, GINT_TO_POINTER(HURST));
    } else if (!strcmp(item, _("Freeze data labels"))) {
      plot->spec->flags |= GPTSPEC_ALL_MARKERS;
      redisplay_edited_png(plot);
    } else if (!strcmp(item, _("Clear data labels"))) { 
      zoom_unzoom_png(plot, PNG_REDISPLAY);
    } else if (!strcmp(item, _("Zoom..."))) { 
      GdkCursor* cursor;

      cursor = gdk_cursor_new(GDK_CROSSHAIR);
      gdk_window_set_cursor(plot->canvas->window, cursor);
      gdk_cursor_destroy(cursor);
      plot->status |= PLOT_ZOOMING;
      gtk_statusbar_push(GTK_STATUSBAR(plot->statusbar), plot->cid, 
                     _(" Drag to define zoom rectangle"));
      create_selection_gc(plot);
    } else if (!strcmp(item, _("Restore full view"))) { 
      zoom_unzoom_png(plot, PNG_UNZOOM);
    }
#ifdef USE_GNOME 
    else if (!strcmp(item, _("Print..."))) { 
      gnome_print_graph(plot->spec->fname);
    }
#endif 
    else if (!strcmp(item, _("OLS estimates"))) { 
      do_graph_model(plot->spec);
    } else if (!strcmp(item, _("Numerical values"))) {
      show_numbers_from_markers(plot->spec);
    } else if (!strcmp(item, _("Edit"))) { 
      start_editing_png_plot(plot);
    } else if (!strcmp(item, _("Close"))) { 
        killplot = 1;
    } 

    if (killplot) {
      gtk_widget_destroy(plot->shell);
    }

    return TRUE;
}

static void attach_color_popup (GtkWidget *w, png_plot *plot)
{
    GtkWidget *item, *cpopup;
    const char *color_items[] = {
      N_("color"),
      N_("monochrome")
    };
    int i;

    cpopup = gtk_menu_new();

    for (i=0; i<2; i++) {
      item = gtk_menu_item_new_with_label(_(color_items[i]));
      g_signal_connect(G_OBJECT(item), "activate",
                   G_CALLBACK(color_popup_activated),
                   _(color_items[i]));
      g_object_set_data(G_OBJECT(item), "plot", plot);
      gtk_widget_show(item);
      gtk_menu_shell_append(GTK_MENU_SHELL(cpopup), item);
    } 

    gtk_menu_item_set_submenu(GTK_MENU_ITEM(w), cpopup);
}

static void build_plot_menu (png_plot *plot)
{
    GtkWidget *item;    
    const char *regular_items[] = {
#ifdef G_OS_WIN32
      N_("Save as Windows metafile (EMF)..."),
#endif
      N_("Save as PNG..."),
        N_("Save as postscript (EPS)..."),
      N_("Save as PDF..."),
#ifndef G_OS_WIN32
      N_("Save as Windows metafile (EMF)..."),
#endif
#ifdef G_OS_WIN32
      N_("Copy to clipboard"),
#endif
      N_("Save to session as icon"),
      N_("Freeze data labels"),
      N_("Clear data labels"),
      N_("Zoom..."),
#ifdef USE_GNOME
      N_("Print..."),
#endif
#ifdef G_OS_WIN32
      N_("Print"),
#endif
      N_("OLS estimates"),
      N_("Numerical values"),
      N_("Edit"),
      N_("Help"),
        N_("Close"),
        NULL
    };
    const char *zoomed_items[] = {
      N_("Restore full view"),
      N_("Close"),
      NULL
    };
    const char **plot_items;
    static int pdf_ok = -1;
    int i;

    if (pdf_ok == -1) {
      pdf_ok = gnuplot_has_pdf();
    }

    plot->popup = gtk_menu_new();

    if (plot_is_zoomed(plot)) {
      plot_items = zoomed_items;
    } else {
      plot_items = regular_items;
    }

    i = 0;
    while (plot_items[i]) {
      if (plot_not_zoomable(plot) &&
          !strcmp(plot_items[i], "Zoom...")) {
          i++;
          continue;
      }
      if (!(plot_is_range_mean(plot) || plot_is_hurst(plot)) &&
          !strcmp(plot_items[i], "Help")) {
          i++;
          continue;
      }
      if (plot_is_saved(plot) &&
          !strcmp(plot_items[i], "Save to session as icon")) {
          i++;
          continue;
      }
      if ((plot_has_controller(plot) || plot_not_editable(plot)) &&
          !strcmp(plot_items[i], "Edit")) {
          i++;
          continue;
      }
      if (!pdf_ok && !strcmp(plot_items[i], "Save as PDF...")) {
          i++;
          continue;
      }         
      if (!plot_has_data_markers(plot) &&
          (!strcmp(plot_items[i], "Freeze data labels") ||
           !strcmp(plot_items[i], "Clear data labels"))) {
          i++;
          continue;
      }
      if (!plot_has_regression_list(plot) &&
          !strcmp(plot_items[i], "OLS estimates")) {
          i++;
          continue;
      }
      if (!plot_is_roots(plot) && 
          !strcmp(plot_items[i], "Numerical values")) {
          i++;
          continue;
      }     

        item = gtk_menu_item_new_with_label(_(plot_items[i]));
        g_object_set_data(G_OBJECT(item), "plot", plot);
        gtk_widget_show(item);
        gtk_menu_shell_append(GTK_MENU_SHELL(plot->popup), item);

      /* items with color sub-menu */
      if (!strcmp(plot_items[i], "Save as Windows metafile (EMF)...") ||
          !strcmp(plot_items[i], "Save as postscript (EPS)...") ||
          !strcmp(plot_items[i], "Copy to clipboard") ||
          !strcmp(plot_items[i], "Print")) {
          attach_color_popup(item, plot);
          g_object_set_data(G_OBJECT(item), "string", _(plot_items[i]));
      } else {
          g_signal_connect(G_OBJECT(item), "activate",
                       G_CALLBACK(plot_popup_activated),
                       _(plot_items[i]));
      }
        i++;
    }

    g_signal_connect(G_OBJECT(plot->popup), "destroy",
                 G_CALLBACK(gtk_widget_destroyed), 
                 &plot->popup);
}

int redisplay_edited_png (png_plot *plot)
{
    gchar *plotcmd;
    FILE *fp;
    int err = 0;

#if GPDEBUG
    fprintf(stderr, "redisplay_edited_png: plot = %p\n", (void *) plot);
#endif

    /* open file in which to dump plot specification */
    gnuplot_png_init(plot->spec, &fp);
    if (fp == NULL) {
      return 1;
    }

    /* dump the edited plot details to file */
    set_png_output(plot->spec);
    print_plotspec_details(plot->spec, fp);
    fclose(fp);

    /* get gnuplot to create a new PNG graph */
    plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot, 
                        plot->spec->fname);
    err = gretl_spawn(plotcmd);
    g_free(plotcmd);

    if (err) {
      errbox(_("Failed to generate PNG file"));
      return 1;
    }

    /* reset format flags */
    set_plot_format_flags(plot);

    /* grab (possibly modified) data ranges */
    get_plot_ranges(plot);

    /* put the newly created PNG onto the plot canvas */
    render_pngfile(plot, PNG_REDISPLAY);

    return 0;
}

static int zoom_unzoom_png (png_plot *plot, int view)
{
    int err = 0;
    char zoomname[MAXLEN];
    gchar *plotcmd = NULL;

    if (view == PNG_ZOOM) {
      FILE *fpin, *fpout;
      char line[MAXLEN];

      fpin = gretl_fopen(plot->spec->fname, "r");
      if (fpin == NULL) {
          return 1;
      }

      build_path(zoomname, paths.userdir, "zoomplot.gp", NULL);
      fpout = gretl_fopen(zoomname, "w");
      if (fpout == NULL) {
          fclose(fpin);
          return 1;
      }

      /* write zoomed range into auxiliary gnuplot source file */

      gretl_push_c_numeric_locale();
      fprintf(fpout, "set xrange [%g:%g]\n", plot->zoom_xmin,
            plot->zoom_xmax);
      fprintf(fpout, "set yrange [%g:%g]\n", plot->zoom_ymin,
            plot->zoom_ymax);
      gretl_pop_c_numeric_locale();

      while (fgets(line, MAXLEN-1, fpin)) {
          if (strncmp(line, "set xrange", 10) &&
            strncmp(line, "set yrange", 10))
            fputs(line, fpout);
      }

      fclose(fpout);
      fclose(fpin);

      plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot, 
                          zoomname);
    } else { 
      /* PNG_UNZOOM or PNG_START */
      plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot, 
                          plot->spec->fname);
    }

    err = gretl_spawn(plotcmd);
    g_free(plotcmd);  

    if (view == PNG_ZOOM) {
      remove(zoomname);
    }

    if (err) {
      errbox(_("Failed to generate PNG file"));
      return 1;
    }

    render_pngfile(plot, view);

    return 0;
}

static gint plot_button_release (GtkWidget *widget, GdkEventButton *event, 
                         png_plot *plot)
{
    if (plot_is_zooming(plot)) {
      double z;

      if (!get_data_xy(plot, event->x, event->y, 
                   &plot->zoom_xmax, &plot->zoom_ymax)) {
          return TRUE;
      }

      /* flip the selected rectangle if required */
      if (plot->zoom_xmin > plot->zoom_xmax) {
          z = plot->zoom_xmax;
          plot->zoom_xmax = plot->zoom_xmin;
          plot->zoom_xmin = z;
      }

      if (plot->zoom_ymin > plot->zoom_ymax) {
          z = plot->zoom_ymax;
          plot->zoom_ymax = plot->zoom_ymin;
          plot->zoom_ymin = z;
      }

      if (plot->zoom_xmin != plot->zoom_xmax &&
          plot->zoom_ymin != plot->zoom_ymax) {
          zoom_unzoom_png(plot, PNG_ZOOM);
      }

      plot->status ^= PLOT_ZOOMING;
      gdk_window_set_cursor(plot->canvas->window, NULL);
      gtk_statusbar_pop(GTK_STATUSBAR(plot->statusbar), plot->cid);
    }

    return TRUE;
}

static gint plot_button_press (GtkWidget *widget, GdkEventButton *event, 
                         png_plot *plot)
{
    if (plot_is_zooming(plot)) {
      /* think about this */
      if (get_data_xy(plot, event->x, event->y, 
                  &plot->zoom_xmin, &plot->zoom_ymin)) {
          plot->screen_xmin = event->x;
          plot->screen_ymin = event->y;
      }
      return TRUE;
    }

    if (plot_doing_position(plot)) {
      if (plot->labelpos_entry != NULL) {
          double dx, dy;
          
          if (get_data_xy(plot, event->x, event->y, &dx, &dy)) {
            gchar *posstr;

            posstr = g_strdup_printf("%g %g", dx, dy);
            gtk_entry_set_text(GTK_ENTRY(plot->labelpos_entry), posstr);
            g_free(posstr);
          }
      } 
      terminate_plot_positioning(plot);
      return TRUE;
    }

    if (plot->popup != NULL) {
      gtk_widget_destroy(plot->popup);
      plot->popup = NULL;
    }

    if (!plot->err) {
      build_plot_menu(plot);
      gtk_menu_popup(GTK_MENU(plot->popup), NULL, NULL, NULL, NULL,
                   event->button, event->time);
    }

    return TRUE;
}

static gboolean 
plot_key_handler (GtkWidget *w, GdkEventKey *key, png_plot *plot)
{
    switch (key->keyval) {
    case GDK_q:
    case GDK_Q:
      gtk_widget_destroy(w);
      break;
    case GDK_s:
    case GDK_S:
      add_graph_to_session(plot->spec, GRETL_OBJ_GRAPH, NULL);
      break;
#ifdef G_OS_WIN32
    case GDK_c:
      win32_process_graph(plot->spec, 1, WIN32_TO_CLIPBOARD);
      break;
#endif
#ifdef HAVE_AUDIO
    case GDK_a:
    case GDK_A:
      audio_render_plot(plot);
      break;
#endif
    default:
      break;
    }

    return TRUE;
}

static 
void plot_expose (GtkWidget *widget, GdkEventExpose *event,
              GdkPixmap *dbuf_pixmap)
{
    /* Don't repaint entire window on each exposure */
    gdk_window_set_back_pixmap(widget->window, NULL, FALSE);

    /* Refresh double buffer, then copy the "dirtied" area to
       the on-screen GdkWindow */
    gdk_window_copy_area(widget->window,
                   widget->style->fg_gc[GTK_STATE_NORMAL],
                   event->area.x, event->area.y,
                   dbuf_pixmap,
                   event->area.x, event->area.y,
                   event->area.width, event->area.height);
}

static void render_pngfile (png_plot *plot, int view)
{
    gint width;
    gint height;
    GdkPixbuf *pbuf;
    char pngname[MAXLEN];
    GError *error = NULL;

    build_path(pngname, paths.userdir, "gretltmp.png", NULL);

    pbuf = gdk_pixbuf_new_from_file(pngname, &error);
    if (pbuf == NULL) {
        errbox(error->message);
        g_error_free(error);
      remove(pngname);
      return;
    }

    width = gdk_pixbuf_get_width(pbuf);
    height = gdk_pixbuf_get_height(pbuf);

    if (width == 0 || height == 0) {
      errbox(_("Malformed PNG file for graph"));
      g_object_unref(pbuf);
      remove(pngname);
      return;
    }

    /* scrap any old record of which points are labeled */
    if (plot->spec->labeled != NULL) {
      free(plot->spec->labeled);
      plot->spec->labeled = NULL;
      plot->format &= ~PLOT_MARKERS_UP;
    }

    gdk_pixbuf_render_to_drawable(pbuf, plot->pixmap, 
                          plot->canvas->style->fg_gc[GTK_STATE_NORMAL],
                          0, 0, 0, 0, width, height,
                          GDK_RGB_DITHER_NONE, 0, 0);

    g_object_unref(pbuf);
    remove(pngname);
    
    if (view != PNG_START) { 
      /* we're changing the view, so refresh the whole canvas */
      gdk_window_copy_area(plot->canvas->window,
                       plot->canvas->style->fg_gc[GTK_STATE_NORMAL],
                       0, 0,
                       plot->pixmap,
                       0, 0,
                       plot->pixel_width, plot->pixel_height);
      if (view == PNG_ZOOM) {
          plot->status |= PLOT_ZOOMED;
      } else if (view == PNG_UNZOOM) {
          plot->status ^= PLOT_ZOOMED;
      }
    }
}

static void destroy_png_plot (GtkWidget *w, png_plot *plot)
{
    /* delete temporary plot source file? */
    if (!plot_is_saved(plot)) {
      remove(plot->spec->fname);
    }

#if GPDEBUG
    fprintf(stderr, "destroy_png_plot: plot = %p, spec = %p\n",
          (void *) plot, (void *) plot->spec);
#endif

    if (plot_has_controller(plot)) {
      /* if the png plot has a controller, destroy it too */
      plot->spec->ptr = NULL;
      destroy_gpt_control_window();
    } else {
      /* no controller: take responsibility for freeing the
         plot specification */
      free_plotspec(plot->spec);
    }

    if (plot->invert_gc != NULL) {
      gdk_gc_destroy(plot->invert_gc);
    }

    gtk_widget_unref(plot->shell);

    free(plot);
}

static void set_approx_pixel_bounds (png_plot *plot, 
                             int max_num_width,
                             int max_num2_width)
{
    if (plot_has_xlabel(plot)) {
      plot->pixel_ymax = plot->pixel_height - 36;
    } else {
      plot->pixel_ymax = plot->pixel_height - 24;
    }

    if (plot_has_title(plot)) {
      plot->pixel_ymin = 36;
    } else {
      plot->pixel_ymin = 14;
    }

    plot->pixel_xmin = 27 + 7 * max_num_width;
    if (plot_has_ylabel(plot)) {
      plot->pixel_xmin += 12;
    }

    plot->pixel_xmax = plot->pixel_width - 20; 
    if (plot_has_y2axis(plot)) {
      plot->pixel_xmax -= 7 * (max_num2_width + 1);
    }
    if (plot_has_y2label(plot)) {
      plot->pixel_xmax -= 11;
    }

#if POINTS_DEBUG
    fprintf(stderr, "set_approx_pixel_bounds():\n"
          " xmin=%d xmax=%d ymin=%d ymax=%d\n", 
          plot->pixel_xmin, plot->pixel_xmax,
          plot->pixel_ymin, plot->pixel_ymax);
    fprintf(stderr, "set_approx_pixel_bounds():\n"
          " max_num_width=%d max_num2_width=%d\n", 
          max_num_width, max_num2_width);
#endif
}

/* Attempt to read y-range info from the ascii representation
   of a gnuplot graph (the "dumb" terminal): return 0 on
   success, non-zero on failure.
*/

static int get_dumb_plot_yrange (png_plot *plot)
{
    FILE *fpin, *fpout;
    char line[MAXLEN], dumbgp[MAXLEN], dumbtxt[MAXLEN];
    gchar *plotcmd = NULL;
    int err = 0, x2axis = 0;
    int max_ywidth = 0;
    int max_y2width = 0;

    fpin = gretl_fopen(plot->spec->fname, "r");
    if (fpin == NULL) {
      return 1;
    }

    build_path(dumbgp, paths.userdir, "dumbplot.gp", NULL);
    build_path(dumbtxt, paths.userdir, "gptdumb.txt", NULL);
    fpout = gretl_fopen(dumbgp, "w");
    if (fpout == NULL) {
      fclose(fpin);
      return 1;
    }

    /* switch to the "dumb" (ascii) terminal in gnuplot */
    while (fgets(line, MAXLEN-1, fpin)) {
      if (strstr(line, "set term")) {
          fputs("set term dumb\n", fpout);
      } else if (strstr(line, "set output")) { 
          fprintf(fpout, "set output '%s'\n", dumbtxt);
      } else {
          fputs(line, fpout);
      }
      if (strstr(line, "x2range")) {
          x2axis = 1;
      }
    }

    fclose(fpin);
    fclose(fpout);

    plotcmd = g_strdup_printf("\"%s\" \"%s\"", paths.gnuplot,
                        dumbgp);
    err = gretl_spawn(plotcmd);
    g_free(plotcmd);

    remove(dumbgp);

    if (err) {
#if POINTS_DEBUG
      fputs("get_dumb_plot_yrange(): plot command failed\n", stderr);
#endif
      return 1;
    } else {
      double y[16] = {0};
      int y_numwidth[16] = {0};
      int y2_numwidth[16] = {0};
      char numstr[32];
      int i, j, k, imin;

      fpin = gretl_fopen(dumbtxt, "r");
      if (fpin == NULL) {
          return 1;
      }

      /* read the y-axis min and max from the ascii graph */

      gretl_push_c_numeric_locale();

      i = j = 0;
      while (i < 16 && fgets(line, MAXLEN-1, fpin)) {
          const char *s = line;
          int nsp = 0;

          while (isspace((unsigned char) *s)) {
              nsp++;
              s++;
            }
          if (nsp > 5) {
            /* not a y-axis number */
            continue; 
          }
          if (sscanf(s, "%lf", &y[i]) == 1) {
#if POINTS_DEBUG
            fprintf(stderr, "from text plot: read y[%d]=%g\n",
                  i, y[i]);
#endif
            sscanf(s, "%31s", numstr);
            y_numwidth[i++] = strlen(numstr);
          }
          if (plot_has_y2axis(plot) && j < 16) {
            double y2;

            s = strrchr(s, ' ');
            if (s != NULL && sscanf(s, "%lf", &y2) == 1) {
                sscanf(s, "%31s", numstr);
                y2_numwidth[j++] = strlen(numstr);
            }
          }
      }

      gretl_pop_c_numeric_locale();

      fclose(fpin);
#if (POINTS_DEBUG == 0)
      remove(dumbtxt);
#endif

      imin = (x2axis)? 1 : 0;

      if (i > (imin + 2) && y[imin] > y[i-1]) {
          plot->ymin = y[i-1];
          plot->ymax = y[imin];
          for (k=imin; k<i-1; k++) {
            if (y_numwidth[k] > max_ywidth) {
                max_ywidth = y_numwidth[k];
            }
          }
      }         

#if POINTS_DEBUG
      fprintf(stderr, "Reading y range from text plot: plot->ymin=%g, "
            "plot->ymax=%g\n", plot->ymin, plot->ymax);
#endif

      if (plot_has_y2axis(plot)) {
          for (k=imin; k<j-2; k++) {
            if (y2_numwidth[k] > max_y2width) {
                max_y2width = y2_numwidth[k];
            }
          }
      }
    }

    if (plot->ymax <= plot->ymin) {
      err = 1;
    }

    if (!err) {
      set_approx_pixel_bounds(plot, max_ywidth, max_y2width);
    }
    
    return err;
}

/* Do a partial parse of the gnuplot source file: enough to determine
   the data ranges so we can read back the mouse pointer coordinates
   when the user moves the pointer over the graph.
*/

static int get_plot_ranges (png_plot *plot)
{
    FILE *fp;
    char line[MAXLEN];
    int got_x = 0;
#ifdef PNG_COMMENTS
    int got_y = 0;
    png_bounds b;
#endif
    int err = 0;

#if GPDEBUG
    fprintf(stderr, "get_plot_ranges: plot=%p, plot->spec=%p\n", 
          (void *) plot, (void *) plot->spec);
#endif    

    plot->xmin = plot->xmax = 0.0;
    plot->ymin = plot->ymax = 0.0;   
    plot->xint = plot->yint = 0;
    plot->pd = 0;

    fp = gretl_fopen(plot->spec->fname, "r");
    if (fp == NULL) {
      plot->status |= (PLOT_DONT_ZOOM | PLOT_DONT_MOUSE);
      return 1;
    }

    gretl_push_c_numeric_locale();

    while (fgets(line, MAXLEN-1, fp) && strncmp(line, "plot ", 5)) {
      if (sscanf(line, "set xrange [%lf:%lf]", 
               &plot->xmin, &plot->xmax) == 2) { 
          got_x = 1;
      } 
    }

    gretl_pop_c_numeric_locale();

    fclose(fp);

#ifdef PNG_COMMENTS
    /* now try getting accurate coordinate info from PNG file */
    if (get_png_bounds_info(&b) == GRETL_PNG_OK) {
      plot->status |= PLOT_PNG_COORDS;
      got_x = got_y = 1;
      plot->pixel_xmin = b.xleft;
      plot->pixel_xmax = b.xright;
      plot->pixel_ymin = plot->pixel_height - b.ytop;
      plot->pixel_ymax = plot->pixel_height - b.ybot;
      plot->xmin = b.xmin;
      plot->xmax = b.xmax;
      plot->ymin = b.ymin;
      plot->ymax = b.ymax;
# if POINTS_DEBUG
      fprintf(stderr, "get_png_bounds_info():\n"
            " xmin=%d xmax=%d ymin=%d ymax=%d\n", 
            plot->pixel_xmin, plot->pixel_xmax,
            plot->pixel_ymin, plot->pixel_ymax);
# endif
    } else {
      fprintf(stderr, "get_png_bounds_info(): failed\n");
    }
#endif /* PNG_COMMENTS */

    /* If got_x = 0 at this point, we didn't an x-range out of
       the gnuplot source file OR the PNG file, so we might as
       well give up.
    */

    if (got_x) {
      plot->status |= PLOT_HAS_XRANGE;
    } else {
      plot->status |= (PLOT_DONT_ZOOM | PLOT_DONT_MOUSE);
      return 1;
    }    

    /* get the "dumb" y coordinates only if we haven't got
       more accurate ones from the PNG file */
    if (!plot_has_png_coords(plot)) { 
      err = get_dumb_plot_yrange(plot);
    }

    if (!err) {
      plot->status |= PLOT_HAS_YRANGE;
      if ((plot->xmax - plot->xmin) / 
          (plot->pixel_xmax - plot->pixel_xmin) >= 1.0) {
          plot->xint = 1;
      }
      if ((plot->ymax - plot->ymin) / 
          (plot->pixel_ymax - plot->pixel_ymin) >= 1.0) {
          plot->yint = 1;
      }
    } else {
      plot->status |= (PLOT_DONT_ZOOM | PLOT_DONT_MOUSE);
#if POINTS_DEBUG 
      fputs("get_plot_ranges: setting PLOT_DONT_ZOOM, PLOT_DONT_MOUSE\n", 
            stderr);
#endif
    }

    return err;
}

static png_plot *png_plot_new (void)
{
    png_plot *plot = mymalloc(sizeof *plot);

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

    plot->shell = NULL;
    plot->canvas = NULL;
    plot->popup = NULL;
    plot->statusarea = NULL;    
    plot->statusbar = NULL;
    plot->cursor_label = NULL;
    plot->pixmap = NULL;
    plot->invert_gc = NULL;
    plot->spec = NULL;

    plot->pixel_width = 640;
    plot->pixel_height = 480;

    plot->xmin = plot->xmax = 0.0;
    plot->ymin = plot->ymax = 0.0;
    plot->xint = plot->yint = 0;

    plot->zoom_xmin = plot->zoom_xmax = 0.0;
    plot->zoom_ymin = plot->zoom_ymax = 0.0;
    plot->screen_xmin = plot->screen_ymin = 0;

    plot->pd = 0;
    plot->err = 0;
    plot->cid = 0;
    plot->status = 0;
    plot->format = 0;

    return plot;
}

int gnuplot_show_png (const char *plotfile, GPT_SPEC *spec, int saved)
{
    GtkWidget *vbox;
    GtkWidget *canvas_hbox;
    GtkWidget *label_frame = NULL;
    GtkWidget *status_hbox = NULL;
    png_plot *plot;
    int polar = 0;
    int err = 0;

#if GPDEBUG
    fprintf(stderr, "gnuplot_show_png:\n plotfile='%s', spec=%p, saved=%d\n",
          plotfile, (void *) spec, saved);
#endif

    plot = png_plot_new();
    if (plot == NULL) {
      return 1;
    }

    if (spec != NULL) {
      plot->spec = spec;
    } else {
      plot->spec = plotspec_new();
      if (plot->spec == NULL) {
          free(plot);
          return 1;
      }
      strcpy(plot->spec->fname, plotfile);
    }

    if (saved) {
      plot->status |= PLOT_SAVED;
    }

    /* make png plot struct accessible via spec */
    plot->spec->ptr = plot;

    /* Parse the gnuplot source file.  If we hit errors here,
       flag this, but it's not necessarily a show-stopper in
       terms of simply displaying the graph. 
    */
    err = read_plotspec_from_file(plot->spec, &plot->pd, &polar);

    if (err) {
      plot->err = 1;
      plot->status |= (PLOT_DONT_EDIT | PLOT_DONT_ZOOM | PLOT_DONT_MOUSE);
    } else if (cant_edit(plot->spec->code)) {
      if (plot->spec->n_markers > 0) {
          plot->status |= (PLOT_DONT_EDIT | PLOT_DONT_ZOOM);
          get_plot_ranges(plot);
          if (polar) {
            plot->format |= PLOT_POLAR;
          }
      } else {
          plot->status |= (PLOT_DONT_EDIT | PLOT_DONT_ZOOM | PLOT_DONT_MOUSE);
      }
    } else {
      set_plot_format_flags(plot);
      get_plot_ranges(plot);
    } 

    if (plot->spec->code == PLOT_VAR_ROOTS) {
      plot->pixel_width = plot->pixel_height;
    }

    plot->shell = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    /* note need for corresponding unref */
    gtk_widget_ref(plot->shell);

    gtk_window_set_title(GTK_WINDOW(plot->shell), _("gretl: gnuplot graph")); 
    gtk_window_set_resizable(GTK_WINDOW(plot->shell), FALSE);

    vbox = gtk_vbox_new(FALSE, 2);
    gtk_container_add(GTK_CONTAINER(plot->shell), vbox);

    g_signal_connect(G_OBJECT(plot->shell), "destroy",
                 G_CALLBACK(destroy_png_plot), plot);
    g_signal_connect(G_OBJECT(plot->shell), "key_press_event", 
                 G_CALLBACK(plot_key_handler), plot);

    /* box to hold canvas */
    canvas_hbox = gtk_hbox_new(FALSE, 1);
    gtk_box_pack_start(GTK_BOX(vbox), canvas_hbox, TRUE, TRUE, 0);
    gtk_widget_show(canvas_hbox);

    /*  eventbox and hbox for status area  */
    plot->statusarea = gtk_event_box_new();
    gtk_box_pack_start(GTK_BOX(vbox), plot->statusarea, FALSE, FALSE, 0);

    status_hbox = gtk_hbox_new (FALSE, 2);
    gtk_container_add(GTK_CONTAINER(plot->statusarea), status_hbox);
    gtk_widget_show (status_hbox);
    gtk_container_set_resize_mode (GTK_CONTAINER (status_hbox),
                           GTK_RESIZE_QUEUE);

    /* Create drawing-area widget */
    plot->canvas = gtk_drawing_area_new();
    gtk_widget_set_size_request(GTK_WIDGET(plot->canvas), 
                        plot->pixel_width, plot->pixel_height);
    gtk_widget_set_events (plot->canvas, GDK_EXPOSURE_MASK
                           | GDK_LEAVE_NOTIFY_MASK
                           | GDK_BUTTON_PRESS_MASK
                           | GDK_BUTTON_RELEASE_MASK
                           | GDK_POINTER_MOTION_MASK
                           | GDK_POINTER_MOTION_HINT_MASK);

    GTK_WIDGET_SET_FLAGS (plot->canvas, GTK_CAN_FOCUS);

    g_signal_connect(G_OBJECT(plot->canvas), "button_press_event", 
                 G_CALLBACK(plot_button_press), plot);
    g_signal_connect(G_OBJECT(plot->canvas), "button_release_event", 
                 G_CALLBACK(plot_button_release), plot);

    /* create the contents of the status area */
    if (plot_has_xrange(plot)) {
      /* cursor label (graph position indicator) */
      label_frame = gtk_frame_new(NULL);
      gtk_frame_set_shadow_type(GTK_FRAME(label_frame), GTK_SHADOW_IN);

      plot->cursor_label = gtk_label_new(" ");
      gtk_container_add(GTK_CONTAINER(label_frame), plot->cursor_label);
      gtk_widget_show(plot->cursor_label);
    }

    /* the statusbar */
    plot->statusbar = gtk_statusbar_new();

    gtk_widget_set_size_request(plot->statusbar, 1, -1);
    gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(plot->statusbar), FALSE);

    gtk_container_set_resize_mode(GTK_CONTAINER (plot->statusbar),
                          GTK_RESIZE_QUEUE);
    plot->cid = gtk_statusbar_get_context_id(GTK_STATUSBAR(plot->statusbar),
                                   "plot_message");

    if (!plot->err) {
      gtk_statusbar_push(GTK_STATUSBAR(plot->statusbar),
                     plot->cid, _(" Click on graph for pop-up menu"));
    }
    
    if (plot_has_xrange(plot)) {
      g_signal_connect(G_OBJECT(plot->canvas), "motion_notify_event",
                   G_CALLBACK(motion_notify_event), plot);
    }

    /* pack the widgets */
    gtk_box_pack_start(GTK_BOX(canvas_hbox), plot->canvas, FALSE, FALSE, 0);

    /* fill the status area */
    if (plot_has_xrange(plot)) {
      gtk_box_pack_start(GTK_BOX(status_hbox), label_frame, FALSE, FALSE, 0);
    }

    gtk_box_pack_start(GTK_BOX(status_hbox), plot->statusbar, TRUE, TRUE, 0); 

    /* show stuff */
    gtk_widget_show(plot->canvas);

    if (plot_has_xrange(plot)) {
      gtk_widget_show(label_frame);
    }

    gtk_widget_show(plot->statusbar);
    gtk_widget_show(plot->statusarea);

    gtk_widget_realize(plot->canvas);
    gdk_window_set_back_pixmap(plot->canvas->window, NULL, FALSE);

    if (plot_has_xrange(plot)) {
      gtk_widget_realize(plot->cursor_label);
      gtk_widget_set_size_request(plot->cursor_label, 140, -1);
    }

    gtk_widget_show(vbox);
    gtk_widget_show(plot->shell);       

    /* set the focus to the canvas area */
    gtk_widget_grab_focus(plot->canvas);  

    plot->pixmap = gdk_pixmap_new(plot->shell->window, 
                          plot->pixel_width, plot->pixel_height, 
                          -1);
    g_signal_connect(G_OBJECT(plot->canvas), "expose_event",
                 G_CALLBACK(plot_expose), plot->pixmap);

    render_pngfile(plot, PNG_START);

    return 0;
}

/* apparatus for getting coordinate info out of PNG files created using
   Allin Cottrell's modified version of gnuplot, which writes such info
   into the PNG comment fields
*/

#ifdef PNG_COMMENTS

static int get_png_plot_bounds (const char *str, png_bounds *bounds)
{
    int ret = GRETL_PNG_OK;

    if (sscanf(str, "xleft=%d xright=%d ybot=%d ytop=%d", 
             &bounds->xleft, &bounds->xright,
             &bounds->ybot, &bounds->ytop) != 4) {
      ret = GRETL_PNG_BAD_COMMENTS;
    } else if (bounds->xleft == 0 && bounds->xright == 0 &&
             bounds->ybot == 0 && bounds->ytop == 0) {
      ret = GRETL_PNG_NO_COORDS;
    } 

    return ret;
}

static int get_png_data_bounds (char *str, png_bounds *bounds)
{
    char *p = str;
    int ret = GRETL_PNG_OK;

    while (*p) {
      if (*p == ',') *p = '.';
      p++;
    }

    gretl_push_c_numeric_locale();

    if (sscanf(str, "xmin=%lf xmax=%lf ymin=%lf ymax=%lf", 
             &bounds->xmin, &bounds->xmax,
             &bounds->ymin, &bounds->ymax) != 4) {
      ret = GRETL_PNG_BAD_COMMENTS;
    } else if (bounds->xmin == 0.0 && bounds->xmax == 0.0 &&
             bounds->ymin == 0.0 && bounds->ymax == 0.0) {
      ret = GRETL_PNG_NO_COORDS;
    } 

    gretl_pop_c_numeric_locale();

    return ret;
}

#define PNG_CHECK_BYTES 4

static int get_png_bounds_info (png_bounds *bounds)
{
    FILE *fp;
    unsigned char header[PNG_CHECK_BYTES];
    char pngname[MAXLEN];
    png_structp png_ptr;
    png_infop info_ptr;
    png_text *text_ptr = NULL;
    int i, num_text;
    volatile int ret = GRETL_PNG_OK;

    build_path(pngname, paths.userdir, "gretltmp.png", NULL); 

    fp = gretl_fopen(pngname, "rb");
    if (fp == NULL) {
      return GRETL_PNG_NO_OPEN;
    }

    fread(header, 1, PNG_CHECK_BYTES, fp);

    if (png_sig_cmp(header, 0, PNG_CHECK_BYTES)) {
      fclose(fp);
      errbox("Bad PNG header: Got bytes %x %x %x %x", 
             header[0],header[1],header[2],header[3]);
      return GRETL_PNG_NOT_PNG;
    }

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
                             NULL, NULL, NULL);
    if (png_ptr == NULL) {
      fclose(fp);
      return GRETL_PNG_NO_OPEN;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL) {
        png_destroy_read_struct(&png_ptr, (png_infopp) NULL, 
                        (png_infopp) NULL);
      fclose(fp);
        return GRETL_PNG_NO_OPEN;
    }

    if (setjmp(png_ptr->jmpbuf)) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        return GRETL_PNG_NO_OPEN;
    }

    png_init_io(png_ptr, fp);

    png_set_sig_bytes(png_ptr, PNG_CHECK_BYTES);
    png_read_info(png_ptr, info_ptr);

    num_text = png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);

    if (num_text > 1) {
      int plot_ret = -1, data_ret = -1;

      for (i=1; i<num_text; i++) {
          if (!strcmp(text_ptr[i].key, "plot bounds")) {
            plot_ret = get_png_plot_bounds(text_ptr[i].text, bounds);
          }
          if (!strcmp(text_ptr[i].key, "data bounds")) {
            data_ret = get_png_data_bounds(text_ptr[i].text, bounds);
          }
      }
      if (plot_ret == GRETL_PNG_NO_COORDS && data_ret == GRETL_PNG_NO_COORDS) {
          /* comments were present and correct, but all zero */
          ret = GRETL_PNG_NO_COORDS;
      }
      else if (plot_ret != GRETL_PNG_OK || data_ret != GRETL_PNG_OK) {
          /* one or both set of coordinates bad or missing */
          if (plot_ret >= 0 || data_ret >= 0) {
            ret = GRETL_PNG_BAD_COMMENTS;
          } else {
            ret = GRETL_PNG_NO_COMMENTS;
          }
      }
    } else {
      /* no coordinates comments present */
      ret = GRETL_PNG_NO_COMMENTS;
    }

    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    fclose(fp);
    
    return ret;
}

#endif /* PNG_COMMENTS */

Generated by  Doxygen 1.6.0   Back to index