/*
 * $Id: x11commn.c,v 1.60 2010-03-04 08:30:17 hito Exp $
 *
 * This file is part of "Ngraph for X11".
 *
 * Copyright (C) 2002, Satoshi ISHIZAKA. isizaka@msa.biglobe.ne.jp
 *
 * "Ngraph for X11" 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.
 *
 * "Ngraph for X11" is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include "gtk_common.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>

#include "dir_defs.h"
#include "object.h"
#include "ioutil.h"
#include "shell.h"
#include "nstring.h"
#include "odata.h"
#include "odraw.h"
#include "nconfig.h"

#include "init.h"
#include "gtk_widget.h"
#include "x11gui.h"
#include "x11graph.h"
#include "x11dialg.h"
#include "ox11menu.h"
#include "x11menu.h"
#include "x11commn.h"
#include "x11info.h"
#include "x11file.h"
#include "x11view.h"

#define COMMENT_BUF_SIZE 1024

struct GraphSave_data
{
  int storedata, storemerge, path;
  char *file, *current_wd, *prev_wd;
  response_cb cb;
  gpointer data;
};

static GtkWidget *ProgressDialog = NULL;
#define PROGRESSBAR_N 2
static GtkProgressBar *ProgressBar[PROGRESSBAR_N];
static GtkWidget *ProgressFrame, *ProgressText, *ProgressButton;
static unsigned int SaveCursor;

static void AddNgpFileList(const char *file);
static void ToFullPath(void);
static void ToBasename(void);
static void ToRalativePath(void);
static void ProgressDialogFinalize(void);
static void GraphSave_response(int ret, struct GraphSave_data *d);

void
OpenGRA(void)
{
  int i, id, otherGC;
  char *device, *name, *name_str = "viewer";
  struct narray *drawrable;
  N_VALUE *gra_inst;

  gra_inst = chkobjinstoid(Menulocal.GRAobj, Menulocal.GRAoid);
  if (gra_inst) {
    return;
  }

  CloseGRA();

  for (i = chkobjlastinst(Menulocal.GRAobj); i >= 0; i--) {
    getobj(Menulocal.GRAobj, "GC", i, 0, NULL, &otherGC);
    if (otherGC < 0)
      break;
  }

  if (i == -1) {
    /* closed gra object is not found. generate new gra object */

    id = newobj(Menulocal.GRAobj);
    gra_inst = chkobjinst(Menulocal.GRAobj, id);
    _getobj(Menulocal.GRAobj, "oid", gra_inst, &(Menulocal.GRAoid));

    /* set page settings */
    putobj(Menulocal.GRAobj, "paper_width", id, &(Menulocal.PaperWidth));
    putobj(Menulocal.GRAobj, "paper_height", id, &(Menulocal.PaperHeight));
    putobj(Menulocal.GRAobj, "left_margin", id, &(Menulocal.LeftMargin));
    putobj(Menulocal.GRAobj, "top_margin", id, &(Menulocal.TopMargin));
    putobj(Menulocal.GRAobj, "zoom", id, &(Menulocal.PaperZoom));
    putobj(Menulocal.GRAobj, "decimalsign", id, &(Menulocal.Decimalsign));
    CheckPage();
  } else {
    /* find closed gra object */

    id = i;
    gra_inst = chkobjinst(Menulocal.GRAobj, id);
    _getobj(Menulocal.GRAobj, "oid", gra_inst, &(Menulocal.GRAoid));

    /* get page settings */
    CheckPage();
  }

  if (arraynum(&(Menulocal.drawrable)) > 0) {
    unsigned int j;
    drawrable = arraynew(sizeof(char *));
    for (j = 0; j < arraynum(&(Menulocal.drawrable)); j++) {
      arrayadd2(drawrable, *(char **)arraynget(&(Menulocal.drawrable), j));
    }
  } else {
    drawrable = NULL;
  }
  putobj(Menulocal.GRAobj, "draw_obj", id, drawrable);
  device = g_strdup("menu:0");
  putobj(Menulocal.GRAobj, "device", id, device);
  name = g_strdup(name_str);
  putobj(Menulocal.GRAobj, "name", id, name);
  getobj(Menulocal.GRAobj, "open", id, 0, NULL, &(Menulocal.GC));
}

void
CheckPage(void)
{
  N_VALUE *gra_inst;

  gra_inst = chkobjinstoid(Menulocal.GRAobj, Menulocal.GRAoid);
  if (gra_inst == NULL) {
    return;
  }

  _getobj(Menulocal.GRAobj, "paper_width", gra_inst,
	  &(Menulocal.PaperWidth));
  _getobj(Menulocal.GRAobj, "paper_height", gra_inst,
	  &(Menulocal.PaperHeight));
  _getobj(Menulocal.GRAobj, "left_margin", gra_inst,
	  &(Menulocal.LeftMargin));
  _getobj(Menulocal.GRAobj, "top_margin", gra_inst,
	  &(Menulocal.TopMargin));
  _getobj(Menulocal.GRAobj, "zoom", gra_inst,
	  &(Menulocal.PaperZoom));
  _getobj(Menulocal.GRAobj, "decimalsign", gra_inst,
	  &(Menulocal.Decimalsign));

  set_paper_type(Menulocal.PaperWidth, Menulocal.PaperHeight);
}

static int
ValidGRA(void)
{
  int id;
  struct objlist *graobj;
  N_VALUE *inst;

  if ((graobj = chkobject("gra")) == NULL)
    return -1;
  id = -1;
  if ((inst = chkobjinstoid(graobj, Menulocal.GRAoid)) != NULL)
    _getobj(graobj, "id", inst, &id);
  if (id == -1)
    id = chkobjlastinst(graobj);
  return id;
}

void
CloseGRA(void)
{
  int id;
  N_VALUE *gra_inst;

  gra_inst = chkobjinstoid(Menulocal.GRAobj, Menulocal.GRAoid);
  if (gra_inst == NULL) {
    return;
  }

  _getobj(Menulocal.GRAobj, "id", gra_inst, &id);
  exeobj(Menulocal.GRAobj, "close", id, 0, NULL);
  delobj(Menulocal.GRAobj, id);
  Menulocal.GRAoid = -1;
}

void
ChangeGRA(void)
{
  int i, otherGC;

  /* search for closed gra object */
  for (i = chkobjlastinst(Menulocal.GRAobj); i >= 0; i--) {
    getobj(Menulocal.GRAobj, "GC", i, 0, NULL, &otherGC);
    if (otherGC < 0)
      break;
  }

  if (i == -1) {
    /* closed gra is not find. maintain present gra object */
    if (chkobjinstoid(Menulocal.GRAobj, Menulocal.GRAoid) == NULL) {
      ChangePage();
    }
  } else {
    /* use closed gra object */
    ChangePage();
  }

  CheckPage();
}

void
SetPageSettingsToGRA(void)
{
  int i, otherGC;
  struct objlist *obj;
  struct narray *drawrable;

  if ((obj = chkobject("gra")) == NULL)
    return;

  for (i = chkobjlastinst(obj); i >= 0; i--) {
    getobj(obj, "GC", i, 0, NULL, &otherGC);
    if (otherGC < 0)
      break;
  }

  if (i >= 0) {
    int id, j, num;
    N_VALUE *inst;
    id = i;
    inst = chkobjinst(obj, id);

    putobj(obj, "paper_width", id, &(Menulocal.PaperWidth));
    putobj(obj, "paper_height", id, &(Menulocal.PaperHeight));
    putobj(obj, "left_margin", id, &(Menulocal.LeftMargin));
    putobj(obj, "top_margin", id, &(Menulocal.TopMargin));
    putobj(obj, "zoom", id, &(Menulocal.PaperZoom));
    putobj(obj, "decimalsign", id, &(Menulocal.Decimalsign));

    _getobj(obj, "draw_obj", inst, &drawrable);

    arrayfree2(drawrable);

    drawrable = arraynew(sizeof(char *));

    num = arraynum(&(Menulocal.drawrable));

    for (j = 0; j < num; j++) {
      arrayadd2(drawrable, *(char **) arraynget(&(Menulocal.drawrable), j));
    }

    _putobj(obj, "draw_obj", inst, drawrable);
  }
}

void
GetPageSettingsFromGRA(void)
{
  int i, otherGC;
  struct objlist *obj;
  struct narray *drawrable;

  if ((obj = chkobject("gra")) == NULL)
    return;

  for (i = chkobjlastinst(obj); i >= 0; i--) {
    getobj(obj, "GC", i, 0, NULL, &otherGC);
    if (otherGC < 0)
      break;
  }

  if (i >= 0) {
    int id, num;
    N_VALUE *inst;
    id = i;
    inst = chkobjinst(obj, id);
    CheckPage();
    _getobj(obj, "draw_obj", inst, &drawrable);
    arraydel2(&(Menulocal.drawrable));
    num = arraynum(drawrable);

    if (num == 0) {
      menuadddrawrable(chkobject("draw"), &(Menulocal.drawrable));
    } else {
      struct objlist *dobj;
      int j;
      for (j = 0; j < num; j++) {
        char *name;
	name = arraynget_str(drawrable, j);
	if (name == NULL) {
	  continue;
	}
	dobj = chkobject(name);
	if (dobj == NULL) {
	  continue;
	}
	arrayadd2(&(Menulocal.drawrable), chkobjectname(dobj));
      }
      arrayuniq_all_str(&(Menulocal.drawrable));
    }
  }

  ChangeGRA();
}

static int
get_new_axis_id(struct objlist *obj, struct objlist **aobj, int fid, int id, int a)
{
  int spc, aid = 0;
  char *axis;
  struct narray iarray;
  int anum;

  if (getobj(obj, (a == AXIS_X) ? "axis_x" : "axis_y", fid, 0, NULL, &axis) < 0) {
    return -1;
  }

  if (axis == NULL) {
    return -1;
  }

  arrayinit(&iarray, sizeof(int));
  if (getobjilist(axis, aobj, &iarray, FALSE, &spc)) {
    return -1;
  }

  anum = arraynum(&iarray);
  if (anum > 0 && spc == OBJ_LIST_SPECIFIED_BY_ID) {
    aid = arraylast_int(&iarray);
    if (aid > id) {
      aid--;
    }
  } else {
    aid = -1;
  }
  arraydel(&iarray);

  return aid;
}

static void
AxisDel2(int id)
{
  struct objlist *obj, *aobj;
  int i, aid1;
  char *axis2;

  obj = getobject("axisgrid");
  if (obj) {
    for (i = chkobjlastinst(obj); i >= 0; i--) {
      N_VALUE *inst;
      inst = chkobjinst(obj, i);
      if (inst == NULL) {
	continue;
      }

      aid1 = get_axis_id(obj, inst, &aobj, AXIS_X);
      if (aid1 == id) {
	delobj(obj, i);
	continue;
      }

      aid1 = get_axis_id(obj, inst, &aobj, AXIS_Y);
      if (aid1 == id) {
	delobj(obj, i);
      }
    }
  }

  obj = getobject("data");
  if (obj == NULL) {
    return;
  }
  for (i = 0; i <= chkobjlastinst(obj); i++) {
    int aid2;
    aid1 = get_new_axis_id(obj, &aobj, i, id, AXIS_X);
    aid2 = get_new_axis_id(obj, &aobj, i, id, AXIS_Y);
    if ((aid1 >= 0) && (aid2 >= 0)) {
      if (aid1 == aid2) {
	aid2 = aid1 + 1;
      }

      axis2 = g_strdup_printf("%s:%d", chkobjectname(aobj), aid1);
      if (axis2) {
	putobj(obj, "axis_x", i, axis2);
      }

      axis2 = g_strdup_printf("%s:%d", chkobjectname(aobj), aid2);
      if (axis2) {
	putobj(obj, "axis_y", i, axis2);
      }
    }
  }
}

void
AxisDel(int id)
{
  struct objlist *obj;
  int i, lastinst, *id_array, n;
  char *group, *group2;
  char type;
  N_VALUE *inst;
  char group3[20];

  obj = chkobject("axis");
  if (obj == NULL)
    return;

  inst = chkobjinst(obj, id);
  if (inst == NULL)
    return;

  _getobj(obj, "group", inst, &group);
  if (group == NULL || group[0] == 'a') {
    AxisDel2(id);
    delobj(obj, id);
    return;
  }

  lastinst = chkobjlastinst(obj);
  type = group[0];
  strncpy(group3, group, sizeof(group3) - 1);
  group3[sizeof(group3) - 1] = '\0';

  id_array = g_malloc(sizeof(*id_array) * (lastinst + 1));
  if (id_array == NULL)
    return;

  n = 0;
  for (i = lastinst; i >= 0; i--) {
    N_VALUE *inst2;
    inst2 = chkobjinst(obj, i);
    _getobj(obj, "group", inst2, &group2);
    if (group2 && group2[0] == type) {
      if (strcmp(group3 + 2, group2 + 2) == 0) {
	AxisDel2(i);
	id_array[n] = i;
	n++;
      }
    }
  }

  for (i = 0; i < n; i++) {
    delobj(obj, id_array[i]);
  }
  g_free(id_array);
}

static void
axis_move_each_obj(char *axis_str, int i, struct objlist *obj, int id1, int id2)
{
  struct objlist *aobj;
  int spc;
  char *axis;
  struct narray iarray;
  int anum;

  if (getobj(obj, axis_str, i, 0, NULL, &axis) < 0 || axis == NULL)
    return;

  arrayinit(&iarray, sizeof(int));
  if (getobjilist(axis, &aobj, &iarray, FALSE, &spc))
    return;

  anum = arraynum(&iarray);
  if (anum > 0 && spc == OBJ_LIST_SPECIFIED_BY_ID) {
    int aid;
    char *axis2;
    aid = arraylast_int(&iarray);
    if (aid == id1) {
      aid = id2;
    } else {
      if (aid > id1)
	aid--;
      if (aid >= id2)
	aid++;
    }
    axis2 = g_strdup_printf("%s:%d", chkobjectname(aobj), aid);
    if (axis2) {
      putobj(obj, axis_str, i, axis2);
    }
  }
  arraydel(&iarray);
}

void
AxisMove(int id1, int id2)
{
  struct objlist *obj;
  int i;

  if ((obj = getobject("data")) == NULL)
    return;

  for (i = 0; i <= chkobjlastinst(obj); i++) {
    axis_move_each_obj("axis_x", i, obj, id1, id2);
    axis_move_each_obj("axis_y", i, obj, id1, id2);
  }
}

static void
AxisNameToGroup(void)		/* this function may exist for compatibility with older version. */
{
  int idx, idy, idu, idr;
  int findX, findY, findU, findR, graphtype;
  int id, id2, num;
  struct objlist *obj;
  char *name, *name2, *gp;
  struct narray group, iarray;
  int a, j;
  char *argv[2];

  if ((obj = (struct objlist *) chkobject("axis")) == NULL)
    return;
  num = chkobjlastinst(obj);
  arrayinit(&iarray, sizeof(int));
  for (id = 0; id <= num; id++) {
    int anum;
    int *data;
    N_VALUE *inst;
    anum = arraynum(&iarray);
    data = arraydata(&iarray);
    for (j = 0; j < anum; j++)
      if (data[j] == id)
	break;
    inst = chkobjinst(obj, id);
    _getobj(obj, "name", inst, &name);
    _getobj(obj, "group", inst, &gp);
    if ((j == anum) && (gp[0] == 'a')) {
      findX = findY = findU = findR = FALSE;
      graphtype = -1;
      if (name != NULL) {
	if (strncmp(name, "sectionX", 8) == 0) {
	  graphtype = 0;
	  findX = TRUE;
	  idx = id;
	} else if (strncmp(name, "sectionY", 8) == 0) {
	  graphtype = 0;
	  findY = TRUE;
	  idy = id;
	} else if (strncmp(name, "sectionU", 8) == 0) {
	  graphtype = 0;
	  findU = TRUE;
	  idu = id;
	} else if (strncmp(name, "sectionR", 8) == 0) {
	  graphtype = 0;
	  findR = TRUE;
	  idr = id;
	} else if (strncmp(name, "frameX", 6) == 0) {
	  graphtype = 1;
	  findX = TRUE;
	  idx = id;
	} else if (strncmp(name, "frameY", 6) == 0) {
	  graphtype = 1;
	  findY = TRUE;
	  idy = id;
	} else if (strncmp(name, "frameU", 6) == 0) {
	  graphtype = 1;
	  findU = TRUE;
	  idu = id;
	} else if (strncmp(name, "frameR", 6) == 0) {
	  graphtype = 1;
	  findR = TRUE;
	  idr = id;
	} else if (strncmp(name, "crossX", 6) == 0) {
	  graphtype = 2;
	  findX = TRUE;
	  idx = id;
	} else if (strncmp(name, "crossY", 6) == 0) {
	  graphtype = 2;
	  findY = TRUE;
	  idy = id;
	}
      }
      for (id2 = id + 1; id2 <= num; id2++) {
	for (j = 0; j < anum; j++)
	  if (data[j] == id2)
	    break;
	inst = chkobjinst(obj, id2);
	_getobj(obj, "name", inst, &name2);
	_getobj(obj, "group", inst, &gp);
	if ((j == anum) && (gp[0] == 'a')) {
	  if (graphtype == 0) {
	    if (name2 != NULL) {
	      if ((strncmp(name2, "sectionX", 8) == 0)
		  && (strcmp(name + 8, name2 + 8) == 0)) {
		findX = TRUE;
		idx = id2;
	      } else if ((strncmp(name2, "sectionY", 8) == 0)
			 && (strcmp(name + 8, name2 + 8) == 0)) {
		findY = TRUE;
		idy = id2;
	      } else if ((strncmp(name2, "sectionU", 8) == 0)
			 && (strcmp(name + 8, name2 + 8) == 0)) {
		findU = TRUE;
		idu = id2;
	      } else if ((strncmp(name2, "sectionR", 8) == 0)
			 && (strcmp(name + 8, name2 + 8) == 0)) {
		findR = TRUE;
		idr = id2;
	      }
	    }
	  } else if (graphtype == 1) {
	    if (name2 != NULL) {
	      if ((strncmp(name2, "frameX", 6) == 0)
		  && (strcmp(name + 6, name2 + 6) == 0)) {
		findX = TRUE;
		idx = id2;
	      } else if ((strncmp(name2, "frameY", 6) == 0)
			 && (strcmp(name + 6, name2 + 6) == 0)) {
		findY = TRUE;
		idy = id2;
	      } else if ((strncmp(name2, "frameU", 6) == 0)
			 && (strcmp(name + 6, name2 + 6) == 0)) {
		findU = TRUE;
		idu = id2;
	      } else if ((strncmp(name2, "frameR", 6) == 0)
			 && (strcmp(name + 6, name2 + 6) == 0)) {
		findR = TRUE;
		idr = id2;
	      }
	    }
	  } else if (graphtype == 2) {
	    if (name2 != NULL) {
	      if ((strncmp(name2, "crossX", 6) == 0)
		  && (strcmp(name + 6, name2 + 6) == 0)) {
		findX = TRUE;
		idx = id2;
	      } else if ((strncmp(name2, "crossY", 6) == 0)
			 && (strcmp(name + 6, name2 + 6) == 0)) {
		findY = TRUE;
		idy = id2;
	      }
	    }
	  }
	}
      }
      if ((graphtype == 0) && findX && findY && findU && findR) {
	arrayinit(&group, sizeof(int));
	a = 2;
	arrayadd(&group, &a);
	arrayadd(&group, &idx);
	arrayadd(&group, &idy);
	arrayadd(&group, &idu);
	arrayadd(&group, &idr);
	argv[0] = (char *) &group;
	argv[1] = NULL;
	exeobj(obj, "grouping", id, 1, argv);
	arraydel(&group);
	arrayadd(&iarray, &idx);
	arrayadd(&iarray, &idy);
	arrayadd(&iarray, &idu);
	arrayadd(&iarray, &idr);
      } else if ((graphtype == 1) && findX && findY && findU && findR) {
	arrayinit(&group, sizeof(int));
	a = 1;
	arrayadd(&group, &a);
	arrayadd(&group, &idx);
	arrayadd(&group, &idy);
	arrayadd(&group, &idu);
	arrayadd(&group, &idr);
	argv[0] = (char *) &group;
	argv[1] = NULL;
	exeobj(obj, "grouping", id, 1, argv);
	arraydel(&group);
	arrayadd(&iarray, &idx);
	arrayadd(&iarray, &idy);
	arrayadd(&iarray, &idu);
	arrayadd(&iarray, &idr);
      } else if ((graphtype == 2) && findX && findY) {
	arrayinit(&group, sizeof(int));
	a = 3;
	arrayadd(&group, &a);
	arrayadd(&group, &idx);
	arrayadd(&group, &idy);
	argv[0] = (char *) &group;
	argv[1] = NULL;
	exeobj(obj, "grouping", id, 1, argv);
	arraydel(&group);
	arrayadd(&iarray, &idx);
	arrayadd(&iarray, &idy);
      }
    }
  }
  arraydel(&iarray);
}

static void
field_obj_del(struct objlist *obj, int id, const char *field)
{
  char *fit;
  struct objlist *fitobj;
  struct narray iarray;

  if ((getobj(obj, field, id, 0, NULL, &fit) >= 0) && (fit != NULL)) {
    arrayinit(&iarray, sizeof(int));
    if (getobjilist(fit, &fitobj, &iarray, FALSE, NULL) == 0) {
      int idnum, i;
      idnum = arraynum(&iarray);
      arraysort_int(&iarray);
      for (i = idnum - 1; i >= 0; i--) {
        int fitid;
	fitid = arraynget_int(&iarray, i);
	delobj(fitobj, fitid);
      }
    }
    arraydel(&iarray);
  }
}

void
FitDel(struct objlist *obj, int id)
{
  field_obj_del(obj, id, "fit");
}

void
ArrayDel(struct objlist *obj, int id)
{
  field_obj_del(obj, id, "array");
}

void
FitCopy(struct objlist *obj, int did, int sid)
{
  char *fit;
  struct objlist *fitobj;
  struct narray iarray;
  struct narray iarray2;
  int fitoid;

  if ((getobj(obj, "fit", sid, 0, NULL, &fit) >= 0) && (fit != NULL)) {
    arrayinit(&iarray, sizeof(int));
    if (getobjilist(fit, &fitobj, &iarray, FALSE, NULL) == 0) {
      int idnum;
      idnum = arraynum(&iarray);
      if (idnum >= 1) {
        int fitid, id2;
	fitid = arraylast_int(&iarray);
	if ((getobj(obj, "fit", did, 0, NULL, &fit) >= 0)
	    && (fit != NULL)) {
	  arrayinit(&iarray2, sizeof(int));
	  if (getobjilist(fit, &fitobj, &iarray2, FALSE, NULL) == 0) {
            int idnum2;
	    idnum2 = arraynum(&iarray2);
	    if (idnum2 >= 1) {
	      id2 = arraylast_int(&iarray2);
	    } else
	      id2 = newobj(fitobj);
	  } else
	    id2 = newobj(fitobj);
	  arraydel(&iarray2);
	} else {
	  id2 = newobj(fitobj);
        }
	if (id2 >= 0) {
          char *field[] = {"name", "equation", NULL};
          N_VALUE *inst;
	  copy_obj_field(fitobj, id2, fitid, field);
	  inst = getobjinst(fitobj, id2);
	  _getobj(fitobj, "oid", inst, &fitoid);
	  if ((fit = mkobjlist(fitobj, NULL, fitoid, NULL, TRUE)) != NULL) {
	    if (putobj(obj, "fit", did, fit) == -1)
	      g_free(fit);
	  }
	}
      }
    }
    arraydel(&iarray);
  }
}

void
FitClear(void)
{
  struct objlist *obj, *fitobj;
  int i, anum, id, hidden;
  char *fit;
  struct narray iarray;

  if (Menulock || Globallock)
    return;
  if ((obj = chkobject("data")) == NULL)
    return;
  if ((fitobj = chkobject("fit")) == NULL)
    return;
  for (i = 0; i <= chkobjlastinst(obj); i++) {
    getobj(obj, "fit", i, 0, NULL, &fit);
    if (fit == NULL) {
      continue;
    }
    arrayinit(&iarray, sizeof(int));
    if (getobjilist(fit, &fitobj, &iarray, FALSE, NULL) == 0) {
      anum = arraynum(&iarray);
      if (anum >= 1) {
	id = arraylast_int(&iarray);
	getobj(obj, "hidden", i, 0, NULL, &hidden);
	if (! hidden) {
	  putobj(fitobj, "equation", id, NULL);
	}
      }
      arraydel(&iarray);
    }
  }
}

static void
del_darray(struct objlist *data_obj)
{
  char *array;
  int i, j, n, last;
  struct array_prm ary;
  struct narray *id_array;
  struct objlist *darray_obj;

  darray_obj = chkobject("darray");
  if (darray_obj == NULL) {
    return;
  }

  last = chkobjlastinst(data_obj);
  if (last < 0) {
    return;
  }

  id_array = arraynew(sizeof(int));
  if (id_array == NULL) {
    return;
  }

  for (i = 0; i <= last; i++) {
    getobj(data_obj, "array", i, 0, NULL, &array);
    if (array == NULL) {
      continue;
    }
    open_array(array, &ary);
    for (j = 0; j < ary.col_num; j++) {
      arrayadd(id_array, &ary.id[j]);
    }
  }

  arraysort_int(id_array);
  arrayuniq_int(id_array);

  n = arraynum(id_array);
  for (i = 0; i < n; i++) {
    int id;
    id = arraynget_int(id_array, n - 1 - i);
    delobj(darray_obj, id);
  }
  arrayfree(id_array);
}

static void
delete_instances(struct objlist *obj)
{
  int instnum, i;
  instnum = chkobjlastinst(obj);
  if (instnum == -1) {
    return;
  }
  for (i = instnum; i >= 0; i--) {
    delobj(obj, i);
  }
}

void
DeleteDrawable(void)
{
  struct objlist *fileobj, *drawobj, *prmobj;

  fileobj = chkobject("data");
  if (fileobj) {
    int i;
    for (i = 0; i <= chkobjlastinst(fileobj); i++) {
      FitDel(fileobj, i);
    }
    del_darray(fileobj);
  }

  drawobj = chkobject("draw");
  if (drawobj) {
    delchildobj(drawobj);
  }

  prmobj = chkobject("parameter");
  if (prmobj) {
    delete_instances(prmobj);
  }
  set_toolbox_mode(TOOLBOX_MODE_TOOLBAR);
  Menulocal.Decimalsign = get_gra_decimalsign_type(Menulocal.default_decimalsign);
  gra_set_default_decimalsign(Menulocal.Decimalsign);
}

static void
store_file(struct objlist *ocur, int hFile, int i)
{
  char *s, *fname, *fname2;
  int j;
  N_VALUE *inst;

  getobj(ocur, "file", i, 0, NULL, &fname);
  if (fname == NULL) {
    return;
  }

  for (j = i - 1; j >= 0; j--) {
    getobj(ocur, "file", j, 0, NULL, &fname2);
    if ((fname2 != NULL) && (strcmp0(fname, fname2) == 0)) {
      break;
    }
  }
  inst = chkobjinst(ocur, i);
  if (j == -1) {
    while (_exeobj(ocur, "store_data", inst, 0, NULL) == 0) {
      _getobj(ocur, "store_data", inst, &s);
      nwrite(hFile, s, strlen(s));
      nwrite(hFile, "\n", 1);
    }
  } else {
    while (_exeobj(ocur, "store_dummy", inst, 0, NULL) == 0) {
      _getobj(ocur, "store_dummy", inst, &s);
      nwrite(hFile, s, strlen(s));
      nwrite(hFile, "\n", 1);
    }
  }
}

static void
save_array(struct objlist *ocur, int hFile, int i, GString *str)
{
  char *s, *array, *array2, iarray_str[] = "iarray:!:push ${darray:!:oid}\n\n";
  int j, k, l, n, id;
  struct array_prm ary, ary2;
  struct narray *id_array;

  getobj(ocur, "array", i, 0, NULL, &array);
  if (array == NULL) {
    return;
  }

  id_array = arraynew(sizeof(int));
  for (j = 0; j < i; j++) {
    getobj(ocur, "array", j, 0, NULL, &array2);
    if (array2 == NULL) {
      continue;
    }
    open_array(array2, &ary2);
    for (k = 0; k < ary2.col_num; k++) {
      n = arraynum(id_array);
      for (l = 0; l < n; l++) {
	id = arraynget_int(id_array, l);
	if (id == ary2.id[k]) {
	  break;
	}
      }
      if (l == n) {
	arrayadd(id_array, &ary2.id[k]);
      }
    }
  }

  open_array(array, &ary);
  for (k = 0; k < ary.col_num; k++) {
    n = arraynum(id_array);
    for (l = 0; l < n; l++) {
      id = arraynget_int(id_array, l);
      if (id == ary2.id[k]) {
	break;
      }
    }
    if (l == n) {
      arrayadd(id_array, &ary.id[k]);
      if (getobj(ary.obj, "save", ary.id[k], 0, NULL, &s) != -1) {
	nwrite(hFile, s, strlen(s));
	nwrite(hFile, "\n", 1);
	nwrite(hFile, iarray_str, sizeof(iarray_str) - 1);
      }
    }
    g_string_append_printf(str, "%s^${iarray:!:get:%d}", (k == 0) ? "darray:\"" : ",", l);
  }
  arrayfree(id_array);
}

static void
save_merge(struct objlist *ocur, int hFile, int storemerge, int i)
{
  char *s;

  getobj(ocur, "save", i, 0, NULL, &s);
  nwrite(hFile, s, strlen(s));
  nwrite(hFile, "\n", 1);
  if (storemerge) {
    store_file(ocur, hFile, i);
  }
}

static void
save_data(struct objlist *ocur, int hFile, int storedata, int i, int *array_data)
{
  char *s;
  int source, argc;
  char *argv2[3], **argv;
  struct narray *array;
  GString *str;

  str = NULL;
  argc = 0;
  argv = NULL;
  array = NULL;
  getobj(ocur, "source", i, 0, NULL, &source);
  switch (source) {
  case DATA_SOURCE_FILE:
    break;
  case DATA_SOURCE_ARRAY:
    array = arraynew(sizeof(char *));
    if (array == NULL) {
      error(NULL, ERRHEAP);
      return;
    }
    s = "array";
    arrayadd(array, &s);
    argv2[0] = (char *) array;
    argv2[1] = NULL;

    argv = argv2;
    argc = 1;

    str = g_string_new("\tdata::array=");
    if (str == NULL) {
      error(NULL, ERRHEAP);
      return;
    }
    if (! *array_data) {
      char iarray_str[] = "new iarray\n\n";
      *array_data = TRUE;
      nwrite(hFile, iarray_str, sizeof(iarray_str) - 1);
    }
    save_array(ocur, hFile, i, str);
    break;
  case DATA_SOURCE_RANGE:
    break;
  }

  getobj(ocur, "save", i, argc, argv, &s);
  nwrite(hFile, s, strlen(s));
  if (storedata) {
    store_file(ocur, hFile, i);
  }

  if (array) {
    arrayfree(array);
  }

  if (str) {
    nwrite(hFile, str->str, str->len);
    nwrite(hFile, "\"\n", 2);
    g_string_free(str, TRUE);
  }
  nwrite(hFile, "\n", 1);
}

static void
save_inst(int hFile, struct objlist *ocur)
{
  int i, instnum;
  char *s;
  instnum = chkobjlastinst(ocur);
  if (instnum == -1) {
    return;
  }
  for (i = 0; i <= instnum; i++) {
    getobj(ocur, "save", i, 0, NULL, &s);
    nwrite(hFile, s, strlen(s));
    nwrite(hFile, "\n", 1);
  }
}

static void
SaveParent(int hFile, const struct objlist *parent, int storedata,
	   int storemerge)
{
  struct objlist *ocur, *odata, *omerge;
  int i, instnum, array_data;
  char *s;

  ocur = chkobjroot();
  odata = chkobject("data");
  omerge = chkobject("merge");
  array_data = FALSE;
  while (ocur) {
    if (chkobjparent(ocur) == parent) {
      if ((instnum = chkobjlastinst(ocur)) != -1) {
	for (i = 0; i <= instnum; i++) {
	  if (ocur == odata) {
	    save_data(ocur, hFile, storedata, i, &array_data);
	  } else if (ocur == omerge) {
	    save_merge(ocur, hFile, storemerge, i);
	  } else {
	    getobj(ocur, "save", i, 0, NULL, &s);
	    nwrite(hFile, s, strlen(s));
	    nwrite(hFile, "\n", 1);
	  }
	}
	if (ocur == odata && array_data) {
	  char iarray_str[] = "del iarray:!\n\n";
	  nwrite(hFile, iarray_str, sizeof(iarray_str) -1);
	}
      }
      SaveParent(hFile, ocur, storedata, storemerge);
    }
    ocur = ocur->next;
  }
}

int
SaveDrawrable(const char *name, int storedata, int storemerge, int save_decimalsign)
{
  int hFile;
  int error;
  struct narray sarray;
  char *ver, *sysname, *s, *opt;

  error = FALSE;

  hFile = nopen(name, O_CREAT | O_TRUNC | O_RDWR, NFMODE_NORMAL_FILE);

  if (hFile < 0) {
    error = TRUE;
  } else {
    int len;
    struct objlist *sysobj, *drawobj, *prmobj;
    N_VALUE *inst;
    char comment[COMMENT_BUF_SIZE];
    sysobj = chkobject("system");
    inst = chkobjinst(sysobj, 0);
    _getobj(sysobj, "name", inst, &sysname);
    _getobj(sysobj, "version", inst, &ver);

    len = snprintf(comment, sizeof(comment),
		   "#!ngraph\n#%%creator: %s \n#%%version: %s\n",
		   sysname, ver);
    if (nwrite(hFile, comment, len) != len)
      error = TRUE;

    prmobj = chkobject("parameter");
    if (prmobj) {
      save_inst(hFile, prmobj);
    }
    if ((drawobj = chkobject("draw")) != NULL) {
      struct objlist *graobj;
      SaveParent(hFile, drawobj, storedata, storemerge);
      if ((graobj = chkobject("gra")) != NULL) {
        int id;
	id = ValidGRA();
	if (id != -1) {
          char *arg[2];
	  arrayinit(&sarray, sizeof(char *));
	  opt = "device";
	  arrayadd(&sarray, &opt);
	  if (! save_decimalsign) {
	    opt = "decimalsign";
	    arrayadd(&sarray, &opt);
	  }
	  arg[0] = (char *) &sarray;
	  arg[1] = NULL;
	  getobj(graobj, "save", id, 1, arg, &s);
	  arraydel(&sarray);
	  len = strlen(s);
	  if (nwrite(hFile, s, len) != len)
	    error = TRUE;
	} else {
	  error = TRUE;
	}
      }
    }
    nclose(hFile);
  }

  if (error)
    ErrorMessage();

  return error;
}

static void
get_save_opt_response(struct response_callback *cb)
{
  struct objlist *fobj, *mobj;
  int fnum, mnum, path;
  int i;
  struct GraphSave_data *save_data;

  save_data = (struct GraphSave_data *) cb->data;

  if (cb->return_value != IDOK) {
    GraphSave_response(cb->return_value, save_data);
    return;
  }

  fobj = chkobject("data");
  mobj = chkobject("merge");

  fnum = chkobjlastinst(fobj) + 1;
  mnum = chkobjlastinst(mobj) + 1;

  path = DlgSave.Path;
  for (i = 0; i < fnum; i++) {
    putobj(fobj, "save_path", i, &path);
  }

  for (i = 0; i < mnum; i++) {
    putobj(mobj, "save_path", i, &path);
  }
  save_data->path = path;
  save_data->storedata = DlgSave.SaveData;
  save_data->storemerge = DlgSave.SaveMerge;
  GraphSave_response(cb->return_value, save_data);
}

static void
get_save_opt(struct GraphSave_data *save_data)
{
  int fnum, mnum, i, src;
  struct objlist *fobj, *mobj;

  fobj = chkobject("data");
  mobj = chkobject("merge");

  fnum = chkobjlastinst(fobj) + 1;
  mnum = chkobjlastinst(mobj) + 1;

  /* there are no instances of data and merge objects */
  if (fnum < 1 && mnum < 1) {
    GraphSave_response(IDOK, save_data);
    return;
  }

  /* check source field of data objects */
  for (i = 0; i < fnum; i++) {
    getobj(fobj, "source", i, 0, NULL, &src);
    if (src ==  DATA_SOURCE_FILE) {
      break;
    }
  }
  if (fnum > 0 && mnum < 1 && i == fnum) {
    GraphSave_response(IDOK, save_data);
    return;
  }

  SaveDialog(&DlgSave);
  response_callback_add(&DlgSave, get_save_opt_response, NULL, save_data);
  DialogExecute(TopLevel, &DlgSave);
}

static void
GraphSave_free(struct GraphSave_data *data)
{
  g_free(data->file);
  g_free(data->current_wd);
  g_free(data->prev_wd);
  g_free(data);
}

static void
check_graph_save_data_callback (int ret, struct GraphSave_data *save_data)
{
  if (save_data == NULL) {
    return;
  }
  if (save_data->cb == NULL) {
    return;
  }
  save_data->cb (ret, save_data->data);
}

static void
GraphSave_response(int ret, struct GraphSave_data *d)
{
  if (ret == IDOK) {
    char mes[256];
    snprintf(mes, sizeof(mes), _("Saving `%.128s'."), d->file);
    SetStatusBar(mes);
    if(! SaveDrawrable(d->file, d->storedata, d->storemerge, TRUE)) {
      switch (d->path) {
      case SAVE_PATH_BASE:
	  ToBasename();
	  break;
      case SAVE_PATH_RELATIVE:
        ToRalativePath();
        break;
      case SAVE_PATH_FULL:
        ToFullPath();
        break;
      }
      changefilename(d->file);
      AddNgpFileList(d->file);
      SetFileName(d->file);
      reset_graph_modified();
    }
    ResetStatusBar();
  }

  if (d->current_wd && nchdir(d->current_wd)) {
    ErrorMessage();
  }
  check_graph_save_data_callback (IDOK, d);
  GraphSave_free(d);
}

static void
GraphSaveSub(struct GraphSave_data *data)
{
  char *prev_wd;
  char *file;
  file = data->file;
  if (file == NULL) {
    if (data->cb) {
      data->cb(IDCANCEL, data->data);
    }
    GraphSave_free(data);
    return;
  }
  prev_wd = data->prev_wd;
  if (prev_wd && nchdir(prev_wd)) {
    ErrorMessage();
  }
  get_save_opt(data);
}

static void
graph_save_response(char *file, gpointer user_data)
{
  struct GraphSave_data *data;
  char *prev_wd, *current_wd;
  data = (struct GraphSave_data *) user_data;
  prev_wd = data->prev_wd;
  current_wd = ngetcwd();
  if (prev_wd && current_wd && strcmp(prev_wd, current_wd) == 0) {
    g_free(prev_wd);
    g_free(current_wd);
    prev_wd = NULL;
    current_wd = NULL;
    data->prev_wd = NULL;
  }
  data->current_wd = current_wd;
  data->file = file;
  GraphSaveSub(data);
}

int
GraphSave(int overwrite, response_cb cb, gpointer user_data)
{
  struct GraphSave_data *data;
  const char *initfil;

  data = g_malloc0 (sizeof (*data));
  data->cb = cb;
  data->data = user_data;
  data->prev_wd = NULL;
  if (NgraphApp.FileName != NULL) {
    initfil = NgraphApp.FileName;
  } else {
    initfil = NULL;
    overwrite = FALSE;
  }
  if ((initfil == NULL) || (! overwrite || (naccess(initfil, 04) == -1))) {
    int chd;
    initfil = (initfil) ? initfil : "untitled.ngp";
    data->prev_wd = ngetcwd();
    chd = Menulocal.changedirectory;
    nGetSaveFileName(TopLevel, _("Save NGP file"), "ngp",
                     &(Menulocal.graphloaddir), initfil, chd,
                     graph_save_response, data);
  } else {
    data->file = g_strdup (initfil);
    GraphSaveSub(data);
  }
  return IDOK;
}

static void
change_filename(char * (*func)(const char *))
{
  int i;
  unsigned int j;
  char *file, *file2, *objname[] = {"data", "merge"};

  for (j = 0; j < sizeof(objname) / sizeof(*objname); j++) {
    struct objlist *obj;
    obj = chkobject(objname[j]);
    if (obj == NULL)
      continue;

    for (i = 0; i <= chkobjlastinst(obj); i++) {
      getobj(obj, "file", i, 0, NULL, &file);
      if (file == NULL)
	continue;

      file2 = func(file);
      if (file2 == NULL)
	return;

      if (strcmp(file, file2) == 0) {
	g_free(file2);
	continue;
      }

      set_graph_modified();
      putobj(obj, "file", i, file2);
    }
  }
}

static void
ToFullPath(void)
{
  change_filename(getfullpath);
}

static void
ToRalativePath(void)
{
  change_filename(getrelativepath);
}

static char *
get_basename(const char *file)
{
  char *ptr;

  ptr = getbasename(file);
  if (ptr == NULL)
    return NULL;

  return ptr;
}

static void
ToBasename(void)
{
  change_filename(get_basename);
}

struct load_dialog_data
{
  char *file, *cwd;
  int console;
  char *option;
};

static void
load_dialog_cb_free(struct load_dialog_data *data)
{
  g_free(data->file);
  g_free(data->option);
  g_free(data->cwd);
  g_free(data);
}

static void
draw_callback(gpointer user_data)
{
  (void) user_data;
  UpdateAll2(NULL, FALSE);
  menu_clear_undo();
}

struct LoadNgpFile_data {
  struct objlist *obj;
  N_VALUE *inst;
  char *argv[2];
  int r;
  GThread *thread;
  char *file, *name;
  int loadpath, allocnow, newid, console;
  struct narray sarray;
  struct load_dialog_data *d;
};

static void
LoadNgpFile_response_finalize(gpointer user_data)
{
  struct LoadNgpFile_data *data;

  data = (struct LoadNgpFile_data *) user_data;

  g_thread_join(data->thread);
  menu_lock(FALSE);

  if (data->r == 0) {
    struct objlist *aobj;
    int i;
    if ((aobj = getobject("axis")) != NULL) {
      for (i = 0; i <= chkobjlastinst(aobj); i++)
	exeobj(aobj, "tight", i, 0, NULL);
    }

    if ((aobj = getobject("axisgrid")) != NULL) {
      for (i = 0; i <= chkobjlastinst(aobj); i++)
	exeobj(aobj, "tight", i, 0, NULL);
    }

    SetFileName(data->file);
    AddNgpFileList(data->name);
    reset_graph_modified();

    switch (data->loadpath) {
    case LOAD_PATH_BASE:
      ToBasename();
      break;
    case LOAD_PATH_FULL:
      ToFullPath();
      break;
    }
    InfoWinClear();
  }

  AxisNameToGroup();
  ResetStatusBar();
  arraydel2(&data->sarray);

  if (data->console) {
    free_console(data->allocnow);
  }

  set_axis_undo_button_sensitivity(FALSE);
  GetPageSettingsFromGRA();
  Draw(FALSE, draw_callback, NULL);
  delobj(data->obj, data->newid);
  load_dialog_cb_free(data->d);
  g_free(data);
}

static gpointer
LoadNgpFile_response_main(gpointer user_data)
{
  struct LoadNgpFile_data *data;
  data = (struct LoadNgpFile_data *) user_data;
  data->r = _exeobj(data->obj, "shell", data->inst, 1, data->argv);
  data->thread = g_thread_self();
  g_idle_add_once(LoadNgpFile_response_finalize, data);
  return NULL;
}

static void
LoadNgpFile_response(struct response_callback *cb)
{
  char *file;
  char *option;
  int console;
  struct load_dialog_data *d;
  struct objlist *sys;
  char *expanddir;
  struct objlist *obj;
  char *name;
  int newid, allocnow = FALSE, tmp;
  char *s;
  int len;
  char mes[256];
  N_VALUE *inst;
  int loadpath, expand;
  struct LoadNgpFile_data *data;

  d = (struct load_dialog_data *) cb->data;

  file = d->file;
  console = d->console;
  option = d->option;

  data = g_malloc0(sizeof(*data));
  if (data == NULL) {
    goto ErrorExit;
  }

  if (DlgLoad.ret != IDOK) {
    goto ErrorExit;
  }
  changefilename(file);

  if (naccess(file, R_OK)) {
    ErrorMessage();
    goto ErrorExit;
  }

  sys = chkobject("system");
  if (sys == NULL) {
    goto ErrorExit;
  }

  loadpath = DlgLoad.loadpath;
  expand = DlgLoad.expand;
  expanddir = DlgLoad.exdir;
  DlgLoad.exdir = NULL;
  if (expanddir == NULL) {
    goto ErrorExit;
  }

  putobj(sys, "expand_dir", 0, expanddir);
  putobj(sys, "expand_file", 0, &expand);

  tmp = FALSE;
  putobj(sys, "ignore_path", 0, &tmp);

  obj = chkobject("shell");
  if (obj == NULL) {
    goto ErrorExit;
  }

  newid = newobj(obj);
  if (newid < 0) {
    goto ErrorExit;
  }

  inst = chkobjinst(obj, newid);
  arrayinit(&data->sarray, sizeof(char *));
  while ((s = getitok2(&option, &len, " \t")) != NULL) {
    if (arrayadd(&data->sarray, &s) == NULL) {
      g_free(s);
      arraydel2(&data->sarray);
      delobj(obj, newid);
      goto ErrorExit;
    }
  }

  name = g_strdup(file);

  if (name == NULL) {
    arraydel2(&data->sarray);
    delobj(obj, newid);
    goto ErrorExit;
  }

  if (arrayadd(&data->sarray, &name) == NULL) {
    g_free(name);
    arraydel2(&data->sarray);
    delobj(obj, newid);
    goto ErrorExit;
  }

  DeleteDrawable();

  if (console) {
    allocnow = allocate_console();
  }

  exeobj(obj, "set_security", newid, 0, NULL);

  data->argv[0] = (char *) &data->sarray;
  data->argv[1] = NULL;

  snprintf(mes, sizeof(mes), _("Loading `%.128s'."), name);
  SetStatusBar(mes);

  menu_lock(TRUE);

  data->obj = obj;
  data->inst = inst;
  data->file = file;
  data->name = name;
  data->loadpath = loadpath;
  data->allocnow = allocnow;
  data->console = console;
  data->newid = newid;
  data->d = d;
  g_thread_new(NULL, LoadNgpFile_response_main, data);
  return;

 ErrorExit:
  g_free(data);
  if (d->cwd) {
    nchdir(d->cwd);
  }
  load_dialog_cb_free(d);
}

void
LoadNgpFile(const char *file, int console, const char *option, const char *cwd)
{
  struct load_dialog_data *data;

  data = g_malloc0(sizeof(* data));
  if (data == NULL) {
    return;
  }
  data->file = g_strdup(file);
  data->console = console;
  data->option = g_strdup(option);
  data->cwd = g_strdup(cwd);

  response_callback_add(&DlgLoad, LoadNgpFile_response, NULL, data);
  LoadDialog(&DlgLoad, file);
  DialogExecute(TopLevel, &DlgLoad);
}

static int
check_ref_axis(char *ref, const char *group)
{
  int anum, aid;
  char *refgroup;
  struct narray iarray;
  struct objlist *aobj;
  N_VALUE *inst;

  if (ref == NULL) {
    return FALSE;
  }

  arrayinit(&iarray, sizeof(int));
  if (getobjilist(ref, &aobj, &iarray, FALSE, NULL)) {
    arraydel(&iarray);
    return TRUE;
  }

  anum = arraynum(&iarray);
  if (anum < 1) {
    arraydel(&iarray);
    return TRUE;
  }

  aid = arraylast_int(&iarray);
  arraydel(&iarray);
  inst = getobjinst(aobj, aid);
  if (inst == NULL) {
    return TRUE;
  }

  _getobj(aobj, "group", inst, &refgroup);
  if (refgroup && group &&
      refgroup[0] == group[0] &&
      strcmp(refgroup + 2, group + 2) == 0) {
    return FALSE;
  }

  return TRUE;
}

static int
file_auto_scale(char *file_obj, struct objlist *aobj, int anum)
{
  int i;
  double min, max, inc;
  char *group, *ref, *argv[2];

  argv[0] = (char *) file_obj;
  argv[1] = NULL;
  for (i = 0; i <= anum; i++) {
    int refother;
    getobj(aobj, "min", i, 0, NULL, &min);
    getobj(aobj, "max", i, 0, NULL, &max);
    getobj(aobj, "inc", i, 0, NULL, &inc);
    getobj(aobj, "group", i, 0, NULL, &group);
    getobj(aobj, "reference", i, 0, NULL, &ref);
    refother = check_ref_axis(ref, group);
    if (! refother && (min == max || inc == 0)) {
      exeobj(aobj, "auto_scale", i, 1, argv);
    }
  }
  return 0;
}

void
FileAutoScale(void)
{
  int anum;
  struct objlist *aobj;
  char *buf;
  struct objlist *fobj;
  int lastinst;
  int i, hidden;
  GString *str;

  if ((fobj = chkobject("data")) == NULL)
    return;

  lastinst = chkobjlastinst(fobj);
  aobj = chkobject("axis");
  anum = chkobjlastinst(aobj);

  if (lastinst < 0 || aobj == NULL || anum == 0)
    return;

  str = g_string_new("data:");
  if (str == NULL) {
    error(NULL, ERRHEAP);
    return;
  }

  for (i = 0; i <= lastinst; i++) {
    getobj(fobj, "hidden", i, 0, NULL, &hidden);
    if (! hidden) {
      g_string_append_printf(str, "%d,", i);
    }
  }

  i = str->len - 1;
  buf = g_string_free(str, FALSE);
  if (buf[i] != ',') {
    g_free(buf);
    return;
  }
  buf[i] = '\0';

  file_auto_scale(buf, aobj, anum);
  g_free(buf);
}

void
AdjustAxis(void)
{
  struct objlist *aobj;
  int i, anum;

  if ((aobj = chkobject("axis")) == NULL)
    return;
  anum = chkobjlastinst(aobj);
  for (i = 0; i <= anum; i++)
    exeobj(aobj, "adjust", i, 0, NULL);
}

struct CheckSave_data {
  response_cb cb;
  gpointer data;
};

static void
CheckSave_response_response (int ret, gpointer user_data)
{
  struct CheckSave_data *data;
  int r = TRUE;

  data = (struct CheckSave_data *) user_data;
  if (ret == IDCANCEL) {
    r = FALSE;
  }
  data->cb(r, data->data);
  g_free(data);
}

static void
CheckSave_response(int ret, gpointer user_data)
{
  struct CheckSave_data *data;
  int r = TRUE;

  data = (struct CheckSave_data *) user_data;
  if (ret == IDYES) {
    GraphSave(TRUE, CheckSave_response_response, user_data);
    return;
  } else if (ret != IDNO) {
    r = FALSE;
  }
  data->cb(r, data->data);
  g_free(data);
}

void
CheckSave(response_cb cb, gpointer user_data)
{
  struct CheckSave_data *data;
  data = g_malloc0(sizeof(*data));
  if (data == NULL) {
    return;
  }
  data->cb = cb;
  data->data = user_data;
  if (get_graph_modified()) {
    response_message_box(TopLevel,
			 _("This graph is modified.\nSave this graph?"),
			 _("Confirm"), RESPONS_YESNOCANCEL,
			 CheckSave_response, data);
  } else {
    CheckSave_response(IDNO, data);
  }
}

static void
add_hist(const char *file, char *mime)
{
  char *full_name, *uri;
  GtkRecentData recent_data = {
    NULL,
    AppName,
    NULL,
    AppName,
    "ngraph %f",
    NULL,
    FALSE,
  };

  if (! g_utf8_validate(file, -1, NULL)) {
    return;
  }

  full_name = getfullpath(file);
  if (full_name == NULL) {
    return;
  }

  recent_data.mime_type = mime;

  uri = g_filename_to_uri(full_name, NULL, NULL);
  g_free(full_name);

  gtk_recent_manager_remove_item (NgraphApp.recent_manager, uri, NULL);
  gtk_recent_manager_add_full(NgraphApp.recent_manager, uri, &recent_data);
  g_free(uri);
}

static void
AddNgpFileList(const char *file)
{
  add_hist(file, NGRAPH_GRAPH_MIME);
}

void
AddDataFileList(const char *file)
{
  add_hist(file, NGRAPH_DATA_MIME);
}

void
SetFileName(char *str)
{
  char *ngp, *name;

  name = g_strdup(str);
  g_free(NgraphApp.FileName);
  if (name == NULL) {
    NgraphApp.FileName = NULL;
    ngp = NULL;
  } else {
    NgraphApp.FileName = getfullpath(name);
    ngp = getfullpath(name);
    g_free(name);
  }
  putobj(Menulocal.obj, "fullpath_ngp", 0, ngp);
}

int
allocate_console(void)
{
  int allocnow;

  loadstdio(&GtkIOSave);
  allocnow = nallocconsole();
  nforegroundconsole();
  return allocnow;
}

void
free_console(int allocnow)
{
  if (allocnow)
    nfreeconsole();

  putstderr = mgtkputstderr;
  printfstderr = mgtkprintfstderr;
  putstdout = mgtkputstdout;
  printfstdout = mgtkprintfstdout;
  inputyn = mgtkinputyn;
  ndisplaydialog = mgtkdisplaydialog;
  ndisplaystatus = mgtkdisplaystatus;
  ninterrupt = mgtkinterrupt;
}

static char *
get_plot_cb_str(struct objlist *obj, int id, int source)
{
  char *s;
  const char *str;

  str = get_plot_info_str(obj, id, source);
  if (str == NULL) {
    return g_strdup(FILL_STRING);
  }

  if (source == DATA_SOURCE_FILE) {
    char *valstr;
    valstr = getbasename(str);
    s = g_strdup_printf("%s", (valstr) ? valstr : FILL_STRING);
    if (valstr != NULL) {
      g_free(valstr);
    }
  } else {
    s = g_strdup(str);
  }

  return s;
}

char *
MergeFileCB(struct objlist *obj, int id)
{
  char *file, *bname;

  getobj(obj, "file", id, 0, NULL, &file);
  if (file == NULL) {
    return g_strdup(FILL_STRING);
  }
  bname = getbasename(file);
  if (bname == NULL) {
    return g_strdup(FILL_STRING);
  }
  return bname;
}

char *
FileCB(struct objlist *obj, int id)
{
  int source;

  getobj(obj, "source", id, 0, NULL, &source);
  return get_plot_cb_str(obj, id, source);
}

char *
PlotFileCB(struct objlist *obj, int id)
{
  int source;

  getobj(obj, "source", id, 0, NULL, &source);
  if (source != DATA_SOURCE_FILE) {
    return NULL;
  }
  return get_plot_cb_str(obj, id, source);
}

struct set_file_hidden_data {
  response_cb cb;
  gpointer user_data;
};

static void
set_file_hidden_response(struct response_callback *cb)
{
  struct SelectDialog *d;
  struct narray *farray, *ifarray;
  int a, r;

  d = (struct SelectDialog *) cb->dialog;
  farray = d->sel;
  ifarray = d->isel;
  r = 0;
  if (cb->return_value == IDOK) {
    int num, inum, *array, lastinst, undo, modified = FALSE, i;
    struct objlist *fobj;
    undo = menu_save_undo_single(UNDO_TYPE_EDIT, "data");
    fobj = chkobject("data");
    lastinst = chkobjlastinst(fobj);
    a = TRUE;
    for (i = 0; i <= lastinst; i++) {
      putobj(fobj, "hidden", i, &a);
    }
    num = arraynum(farray);
    arraysort_int(farray);
    array = arraydata(farray);
    a = FALSE;
    for (i = 0; i < num; i++) {
      putobj(fobj, "hidden", array[i], &a);
    }

    inum = arraynum(ifarray);
    if (inum != num) {
      modified = TRUE;
    } else {
      for (i = 0; i < num; i++) {
	if (arraynget_int(ifarray, i) != array[i]) {
	  modified = TRUE;
	  break;
	}
      }
    }
    if (modified) {
      set_graph_modified();
    } else {
      menu_undo_internal(undo);
    }
    r = 1;
  }

  arrayfree(ifarray);
  arrayfree(farray);

  if (cb->data) {
    struct set_file_hidden_data *data;
    data = (struct set_file_hidden_data *) cb->data;
    if (data->cb) {
      data->cb(r, data->user_data);
    }
    g_free(data);
  }
}

void
SetFileHidden(response_cb cb, gpointer user_data)
{
  struct objlist *fobj;
  int lastinst;
  struct narray *farray, *ifarray;
  int i, a;
  struct set_file_hidden_data *data;

  fobj = chkobject("data");
  if (fobj == NULL) {
    if (cb) {
      cb(1, user_data);
    }
    return;
  }

  lastinst = chkobjlastinst(fobj);
  if (lastinst < 0) {
    if (cb) {
      cb(1, user_data);
    }
    return;
  }

  data = g_malloc0(sizeof(*data));
  if (data == NULL) {
    if (cb) {
      cb(1, user_data);
    }
    return;
  }
  data->cb = cb;
  data->user_data = user_data;

  ifarray = arraynew(sizeof(int));
  if (ifarray == NULL) {
    if (cb) {
      cb(1, user_data);
    }
    g_free(data);
    return;
  }
  farray = arraynew(sizeof(int));
  if (farray == NULL) {
    if (cb) {
      cb(1, user_data);
    }
    g_free(data);
    arrayfree(ifarray);
    return;
  }
  for (i = 0; i <= lastinst; i++) {
    getobj(fobj, "hidden", i, 0, NULL, &a);
    if (!a) {
      arrayadd(ifarray, &i);
    }
  }

  SelectDialog(&DlgSelect, fobj, NULL, FileCB, farray, ifarray);
  response_callback_add(&DlgSelect, set_file_hidden_response, NULL, data);
  DialogExecute(TopLevel, &DlgSelect);
}

struct CheckIniFile_data {
  struct objlist *obj;
  int id, modified;
  obj_response_cb cb;
};

static void
CheckIniFile_response(int ret, gpointer user_data)
{
  struct objlist *obj;
  int id, modified;
  obj_response_cb cb;
  struct CheckIniFile_data *data;

  data = (struct CheckIniFile_data *) user_data;
  id = data->id;
  obj = data->obj;
  cb = data->cb;
  modified = data->modified;
  g_free(data);

  if (ret != IDYES) {
    cb(FALSE, obj, id, modified);
    return;
  }
  if (!copyconfig()) {
    message_box(TopLevel, _("Ngraph.ini could not be copied."), "Ngraph.ini", RESPONS_ERROR);
    cb(FALSE, obj, id, modified);
    return;
  }
  cb(TRUE, obj, id, modified);
}

void
CheckIniFile(obj_response_cb cb, struct objlist *obj, int id, int modified)
{
  int ret;

  ret = writecheckconfig();
  if (ret == 0) {
    message_box(TopLevel, _("Ngraph.ini is not found."), "Ngraph.ini", RESPONS_ERROR);
    cb(FALSE, obj, id, modified);
    return;
  } else if ((ret == -1) || (ret == -3)) {
    message_box(TopLevel, _("Ngraph.ini is write protected."), "Ngraph.ini", RESPONS_ERROR);
    cb(FALSE, obj, id, modified);
    return;
  } else if ((ret == -2) || (ret == 2)) {
    struct CheckIniFile_data *data;
    struct objlist *sys;
    char *homedir, *buf;

    sys = getobject("system");
    if (sys == NULL) {
      cb(FALSE, obj, id, modified);
      return;
    }

    if (getobj(sys, "home_dir", 0, 0, NULL, &homedir) == -1) {
      cb(FALSE, obj, id, modified);
      return;
    }

    data = g_malloc0(sizeof(*data));
    if (data == NULL) {
      cb(FALSE, obj, id, modified);
      return;
    }
    data->cb = cb;
    data->obj = obj;
    data->id = id;
    data->modified = modified;

    buf = g_strdup_printf(_("Install `Ngraph.ini' to %s ?"), homedir);
    response_message_box(TopLevel, buf, "Ngraph.ini", RESPONS_YESNO, CheckIniFile_response, data);
    g_free(buf);
  } else {
    cb(TRUE, obj, id, modified);
  }
}

struct progress_dialog_data {
  GString *msg[2], *title;
  double fraction[2];
  guint tag;
  GThread *thread;
  progress_func update, finalize;
  int finish;
};
static struct progress_dialog_data * ProgressDialogData = NULL;

void
ProgressDialogSetTitle(const char *title)
{
  if (ProgressDialogData) {
    g_string_assign(ProgressDialogData->title, title);
  }
}

static void
show_progress(int pos, const char *msg, double fraction)
{
  int i;

  if (! ProgressDialog)
    return;

  if (! ProgressDialogData)
    return;

  i = (pos) ? 1 : 0;

  g_string_assign(ProgressDialogData->msg[i], msg);
  ProgressDialogData->fraction[i] = fraction;
}

static void
stop_btn_clicked(void)
{
  if (ProgressDialogData == NULL) {
    return;
  }
  if (ProgressDialogData->finish) {
    ProgressDialogFinalize();
    return;
  }
  set_interrupt();
}

static void
progress_dialog_finalize(gpointer user_data)
{
  g_thread_join(ProgressDialogData->thread);
  if (ProgressDialogData->finalize) {
    ProgressDialogData->finalize(user_data);
  }
  if (gtk_widget_get_visible(GTK_WIDGET(ProgressFrame))) {
    gtk_progress_bar_set_fraction(ProgressBar[0], 1);
    gtk_progress_bar_set_fraction(ProgressBar[1], 1);
    gtk_window_set_title(GTK_WINDOW(ProgressDialog), _("Drawing end"));
    gtk_button_set_label(GTK_BUTTON(ProgressButton), _("_OK"));
  } else {
    ProgressDialogFinalize();
  }
}

static gboolean
progress_dialog_update(gpointer user_data)
{
  int i;
  if (ProgressDialogData == NULL) {
    return FALSE;
  } else if (ProgressDialogData->finish) {
    progress_dialog_finalize(user_data);
    return FALSE;
  }
  for (i = 0; i < PROGRESSBAR_N; i++) {
    GtkProgressBar *bar;
    double fraction;
    const char *msg, *title;
    bar = ProgressBar[i];
    fraction = ProgressDialogData->fraction[i];
    msg = ProgressDialogData->msg[i]->str;
    title = ProgressDialogData->title->str;
    if (fraction < 0) {
      gtk_progress_bar_pulse(bar);
    } else {
      gtk_progress_bar_set_fraction(bar, fraction);
    }
    gtk_progress_bar_set_text(bar, msg);
    gtk_window_set_title(GTK_WINDOW(ProgressDialog), title);
  }
  return TRUE;
}

static void
progress_dialog_add_text_view(GtkWidget *vbox)
{
  GtkWidget *swin;
  swin = gtk_scrolled_window_new();
  gtk_widget_set_vexpand(swin, TRUE);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  ProgressText = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(ProgressText), FALSE);
  gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(swin), ProgressText);

  ProgressFrame = gtk_frame_new(_("Error messages"));
  gtk_frame_set_child(GTK_FRAME(ProgressFrame), swin);

  gtk_box_append(GTK_BOX(vbox), ProgressFrame);
}

static void
create_progress_dialog(const char *title)
{
  GtkWidget *btn, *vbox, *hbox;

  if (TopLevel == NULL)
    return;

  reset_interrupt();

  SaveCursor = NGetCursor();
  NSetCursor(GDK_WATCH);

  if (ProgressDialog) {
    GtkTextBuffer *buf;
    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ProgressText));
    gtk_text_buffer_set_text(buf, "", -1);
    gtk_widget_set_visible(ProgressDialog, FALSE);

    ProgressDialogSetTitle(title);
    show_progress(0, "", 0);
    show_progress(1, "", 0);
    set_progress_func(show_progress);
    return;
  }

  ProgressDialog = gtk_window_new();
  g_signal_connect(ProgressDialog, "close_request", G_CALLBACK(gtk_true), NULL);
  gtk_window_set_title(GTK_WINDOW(ProgressDialog), title);
  gtk_window_set_deletable(GTK_WINDOW(ProgressDialog), FALSE);

  gtk_window_set_transient_for(GTK_WINDOW(ProgressDialog), GTK_WINDOW(TopLevel));
  gtk_window_set_modal(GTK_WINDOW(ProgressDialog), TRUE);

  vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);

  ProgressBar[0] = GTK_PROGRESS_BAR(gtk_progress_bar_new());
  gtk_progress_bar_set_ellipsize(ProgressBar[0], PANGO_ELLIPSIZE_MIDDLE);
  gtk_progress_bar_set_show_text(ProgressBar[0], TRUE);
  gtk_box_append(GTK_BOX(vbox), GTK_WIDGET(ProgressBar[0]));

  ProgressBar[1] = GTK_PROGRESS_BAR(gtk_progress_bar_new());
  gtk_progress_bar_set_ellipsize(ProgressBar[1], PANGO_ELLIPSIZE_MIDDLE);
  gtk_progress_bar_set_show_text(ProgressBar[1], TRUE);
  gtk_box_append(GTK_BOX(vbox), GTK_WIDGET(ProgressBar[1]));

  progress_dialog_add_text_view(vbox);

  btn = gtk_button_new_with_mnemonic(_("_Stop"));
  g_signal_connect(btn, "clicked", G_CALLBACK(stop_btn_clicked), NULL);
  ProgressButton = btn;
#if USE_HEADER_BAR
  hbox = gtk_header_bar_new();
  gtk_header_bar_pack_end(GTK_HEADER_BAR(hbox), btn);
  gtk_window_set_titlebar(GTK_WINDOW(ProgressDialog), hbox);
#else
  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);

  gtk_box_append(GTK_BOX(hbox), btn);
  gtk_box_append(GTK_BOX(vbox), hbox);
#endif
  gtk_window_set_child(GTK_WINDOW(ProgressDialog), vbox);

  gtk_window_set_default_size(GTK_WINDOW(ProgressDialog), 400, -1);

  set_progress_func(show_progress);
}

static gpointer
ProgressDialog_thread(gpointer user_data)
{
  if (ProgressDialogData->update) {
    ProgressDialogData->update(user_data);
  }
  ProgressDialogData->finish = TRUE;
  return NULL;
}

static void
progress_dialog_set_text(gpointer user_data)
{
  GtkTextBuffer *buf;
  GtkTextIter iter;
  char *text;

  text = (char *) user_data;

  if (text == NULL) {
    return;
  }

  if (ProgressText == NULL) {
    g_free(text);
    return;
  }

  gtk_widget_set_visible(ProgressFrame, TRUE);

  buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ProgressText));
  gtk_text_buffer_get_end_iter(buf, &iter);
  gtk_text_buffer_insert(buf, &iter, text, -1);

  g_free(text);
}

int
ProgressDialogIsActive(void)
{
  if (ProgressDialogData == NULL) {
    return FALSE;
  }
  return ! ProgressDialogData->finish;
}

void
ProgressDialog_append_text(const char *text)
{
  char *str;
  str = g_strdup(text);
  g_idle_add_once(progress_dialog_set_text, str);
}

void
ProgressDialogCreate(const char *title, progress_func update, progress_func finalize, gpointer data)
{
  int i;
  if (ProgressDialogData) {
    finalize(data);
    return;
  }
  ProgressDialogData = g_malloc0(sizeof(*ProgressDialogData));
  if (ProgressDialogData == NULL) {
    return;
  }
  for (i = 0; i < PROGRESSBAR_N; i++) {
    ProgressDialogData->msg[i] = g_string_new("");
    ProgressDialogData->fraction[i] = 0;
  }
  ProgressDialogData->title = g_string_new("");
  ProgressDialogData->update = update;
  ProgressDialogData->finalize = finalize;
  ProgressDialogData->finish = FALSE;
  create_progress_dialog(title);
  gtk_window_present(GTK_WINDOW(ProgressDialog));
  gtk_widget_set_visible(ProgressFrame, FALSE);
  gtk_button_set_label(GTK_BUTTON(ProgressButton), _("_Stop"));
  ProgressDialogData->thread = g_thread_new(NULL, ProgressDialog_thread, data);

  ProgressDialogData->tag = g_timeout_add(100, progress_dialog_update, data);
}

static void
ProgressDialogFinalize(void)
{
  int i;
  if (TopLevel == NULL)
    return;

  gtk_widget_set_visible(ProgressDialog, FALSE);
  NSetCursor(SaveCursor);
  set_progress_func(NULL);

  for (i = 0; i < PROGRESSBAR_N; i++) {
    g_string_free(ProgressDialogData->msg[i], TRUE);
  }
  g_string_free(ProgressDialogData->title, TRUE);
  g_free(ProgressDialogData);
  ProgressDialogData = NULL;
}

void
ErrorMessage(void)
{
  const char *s;
  char *ptr;

  s = g_strerror(errno);
  ptr = g_strdup(s);
  message_box(NULL, CHK_STR(ptr), _("error"), RESPONS_ERROR);
  g_free(ptr);
}
