/* 
 * Proview   Open Source Process Control.
 * Copyright (C) 2005-2017 SSAB EMEA AB.
 *
 * This file is part of Proview.
 *
 * 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 Proview. If not, see <http://www.gnu.org/licenses/>
 *
 * Linking Proview statically or dynamically with other modules is
 * making a combined work based on Proview. Thus, the terms and 
 * conditions of the GNU General Public License cover the whole 
 * combination.
 *
 * In addition, as a special exception, the copyright holders of
 * Proview give you permission to, from the build function in the
 * Proview Configurator, combine Proview with modules generated by the
 * Proview PLC Editor to a PLC program, regardless of the license
 * terms of these modules. You may copy and distribute the resulting
 * combined work under the terms of your choice, provided that every 
 * copy of the combined work is accompanied by a complete copy of 
 * the source code of Proview (the version used to produce the 
 * combined work), being distributed under the terms of the GNU 
 * General Public License plus this exception.
 **/

#include "glow_std.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <float.h>

#include "rt_load.h"
#include "flow.h"
#include "flow_browctx.h"
#include "flow_browapi.h"
#include "glow.h"
#include "glow_curvectx.h"
#include "glow_growctx.h"
#include "glow_curveapi.h"
#include "glow_msg.h"
#include "rt_gdh.h"
#include "rt_gdh_msg.h"
#include "co_cdh.h"
#include "co_time.h"
#include "co_dcli.h"
#include "cow_wow.h"
#include "co_lng.h"
#include "co_cnf.h"

#include "glow_growctx.h"
#include "glow_growapi.h"
#include "ge_curve.h"
#include "ge_msg.h"

#define MARK_WIDTH 2.0

void GeCurve::activate_exit()
{
  if ( close_cb)
    (close_cb)( parent_ctx);
  else
    delete this;
}

void GeCurve::activate_configure()
{
  configure_curves();
  configure_axes();
}

void GeCurve::activate_new()
{
  if ( new_cb)
    (new_cb)( parent_ctx);
}

void GeCurve::activate_save()
{
  if ( save_cb)
    (save_cb)( parent_ctx);
}

void GeCurve::activate_open()
{
  if ( open_cb)
    (open_cb)( parent_ctx);
}

void GeCurve::activate_snapshot()
{
  if ( snapshot_cb)
    (snapshot_cb)( parent_ctx);
}

void GeCurve::activate_export()
{
  double ll_x, ll_y, ur_x, ur_y;
  pwr_tTime to = pwr_cNTime;
  pwr_tTime from = pwr_cNTime;
  double from_time, to_time;
  pwr_tFileName filename;

  // Get default file from proview.cnf
  if ( layout_mask & curve_mEnable_ExportTime) {
    if ( !cnf_get_value( "curveExportFile", filename, sizeof(filename)))
      strcpy( filename, "~/history_$date.txt");
  }
  else {
    if ( !cnf_get_value( "curveExportFile", filename, sizeof(filename)))
      strcpy( filename, "~/fast_$date.txt");
  }

  grow_MeasureWindow( growcurve_ctx, &ll_x, &ll_y, &ur_x, &ur_y);

  if ( !cd->x_reverse) {
    from_time = cd->x_min_value_axis[0] + ll_x *
      (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
    to_time = cd->x_min_value_axis[0] + ur_x *
      (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
  }
  else {
    from_time = cd->x_min_value_axis[0] + (200.0 - ll_x) *
      (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
    to_time = cd->x_min_value_axis[0] + (200.0 - ur_x) *
      (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
  }
  from.tv_sec = (pwr_tInt64) from_time;
  to.tv_sec = (pwr_tInt64) to_time;
  open_export( &from, &to, 1000, filename);
}

void GeCurve::activate_print()
{
  pwr_tFileName fname;
  pwr_tCmd cmd;

  dcli_translate_filename( fname, "$pwrp_tmp/curve.ps");
  print( fname);

  sprintf( cmd, "$pwr_exe/rt_print.sh %s 1", fname);
  system(cmd);
}

void GeCurve::activate_background()
{

  if ( curve_color == background_dark) {
    curve_color = background_bright;
    curve_border = border_bright;
  }
  else {
    curve_color = background_dark;
    curve_border = border_dark;
  }
  grow_SetObjectFillColor( curve_object, curve_color);
  grow_SetObjectBorderColor( curve_object, curve_border);
  cd->select_color( curve_color == background_dark);
  configure_curves();
}

void GeCurve::activate_filledcurves( int set)
{

  if ( set) {
    grow_SetTrendFillCurve( curve_object, 1);
    configure_curves();
  }
  else {
    grow_SetTrendFillCurve( curve_object, 0);
    configure_curves();
  }
  grow_NavRedraw( growcurve_ctx);
}

void GeCurve::activate_curvetype( int type)
{
  grow_SetCurveType( curve_object, (glow_eCurveType)type);
  configure_curves();
  grow_NavRedraw( growcurve_ctx);
}

void GeCurve::activate_help()
{
  if ( help_cb)
    (help_cb)( parent_ctx);
}

void GeCurve::activate_edit()
{
  set_times_sensitivity( 1);
}

void GeCurve::activate_period( time_ePeriod period)
{
  pwr_tTime from;
  pwr_tTime to;
  pwr_tTime center;
  double ll_x, ll_y, ur_x, ur_y;
  int low, high;

  if ( center_from_window) {
    // Get the center time
    measure_window( &ll_x, &ll_y, &ur_x, &ur_y);

    low = int( cd->x_min_value_axis[0] + 
	       ll_x / 200 * (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]));
    high = int( cd->x_min_value_axis[0] +
		ur_x / 200 * (cd->x_max_value_axis[0] - cd->x_min_value_axis[0]));

    center_from_window = 0;
  }
  else {
    // Get period from current time intervall
    pwr_tStatus sts;
    pwr_tTime t_low, t_high;

    sts = get_times( &t_low, &t_high);
    if ( EVEN(sts)) {
      // No previous valid time
      time_Period( period, &from, &to, 0, 1);
      set_times_sensitivity( 0);
      set_times( &from, &to);
      return;
    }

    high = t_high.tv_sec;
    low = t_low.tv_sec;
  }
  center.tv_sec = low + (high - low)/2;
  center.tv_nsec = 0;

  time_Period( period, &from, &to, &center, 1);

  set_times_sensitivity( 0);
  set_times( &from, &to);
}

void GeCurve::activate_period_markers()
{
  set_times_sensitivity( 0);
  set_times_markers();
}

void GeCurve::set_times_markers() 
{
  if ( time_Acomp( &last_mark1_time, &last_mark2_time) == -1)
    set_times( &last_mark1_time, &last_mark2_time);
  else
    set_times( &last_mark2_time, &last_mark1_time);
}

void GeCurve::update_times_markers()
{
  double x;
  double ll_x, ll_y, ur_x, ur_y;
  char str[40];
  double time;
  double values[CURVE_MAX_COLS];

  // Mark 1
  grow_MeasureNode( curve_markobject1, &ll_x, &ll_y, &ur_x, &ur_y);

  x = (ll_x + ur_x) / 2;
  last_mark1_x = x;
  x_to_points( x, &time, values);

  for ( int i = 0; i < cd->cols; i++) {
    sprintf( str, "%7.2f", values[i]);
    grow_SetAnnotation( mark1_annot[i], 0, str, strlen(str));
  }
      
  if ( !(strcmp( cd->x_format[0], "%10t") == 0 || 
	 strcmp( cd->x_format[0], "%11t") == 0)) {
    sprintf( str, "%7.2f", time);
    grow_SetAnnotation( mark1_annot[cd->cols], 0, str, strlen(str));
  }
  else {
    // Time is a date
    pwr_tTime t;
    time_Float64ToD( (pwr_tDeltaTime *) &t, time);
    last_mark1_time = t;
    time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
    grow_SetAnnotation( mark1_annot[cd->cols], 0, str, strlen(str));
  }

  // Mark 2
  grow_MeasureNode( curve_markobject2, &ll_x, &ll_y, &ur_x, &ur_y);

  x = (ll_x + ur_x) / 2;
  last_mark2_x = x;
  x_to_points( x, &time, values);

  for ( int i = 0; i < cd->cols; i++) {
    sprintf( str, "%7.2f", values[i]);
    grow_SetAnnotation( mark2_annot[i], 0, str, strlen(str));
  }
      
  if ( !(strcmp( cd->x_format[0], "%10t") == 0 || 
	 strcmp( cd->x_format[0], "%11t") == 0)) {
    sprintf( str, "%7.2f", time);
    grow_SetAnnotation( mark2_annot[cd->cols], 0, str, strlen(str));
  }
  else {
    // Time is a date
    pwr_tTime t;
    time_Float64ToD( (pwr_tDeltaTime *) &t, time);
    last_mark1_time = t;
    time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
    grow_SetAnnotation( mark2_annot[cd->cols], 0, str, strlen(str));
  }
  if ( current_period == ge_ePeriod_Markers)
    set_times_markers();
}

void GeCurve::activate_minmax_ok( double min_value, double max_value)
{

  int i = minmax_idx;
  if ( minmax_idx < cd->cols)
    cd->scale( cd->y_axis_type[i], cd->y_value_type[i], 
	       min_value,  max_value, 
	       &cd->y_min_value_axis[i], &cd->y_max_value_axis[i], 
	       &cd->y_trend_lines[i], &cd->y_axis_lines[i], &cd->y_axis_linelongq[i],
	       &cd->y_axis_valueq[i], cd->y_format[i], 
	       &cd->y_axis_width[i], 1, 1);
  else {
    double axis_width;
    i = 0;
    cd->scale( cd->x_axis_type[i], cd->x_value_type[i], 
	       min_value,  max_value, 
	       &cd->x_min_value_axis[i], &cd->x_max_value_axis[i], 
	       &cd->x_trend_lines[i], &cd->x_axis_lines[i], &cd->x_axis_linelongq[i],
	       &cd->x_axis_valueq[i], cd->x_format[i], 
	       &axis_width, 1, 1);
    if ( cd->type == curve_eDataType_MultiTrend) {
      for ( i = 1; i < cd->cols; i++) {
	cd->x_min_value_axis[i] = cd->x_min_value_axis[0];
	cd->x_max_value_axis[i] = cd->x_max_value_axis[0];
      }
    }
  }

  // cd->min_value_axis[minmax_idx] = min_value;
  // cd->max_value_axis[minmax_idx] = max_value;

  configure_curves();
  configure_axes();

}

void GeCurve::activate_minmax_save( double min_value, double max_value)
{
  FILE *fp;
  int i = minmax_idx;

  fp = fopen( minmax_filename(cd->y_name[i]), "w");
  if ( !fp)
    return;
  
  fprintf( fp, "%lf %lf\n", min_value, max_value);
  fclose( fp);
}

int GeCurve::get_saved_minmax( char *name, double *min_value, double *max_value)
{
  FILE *fp;

  fp = fopen( minmax_filename( name), "r");
  if ( !fp)
    return 0;
  
  fscanf( fp, "%lf %lf\n", min_value, max_value);
  fclose( fp);
  return 1;
}

char *GeCurve::minmax_filename( char *aname) 
{
  static pwr_tFileName fname;
  pwr_tAName attrname;

  strcpy( attrname, aname);
  cdh_ToLower( attrname, attrname);
  for ( char *s = attrname; *s; s++) {
    if ( *s == '-')
      *s = '_';
    if ( *s == '.')
      *s = '_';
  }	 
  strcpy( fname, "$HOME/pwr_curve_");
  strcat( fname, attrname);
  strcat( fname, ".dat");

  dcli_translate_filename( fname, fname);
  return fname;
}

//
// Callbacks from growcurve
//
int GeCurve::growcurve_cb( GlowCtx *ctx, glow_tEvent event)
{
  GeCurve	*curve;

  grow_GetCtxUserData( (GrowCtx *)ctx, (void **) &curve);

  switch ( event->event) {
  case glow_eEvent_MB1Click: {
    // Move mark slider to this position
    double ll_x, ll_y, ur_x, ur_y;
    char str[40];
    double time;
    double values[CURVE_MAX_COLS];

    if ( curve->selected_mark == 0 || curve->selected_mark == 1) {
      grow_MeasureNode( curve->curve_markobject1, &ll_x, &ll_y, &ur_x, &ur_y);
      grow_MoveNode( curve->curve_markobject1, event->any.x - (ur_x - ll_x)/2, ll_y);
      grow_Redraw( curve->growcurve_ctx);

      curve->last_mark1_x = event->any.x;
      curve->x_to_points( event->any.x, &time, values);

      for ( int i = 0; i < curve->cd->cols; i++) {
	sprintf( str, "%7.2f", values[i]);
	grow_SetAnnotation( curve->mark1_annot[i], 0, str, strlen(str));
      }
      
      if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	     strcmp( curve->cd->x_format[0], "%11t") == 0)) {
	sprintf( str, "%7.2f", time);
	grow_SetAnnotation( curve->mark1_annot[curve->cd->cols], 0, str, strlen(str));
      }
      else {
	// Time is a date
	pwr_tTime t;
	time_Float64ToD( (pwr_tDeltaTime *) &t, time);
	curve->last_mark1_time = t;
	time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
	grow_SetAnnotation( curve->mark1_annot[curve->cd->cols], 0, str, strlen(str));
	if ( curve->current_period == ge_ePeriod_Markers)
	  curve->set_times_markers();
      }
    }
    else if ( curve->selected_mark == 2) {
      grow_MeasureNode( curve->curve_markobject2, &ll_x, &ll_y, &ur_x, &ur_y);
      grow_MoveNode( curve->curve_markobject2, event->any.x - (ur_x - ll_x)/2, ll_y);
      grow_Redraw( curve->growcurve_ctx);

      curve->last_mark2_x = event->any.x;
      curve->x_to_points( event->any.x, &time, values);

      for ( int i = 0; i < curve->cd->cols; i++) {
	sprintf( str, "%7.2f", values[i]);
	grow_SetAnnotation( curve->mark2_annot[i], 0, str, strlen(str));
      }
      
      if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	     strcmp( curve->cd->x_format[0], "%11t") == 0)) {
	sprintf( str, "%7.2f", time);
	grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
      }
      else {
	// Time is a date
	pwr_tTime t;
	time_Float64ToD( (pwr_tDeltaTime *) &t, time);
	curve->last_mark2_time = t;
	time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
	grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
	if ( curve->current_period == ge_ePeriod_Markers)
	  curve->set_times_markers();
      }
    }
    break;
  }
  case glow_eEvent_MB1ClickCtrl: {
    // Move mark slider to this position
    double ll_x, ll_y, ur_x, ur_y;
    char str[40];
    double time;
    double values[CURVE_MAX_COLS];

    grow_MeasureNode( curve->curve_markobject2, &ll_x, &ll_y, &ur_x, &ur_y);
    grow_MoveNode( curve->curve_markobject2, event->any.x - (ur_x - ll_x)/2, ll_y);
    grow_Redraw( curve->growcurve_ctx);

    curve->last_mark2_x = event->any.x;
    curve->x_to_points( event->any.x, &time, values);

    for ( int i = 0; i < curve->cd->cols; i++) {
      sprintf( str, "%7.2f", values[i]);
      grow_SetAnnotation( curve->mark2_annot[i], 0, str, strlen(str));
    }
      
    if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	   strcmp( curve->cd->x_format[0], "%11t") == 0)) {
      sprintf( str, "%7.2f", time);
      grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
    }
    else {
      // Time is a date
      pwr_tTime t;
      time_Float64ToD( (pwr_tDeltaTime *) &t, time);
      curve->last_mark2_time = t;
      time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
      grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
      if ( curve->current_period == ge_ePeriod_Markers)
	curve->set_times_markers();
    }
    break;
  }
  case glow_eEvent_SliderMoveStart: {
    if ( event->object.object_type == glow_eObjectType_NoObject)
      grow_SetMoveRestrictions( (GrowCtx *)ctx, glow_eMoveRestriction_Disable, 0, 0, NULL);
    else {
      if ( event->object.object == curve->curve_markobject1)
	grow_SetMoveRestrictions( (GrowCtx *)ctx, glow_eMoveRestriction_HorizontalSlider, 
				  200 - MARK_WIDTH/2, -MARK_WIDTH/2, 
				  curve->curve_markobject1);
      else if ( event->object.object == curve->curve_markobject2)
	grow_SetMoveRestrictions( (GrowCtx *)ctx, glow_eMoveRestriction_HorizontalSlider, 
				  200 - MARK_WIDTH/2, -MARK_WIDTH/2, 
				  curve->curve_markobject2);
    }
    break;
  }
  case glow_eEvent_SliderMoved: {
    char str[40];
    double ll_x, ll_y, ur_x, ur_y;
    double time;
    double values[CURVE_MAX_COLS];

    if ( event->object.object == curve->curve_markobject1) {
      grow_MeasureNode( curve->curve_markobject1, &ll_x, &ll_y, &ur_x, &ur_y);
    
      curve->last_mark1_x = event->any.x;

      curve->x_to_points( event->any.x, &time, values);

      for ( int i = 0; i < curve->cd->cols; i++) {
	sprintf( str, "%7.2f", values[i]);
	grow_SetAnnotation( curve->mark1_annot[i], 0, str, strlen(str));
      }
      
      if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	     strcmp( curve->cd->x_format[0], "%11t") == 0)) {
	sprintf( str, "%7.2f", time);
	grow_SetAnnotation( curve->mark1_annot[curve->cd->cols], 0, str, strlen(str));
      }
      else {
	// Time is a date
	pwr_tTime t;
	time_Float64ToD( (pwr_tDeltaTime *) &t, time);
	curve->last_mark1_time = t;
	time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
	grow_SetAnnotation( curve->mark1_annot[curve->cd->cols], 0, str, strlen(str));
	if ( curve->current_period == ge_ePeriod_Markers)
	  curve->set_times_markers();
      }
    }
    else if ( event->object.object == curve->curve_markobject2) {
      grow_MeasureNode( curve->curve_markobject2, &ll_x, &ll_y, &ur_x, &ur_y);
    
      curve->last_mark2_x = event->any.x;

      curve->x_to_points( event->any.x, &time, values);

      for ( int i = 0; i < curve->cd->cols; i++) {
	sprintf( str, "%7.2f", values[i]);
	grow_SetAnnotation( curve->mark2_annot[i], 0, str, strlen(str));
      }
      
      if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	     strcmp( curve->cd->x_format[0], "%11t") == 0)) {
	sprintf( str, "%7.2f", time);
	grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
      }
      else {
	// Time is a date
	pwr_tTime t;
	time_Float64ToD( (pwr_tDeltaTime *) &t, time);
	curve->last_mark2_time = t;
	time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
	grow_SetAnnotation( curve->mark2_annot[curve->cd->cols], 0, str, strlen(str));
	if ( curve->current_period == ge_ePeriod_Markers)
	  curve->set_times_markers();
      }
    }
    break;
  }
  case glow_eEvent_CursorMotion: {
    char str[40];
    double time;
    double values[CURVE_MAX_COLS];

    if ( curve->cd->cols == 0)
      return 1;

    curve->last_cursor_x = event->any.x;
    curve->x_to_points( event->any.x, &time, values);

    for ( int i = 0; i < curve->cd->cols; i++) {
      sprintf( str, "%7.2f", values[i]);
      grow_SetAnnotation( curve->cursor_annot[i], 0, str, strlen(str));
    }
      
    if ( !(strcmp( curve->cd->x_format[0], "%10t") == 0 || 
	   strcmp( curve->cd->x_format[0], "%11t") == 0)) {
      sprintf( str, "%7.2f", time);
      grow_SetAnnotation( curve->cursor_annot[curve->cd->cols], 0, str, strlen(str));
    }
    else {
      // Time is a date
      pwr_tTime t;
      time_Float64ToD( (pwr_tDeltaTime *) &t, time);
      time_AtoAscii( &t, time_eFormat_DateAndTime, str, sizeof(str));
      grow_SetAnnotation( curve->cursor_annot[curve->cd->cols], 0, str, strlen(str));
    }
    break;
  }
  case glow_eEvent_Key_Left:
    curve_Scroll( curve->growcurve_ctx, 0.1);
    break;
  case glow_eEvent_Key_Right:
    curve_Scroll( curve->growcurve_ctx, -0.1);
    break;
  case glow_eEvent_Key_Up:
    curve_Zoom( curve->growcurve_ctx, 1.5);
    break;
  case glow_eEvent_Key_Down:
    curve_Zoom( curve->growcurve_ctx, 0.75);
    break;
  case glow_eEvent_ScrollUp:
    curve_Scroll( curve->growcurve_ctx, 0.05);
    break;
  case glow_eEvent_ScrollDown:
    curve_Scroll( curve->growcurve_ctx, -0.05);
    break;
  default:
    ;
  }
  return 1;
}

int GeCurve::init_growcurve_cb( GlowCtx *fctx, void *client_data)
{
  grow_sAttributes grow_attr;
  unsigned long mask;
  char path[2][80] = {"$pwrp_exe/", "$pwr_exe/"};

  GeCurve *curve = (GeCurve *) client_data;
  curve->growcurve_ctx = (CurveCtx *) fctx;

  mask = 0;
  mask |= grow_eAttr_grid_on;
  grow_attr.grid_on = 0;
  mask |= grow_eAttr_double_buffer_on;
  grow_attr.default_hot_mode = glow_eHotMode_TraceAction;
  mask |= grow_eAttr_default_hot_mode;
  grow_attr.double_buffer_on = 1;
  if ( curve->initial_right_position) { 
    mask |= grow_eAttr_initial_position;
    grow_attr.initial_position = glow_eDirection_Right;
  }
  mask |= grow_eAttr_color_theme;
  strcpy( grow_attr.color_theme, "$default");
  grow_SetAttributes( curve->growcurve_ctx, &grow_attr, mask); 

  grow_SetCtxUserData( curve->growcurve_ctx, curve);

  grow_SetMoveRestrictions( curve->growcurve_ctx, glow_eMoveRestriction_Disable, 0, 0, NULL);

  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_MB1Click,
  	glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_MB1ClickCtrl,
  	glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_CursorMotion, 
	glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_SliderMoved, 
	glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_SliderMoveStart, 
	glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_MB1Press, 
        glow_eEventType_MoveNode, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_Key_Left, 
        glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_Key_Right, 
        glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_Key_Up, 
        glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_Key_Down, 
        glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_ScrollUp, 
        glow_eEventType_CallBack, growcurve_cb);
  grow_EnableEvent( (GrowCtx *)curve->growcurve_ctx, glow_eEvent_ScrollDown, 
        glow_eEventType_CallBack, growcurve_cb);

  grow_SetPath( curve->growcurve_ctx, 2, (char *)path);
  grow_ReadCustomColorFile( curve->growcurve_ctx, 0);

  grow_SetBackgroundColor( curve->growcurve_ctx, glow_eCtColor_Background);

  grow_CreateGrowCurve( curve->growcurve_ctx, "curve", NULL, 0, 0, 200, 30,
			curve->curve_border, 2, glow_mDisplayLevel_1, 1, 1,
                        curve->curve_color, curve, &curve->curve_object);
  grow_SetObjectOriginalFillColor( curve->curve_object, glow_eCtColor_DiagramFillcolor);
  grow_SetObjectOriginalBorderColor( curve->curve_object, glow_eCtColor_DiagramBordercolor);

  grow_CreateGrowAxis( curve->growcurve_ctx, "y_axis", 0, 30, 200, 31.85,
		       glow_eDrawType_Line, 1, 5, 
		       glow_eDrawType_TextHelvetica, curve, 
                       &curve->curve_axisobject);
  grow_SetObjectOriginalBorderColor( curve->curve_axisobject, glow_eCtColor_AxisBordercolor);
  grow_SetObjectOriginalTextColor( curve->curve_axisobject, glow_eCtColor_BackgroundTextAndLines);

  grow_tNodeClass nc;
  grow_CreateNodeClass( curve->growcurve_ctx, "MarkNc", glow_eNodeGroup_Common, &nc);
  grow_AddLine( nc, "", 0, 0, 0, 32, glow_eDrawType_Color34, 1, 0, NULL);
  grow_AddRect( nc, "", -MARK_WIDTH/2, 30.1, MARK_WIDTH, 1.8, glow_eDrawType_LineGray, 1, 0,
		glow_mDisplayLevel_1, 0, 0, 1,
		glow_eDrawType_Color33, NULL);
  glow_sPoint p1[3] = { {-0.1, 30.3}, {-MARK_WIDTH/2+0.1, 31},{-0.1, 31.85}};
  grow_AddPolyLine( nc, "", p1, 3, glow_eDrawType_Line, 1, 0, 1, 0, 1, glow_eDrawType_Color38,
		    1, 0);
  glow_sPoint p2[3] = { {0.1, 30.3}, { MARK_WIDTH/2-0.1, 31},{ 0.1, 31.7}};
  grow_AddPolyLine( nc, "", p2, 3, glow_eDrawType_Line, 1, 0, 1, 0, 1, glow_eDrawType_Color38,
		    1, 0);
  grow_CreateGrowSlider( curve->growcurve_ctx, "", nc, 1, 0, NULL, &curve->curve_markobject2);
  grow_SetSliderInfo( curve->curve_markobject2, glow_eDirection_Right,
	200, 0, 200, 0);
  grow_CreateGrowSlider( curve->growcurve_ctx, "", nc, 1, 0, NULL, &curve->curve_markobject1);
  grow_SetSliderInfo( curve->curve_markobject1, glow_eDirection_Right,
	200, 0, 200, 0);
  // grow_SetMode( curve->growcurve_ctx, grow_eMode_Edit);

  curve->configure_curves();
  if ( curve->deferred_configure_axes)
    curve->configure_axes();
  return 1;
}

//
// Callbacks from growaxis
//
int GeCurve::growaxis_cb( GlowCtx *ctx, glow_tEvent event)
{
  GeCurve	*curve;

  grow_GetCtxUserData( (GrowCtx *)ctx, (void **) &curve);

  switch ( event->event)
  {
    case glow_eEvent_MB1Click:
    {
      if ( event->object.object_type != glow_eObjectType_NoObject) {
        for ( int i = 0; i < curve->cd->cols; i++) {
          if ( event->object.object == curve->axis_object[i]) {
            grow_SetTrendLines( curve->curve_object,
	              curve->cd->x_trend_lines[0] - 2,
		      2 * curve->cd->y_trend_lines[i] - 3);
            grow_Redraw( curve->growcurve_ctx);
            grow_SetAxisConf( curve->axis_lineobject, 0, 10,
                  2 * curve->cd->y_trend_lines[i] - 1, 1, 10, 0, "%2.0f");
            grow_Redraw( curve->growaxis_ctx);
            break;
          }
        }
      }
      break;
    }
    case glow_eEvent_MB1DoubleClick:
    {
      if ( event->object.object_type != glow_eObjectType_NoObject) {
        for ( int i = 0; i < curve->cd->cols; i++) {
          if ( event->object.object == curve->axis_object[i]) {
	    curve->open_minmax( i);
            break;
          }
        }
      }
      break;
    }
    case glow_eEvent_Resized:
    {
      if ( curve->axis_displayed)
	curve->resize();
      break;
    }
    default:
      ;
  }
  return 1;
}

//
// Callbacks from grownames
//

int GeCurve::grownames_cb( GlowCtx *ctx, glow_tEvent event)
{
  GeCurve	*curve;

  grow_GetCtxUserData( (GrowCtx *)ctx, (void **) &curve);

  switch ( event->event)
  {
    case glow_eEvent_MB1Click:
    {
      glow_eDrawType color;


      if ( event->object.object_type != glow_eObjectType_NoObject) {
        for ( int i = 0; i < curve->cd->cols; i++) {
          if ( event->object.object == curve->hide_rect[i] ) {
            if ( curve->hide[i]) {
              // Check max number of curves
              int num = 0;
              for ( int j = 0; j < curve->cd->cols; j++) {
                if ( !curve->hide[j])
                  num++;
              }
              if ( num >= TREND_MAX_CURVES - 1) {
	        curve->wow->DisplayError( "Error",
			"         Max number of curves exceeded        ");
                break;
              }
            }
            curve->hide[i] = !curve->hide[i];
            if ( curve->hide[i])
              color = glow_eCtColor_Background;
            else
              color = glow_eCtColor_BackgroundTextAndLines;
            grow_SetObjectBorderColor( curve->hide_l1[i], color);
            grow_SetObjectBorderColor( curve->hide_l2[i], color);

            if ( curve->auto_refresh) {
              curve->configure_curves();
              curve->configure_axes();
            }
            break;
          }
	}
        for ( int i = 0; i < curve->cd->cols + 1; i++) {
          if ( event->object.object == curve->scale_rect[i] ) {
	    curve->open_minmax( i);
	    break;
	  }
        }
	if ( event->object.object == curve->mark1_rect ||
	     event->object.object == curve->mark1_text) {
	  if ( curve->selected_mark == 1) {
	    grow_SetObjectFillColor( curve->mark1_rect, glow_eCtColor_AreaDelimiter);
	    curve->selected_mark = 0;
	  }
	  else {
	    grow_SetObjectFillColor( curve->mark1_rect, glow_eCtColor_ButtonActiveFillcolor);
	    grow_SetObjectFillColor( curve->mark2_rect, glow_eCtColor_AreaDelimiter);
	    curve->selected_mark = 1;
	  }
	}
	if ( event->object.object == curve->mark2_rect ||
	     event->object.object == curve->mark2_text) {
	  if ( curve->selected_mark == 2) {
	    grow_SetObjectFillColor( curve->mark2_rect, glow_eCtColor_AreaDelimiter);
	    curve->selected_mark = 0;
	  }
	  else {
	    grow_SetObjectFillColor( curve->mark1_rect, glow_eCtColor_AreaDelimiter);
	    grow_SetObjectFillColor( curve->mark2_rect, glow_eCtColor_ButtonActiveFillcolor);
	    curve->selected_mark = 2;
	  }
	}
      }
      break;
    }
    case glow_eEvent_HotRequest:
      return 0;
    case glow_eEvent_Resized:
    {
      break;
    }
    default:
      ;
  }
  return 1;
}

int GeCurve::init_growaxis_cb( GlowCtx *fctx, void *client_data)
{
  grow_sAttributes grow_attr;
  unsigned long mask;

  GeCurve *curve = (GeCurve *) client_data;
  curve->growaxis_ctx = (GrowCtx *) fctx;

  mask = 0;
  // Double buffer is used to avoid pulldown menu at print
  mask |= grow_eAttr_double_buffer_on;
  grow_attr.double_buffer_on = 1;
  mask |= grow_eAttr_grid_on;
  grow_attr.grid_on = 0;
  mask |= grow_eAttr_hot_mode;
  grow_attr.hot_mode = glow_eHotMode_TraceAction;
  grow_SetAttributes( curve->growaxis_ctx, &grow_attr, mask); 

  grow_SetCtxUserData( curve->growaxis_ctx, curve);

  grow_EnableEvent( (GrowCtx *)curve->growaxis_ctx, glow_eEvent_MB1Click,
  	glow_eEventType_CallBack, growaxis_cb);
  grow_EnableEvent( (GrowCtx *)curve->growaxis_ctx, glow_eEvent_MB1DoubleClick,
  	glow_eEventType_CallBack, growaxis_cb);
  grow_EnableEvent( (GrowCtx *)curve->growaxis_ctx, glow_eEvent_Resized,
  	glow_eEventType_CallBack, growaxis_cb);

  if ( curve->growcurve_ctx)
    curve->configure_axes();
  else
    curve->deferred_configure_axes = 1;
  return 1;
}

int GeCurve::init_grownames_cb( GlowCtx *fctx, void *client_data)
{
  GeCurve *curve = (GeCurve *) client_data;
  curve->grownames_ctx = (GrowCtx *) fctx;
  grow_sAttributes grow_attr;
  unsigned long mask;

  if ( Lng::translatefile_coding() == lng_eCoding_UTF_8)
    grow_SetTextCoding( (GrowCtx *)curve->grownames_ctx, glow_eTextCoding_UTF_8);

  mask = 0;
  // Double buffer is used for print
  mask |= grow_eAttr_double_buffer_on;
  grow_attr.double_buffer_on = 1;
  mask |= grow_eAttr_grid_on;
  grow_attr.grid_on = 0;
  mask |= grow_eAttr_hot_mode;
  grow_attr.hot_mode = glow_eHotMode_TraceAction;
  mask |= grow_eAttr_color_theme;
  strcpy( grow_attr.color_theme, "$default");
  grow_SetAttributes( curve->grownames_ctx, &grow_attr, mask); 

  grow_SetCtxUserData( curve->grownames_ctx, curve);

  grow_EnableEvent( (GrowCtx *)curve->grownames_ctx, glow_eEvent_MB1Click,
  	glow_eEventType_CallBack, grownames_cb);
  grow_EnableEvent( (GrowCtx *)curve->grownames_ctx, glow_eEvent_Resized,
  	glow_eEventType_CallBack, grownames_cb);
  grow_EnableEvent( (GrowCtx *)curve->grownames_ctx, glow_eEvent_HotRequest, 
	glow_eEventType_CallBack, grownames_cb);

  curve->config_names();
  return 1;
}

int GeCurve::config_names()
{
  if ( !cd)
    return 0;

  grow_tObject t1;
  glow_eDrawType color;
  double x, y;
  grow_sAttributes grow_attr;
  unsigned long mask;
  char path[2][80] = {"$pwrp_exe/", "$pwr_exe/"};
  int date = (strcmp( cd->x_format[0], "%10t") == 0 || 
	      strcmp( cd->x_format[0], "%11t") == 0) ? 1 : 0;        

  int time_size;
  if ( date)
    time_size = 9;
  else
    time_size = 3;

  // Create nodeclass for mark values
  grow_New( grownames_ctx);

  mask = grow_eAttr_color_theme;
  strcpy( grow_attr.color_theme, "$default");
  grow_SetAttributes( grownames_ctx, &grow_attr, mask); 

  grow_SetPath( grownames_ctx, 2, (char *)path);
  grow_ReadCustomColorFile( grownames_ctx, 0);

  grow_SetBackgroundColor( grownames_ctx, glow_eCtColor_Background);

  grow_tNodeClass nc;
  grow_CreateNodeClass( grownames_ctx, "MarkVal", glow_eNodeGroup_Common, &nc);
  grow_AddRect( nc, "", 0, 0, time_size, 0.75, glow_eCtColor_Background, 1, 0,
		glow_mDisplayLevel_1, 0, 0, 0,
		glow_eDrawType_Line, NULL);
  grow_AddAnnot( nc, 0.2, 0.7, 0, glow_eDrawType_TextHelvetica, 
		 glow_eCtColor_BackgroundTextAndLines,
		 3, glow_eAnnotType_OneLine, 0, glow_mDisplayLevel_1, NULL);

  // Draw header
  grow_tObject o1;
  // grow_CreateGrowLine( grownames_ctx, "", 0, 0.75, 60, 0.75,
  //			 glow_eDrawType_Color34, 2, 0, NULL, &o1);
  y = 0;
  grow_CreateGrowRect( grownames_ctx, "", 0, y, 60, y + 0.9,
			 glow_eDrawType_Line, 1, 0, glow_mDisplayLevel_1, 1, 0, 0,
			 glow_eCtColor_AreaDelimiter, NULL, &o1);
  x = 0.8;
  grow_CreateGrowText( grownames_ctx, "", Lng::translate("View"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &o1);
  x += 1.8;
  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Cursor"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &o1);
  x += time_size + 0.2;
  grow_CreateGrowRect( grownames_ctx, "", x - 0.4, 0, time_size, 0.75,
		       glow_eCtColor_IndicatorBorderColor, 1, 0, glow_mDisplayLevel_1, 1, 1, 1,
		       glow_eCtColor_AreaDelimiter, NULL, &mark1_rect);
  grow_SetObjectShadowWidth( mark1_rect, 10);

  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Mark 1"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &mark1_text);
  // TODO
  x += time_size + 0.2;
  grow_CreateGrowRect( grownames_ctx, "", x - 0.4, 0, time_size, 0.75,
		       glow_eCtColor_IndicatorBorderColor, 1, 0, glow_mDisplayLevel_1, 1, 1, 1,
		       glow_eCtColor_AreaDelimiter, NULL, &mark2_rect);
  grow_SetObjectShadowWidth( mark2_rect, 10);

  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Mark 2"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &mark2_text);
  x += time_size + 0.2;
  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Unit"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &o1);
  x += 2;
  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Scale"),
		       x, y + 0.6, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &o1);
  if ( options & curve_mOptions_ShowDescrFirst) {
    x += 3;
    grow_CreateGrowText( grownames_ctx, "", Lng::translate("Description"),
			 x, y + 0.6, glow_eDrawType_TextHelvetica, 
			 glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
			 glow_mDisplayLevel_1, NULL, &o1);
  
    x += 14;
    grow_CreateGrowText( grownames_ctx, "", Lng::translate("Attribute"),
			 x, y + 0.6, glow_eDrawType_TextHelvetica, 
			 glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
			 glow_mDisplayLevel_1, NULL, &o1);
  }
  else {
    x += 3;
    grow_CreateGrowText( grownames_ctx, "", Lng::translate("Attribute"),
			 x, y + 0.6, glow_eDrawType_TextHelvetica, 
			 glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
			 glow_mDisplayLevel_1, NULL, &o1);
  
    x += 14;
    grow_CreateGrowText( grownames_ctx, "", Lng::translate("Description"),
			 x, y + 0.6, glow_eDrawType_TextHelvetica, 
			 glow_eCtColor_AreaDelimiterTextAndLines, 3, glow_eFont_LucidaSans, 
			 glow_mDisplayLevel_1, NULL, &o1);
  }
  
  y += 1;
  for ( int i = 0; i < cd->cols; i++) {
    // Draw shadowed frame
    grow_CreateGrowLine( grownames_ctx, "", 0, y + 1, 60, y + 1,
			 glow_eCtColor_LineDelimiter, 1, 0, NULL, &o1);

    // Draw color rectangle
    grow_CreateGrowRect( grownames_ctx, "", 0.25, y + 0.3, 0.75, 0.5,
			 glow_eCtColor_IndicatorBorderColor, 1, 0, glow_mDisplayLevel_1, 1, 1, 0,
			 cd->color[i], NULL, &name_rect[i]);

    if ( hide[i])
      color = glow_eCtColor_Background;
    else
      color = glow_eCtColor_BackgroundTextAndLines;

    // Draw checkbox for hide
    grow_CreateGrowLine( grownames_ctx, "", 1.4, y + 0.45, 1.52, y + 0.75,
			 color, 2, 0, NULL, &hide_l1[i]);
    grow_CreateGrowLine( grownames_ctx, "", 1.50, y + 0.75, 1.77, y + 0.35,
			 color, 2, 0, NULL, &hide_l2[i]);
    grow_CreateGrowRect( grownames_ctx, "", 1.3, y + 0.3, 0.5, 0.5,
			 color, 1, 0, glow_mDisplayLevel_1, 0, 1, 0,
			 glow_eCtColor_Background, NULL, &hide_rect[i]);

    // Draw nodes for mark and cursor values
    x = 2.2;
    grow_CreateGrowNode( grownames_ctx, "", nc, x, y + 0.05, NULL, 
			 &cursor_annot[i]);
    x += time_size + 0.2;
    grow_CreateGrowNode( grownames_ctx, "", nc, x, y + 0.05, NULL, 
			 &mark1_annot[i]);
    // TODO
    x += time_size + 0.2;
    grow_CreateGrowNode( grownames_ctx, "", nc, x, y + 0.05, NULL, 
			 &mark2_annot[i]);
    // Draw unit
    x += time_size + 0.6;
    if ( strcmp( cd->y_unit[i], "") == 0)
      strcpy( cd->y_unit[i], " ");
    grow_CreateGrowText( grownames_ctx, "", cd->y_unit[i],
			 x, y + 0.75, glow_eDrawType_TextHelvetica, 
			 glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans, 
			 glow_mDisplayLevel_1, NULL, &t1);
    // Draw button for scale
    x += 2;
    grow_CreateGrowRect( grownames_ctx, "", x, y + 0.1, 1.2, 0.7,
			 glow_eCtColor_ButtonBordercolor, 1, 0, glow_mDisplayLevel_1, 1, 1, 1,
			 glow_eCtColor_ButtonFillcolor, NULL, &scale_rect[i]);
    grow_SetObjectShadowWidth( scale_rect[i], 20);
    // Draw attribute name
    if ( options & curve_mOptions_ShowDescrFirst) {
      double w, h, descent;

      x += 3;
      if ( strcmp( cd->y_description[i], "") != 0) {
	grow_CreateGrowText( grownames_ctx, "", cd->y_description[i],
			     x, y + 0.75, glow_eDrawType_TextHelvetica,
			     glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans,
			     glow_mDisplayLevel_1, NULL, &t1);

	grow_GetTextExtent( grownames_ctx, cd->y_name[i], strlen(cd->y_name[i]),
			    glow_eDrawType_TextHelvetica, 3, glow_eFont_LucidaSans, &w, &h, &descent);
	if ( w < 13)
	  x += 14;
	else
	  x += w + 1;
      }	
      else
	x += 14;
	
      grow_CreateGrowText( grownames_ctx, "", cd->y_name[i],
			   x, y + 0.75, glow_eDrawType_TextHelvetica,
			   glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans,
			   glow_mDisplayLevel_1, NULL, &t1);
    }
    else {
      x += 3;
      grow_CreateGrowText( grownames_ctx, "", cd->y_name[i],
			   x, y + 0.75, glow_eDrawType_TextHelvetica,
			   glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans,
			   glow_mDisplayLevel_1, NULL, &t1);

      if ( strcmp( cd->y_description[i], "") != 0) {
	double w, h, descent;

	grow_GetTextExtent( grownames_ctx, cd->y_name[i], strlen(cd->y_name[i]),
			    glow_eDrawType_TextHelvetica, 3, glow_eFont_LucidaSans, &w, &h, &descent);
	if ( w < 13)
	  x += 14;
	else
	  x += w + 1;
	
	grow_CreateGrowText( grownames_ctx, "", cd->y_description[i],
			     x, y + 0.75, glow_eDrawType_TextHelvetica,
			     glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans,
			     glow_mDisplayLevel_1, NULL, &t1);
      }
    }

    grow_SetAnnotation( cursor_annot[i], 0, "0", 1);
    grow_SetAnnotation( mark1_annot[i], 0, "0", 1);
    grow_SetAnnotation( mark2_annot[i], 0, "0", 1);
    y += 1;
  }
  // Draw nodes for time values
  // Draw shadowed frame
  grow_CreateGrowLine( grownames_ctx, "", 0, y + 1, 60, y + 1,
		       glow_eCtColor_LineDelimiter, 1, 0, NULL, &o1);
  x = 2.2;
  grow_CreateGrowNode( grownames_ctx, "", nc, x, y + 0.05, NULL, 
		       &cursor_annot[cd->cols]);
  x += time_size + 0.2;
  grow_CreateGrowNode( grownames_ctx, "", nc, x, y +0.05, NULL, 
		       &mark1_annot[cd->cols]);
  // TODO
  x += time_size + 0.2;
  grow_CreateGrowNode( grownames_ctx, "", nc, x, y + 0.05, NULL, 
		       &mark2_annot[cd->cols]);
  // Draw unit
  x += time_size + 0.6;
  grow_CreateGrowText( grownames_ctx, "", "s",
		       x, y + 0.75, glow_eDrawType_TextHelvetica, 
		       glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &t1);
  // Draw button for scale
  x += 2;
  grow_CreateGrowRect( grownames_ctx, "", x, y + 0.1, 1.2, 0.7,
		       glow_eCtColor_ButtonBordercolor, 1, 0, glow_mDisplayLevel_1, 1, 1, 1,
		       glow_eCtColor_ButtonFillcolor, NULL, &scale_rect[cd->cols]);
  grow_SetObjectShadowWidth( scale_rect[cd->cols], 20);
  // Draw attribute name
  x += 3;
  grow_CreateGrowText( grownames_ctx, "", Lng::translate("Time axis"),
		       x, y + 0.75, glow_eDrawType_TextHelvetica,
		       glow_eCtColor_BackgroundTextAndLines, 3, glow_eFont_LucidaSans, 
		       glow_mDisplayLevel_1, NULL, &t1);
  grow_SetAnnotation( cursor_annot[cd->cols], 0, "0", 1);
  grow_SetAnnotation( mark1_annot[cd->cols], 0, "0", 1);
  grow_SetAnnotation( mark2_annot[cd->cols], 0, "0", 1);

  return 1;
}

void GeCurve::scroll( double value)
{
  curve_Scroll( growcurve_ctx, value);
}

void GeCurve::print( char *filename)
{
  double x;
  int w1, w2, h1, h2;
  glow_eDrawType c1, c2, c3;

  grow_GetBackgroundColor( growaxis_ctx, &c1);
  grow_GetBackgroundColor( growcurve_ctx, &c2);
  grow_GetBackgroundColor( grownames_ctx, &c3);
  grow_SetBackgroundColor( growaxis_ctx, glow_eDrawType_Color4);
  grow_SetBackgroundColor( growcurve_ctx, glow_eDrawType_Color4);
  grow_SetBackgroundColor( grownames_ctx, glow_eDrawType_Color4);
  grow_Redraw( growaxis_ctx);
  grow_Redraw( growcurve_ctx);
  grow_Redraw( grownames_ctx);
  grow_GetWindowSize( growaxis_ctx, &w1, &h1);
  grow_GetWindowSize( growcurve_ctx, &w2, &h2);
  if ( w1 + w2 == 0)
    return;
  x = double(w1) / (w1 + w2);
  grow_Print( growaxis_ctx, filename, 0.0, x, 0);
  grow_Print( growcurve_ctx, filename, x, 1.0, 0);
  grow_Print( grownames_ctx, filename, 0, 0, 1);
  grow_SetBackgroundColor( growaxis_ctx, c1);
  grow_SetBackgroundColor( growcurve_ctx, c2);
  grow_SetBackgroundColor( grownames_ctx, c3);
  grow_Redraw( growaxis_ctx);
  grow_Redraw( growcurve_ctx);
  grow_Redraw( grownames_ctx);
}

int GeCurve::configure_axes()
{
  double x = 0;
  int i, idx;
  grow_sAttributes grow_attr;
  unsigned long mask;
  char path[2][80] = {"$pwrp_exe/", "$pwr_exe/"};

  if ( !cd)
    return 0;

  grow_SetNodraw( growaxis_ctx);
  grow_New( growaxis_ctx);

  mask = grow_eAttr_color_theme;
  strcpy( grow_attr.color_theme, "$default");
  grow_SetAttributes( growaxis_ctx, &grow_attr, mask); 

  grow_SetPath( growaxis_ctx, 2, (char *)path);
  grow_ReadCustomColorFile( growaxis_ctx, 0);

  grow_SetBackgroundColor( growaxis_ctx, glow_eCtColor_Background);

  memset( axis_object, 0, sizeof( axis_object));

  for ( i = 0; i < cd->cols; i++) {
    if ( cd->y_value_type[i] != pwr_eType_Boolean &&
         !hide[i]) {
      grow_CreateGrowRect( growaxis_ctx, "", x, 0,
			 cd->y_axis_width[i], 30,
			   glow_eDrawType_Line, 1, 0, glow_mDisplayLevel_1, 1, 0, 0,
			 cd->axiscolor[i], NULL, 
			 &axis_rect[i]);
      x += cd->y_axis_width[i];
    }
  }

  // Draw horizontal lines with same interval as the trend object
  // Get number of horizontal lines from first not hidden float
  idx = 0;
  for ( i = 0; i < cd->cols; i++) {
    if ( cd->y_value_type[i] != pwr_eType_Boolean && !hide[i]) {
      idx = i;
      break;
    }
  }
  grow_CreateGrowAxis( growaxis_ctx, "",
		       -2, 0, x + 1, 30,
                       border_bright, 1, 0,
                       glow_eDrawType_TextHelvetica, NULL,
		       &axis_lineobject);
  grow_SetAxisConf( axis_lineobject, 0, 10,
         2 * cd->y_trend_lines[idx] - 1, 1, 10, 0, "%1.0f");

  x = 0;
  for ( i = 0; i < cd->cols; i++) {
    if ( cd->y_value_type[i] != pwr_eType_Boolean &&
         !hide[i]) {
      grow_CreateGrowAxis( growaxis_ctx, "",
		       x, 0, x + cd->y_axis_width[i], 30,
                       glow_eDrawType_Line, 1, 6,
                       glow_eDrawType_TextHelvetica, NULL, 
		       &axis_object[i]);

      grow_SetAxisConf( axis_object[i], cd->y_max_value_axis[i],
         cd->y_min_value_axis[i],
         2 * cd->y_trend_lines[i] - 1, 2, 2, 0, cd->y_format[i]);
      x += cd->y_axis_width[i];
    }
  }
  axis_window_width = MAX( x, 1);

  double zoom_x, zoom_y;
  int width;

  curve_GetZoom( growcurve_ctx, &zoom_x, &zoom_y);
  grow_ZoomY( growaxis_ctx, zoom_y);
  grow_ZoomX( growaxis_ctx, zoom_y);

  if ( axis_displayed) {
    width = int( zoom_y * axis_window_width);
    axis_set_width( width);
  }
  grow_ResetNodraw( growaxis_ctx);
  grow_Redraw( growaxis_ctx);

  update_times_markers();
  return 1;
}

int GeCurve::configure_curves()
{
  int i, j, idx;
  glow_sCurveData gcd;
  int max_index, min_index;
  double minval, maxval;

  if ( !cd)
    return 0;

  if ( cd->type == curve_eDataType_LogFile || 
       cd->type == curve_eDataType_DsTrend) {
    gcd.type = glow_eCurveDataType_CommonX;

    // Get max and min index in x
    max_index = -1;
    min_index = -1;
    if ( cd->x_min_value_axis[0] > cd->x_min_value[0] || 
	 cd->x_max_value_axis[0] < cd->x_max_value[0]) {
      for ( i = 0; i < cd->rows[0]; i++) {
	if ( min_index == -1 && cd->x_data[0][i] >= cd->x_min_value_axis[0] )
	  min_index = i;
	if ( max_index == -1 && cd->x_data[0][i] >= cd->x_max_value_axis[0] )
	  max_index = i - 1;
      }
      if ( min_index == -1)
	min_index = 0;
      if ( max_index == -1)
	max_index = cd->rows[0] - 1; 
    }
    else {
      max_index = cd->rows[0] - 1;
      min_index = 0;
    }

    idx = 0;
    for ( i = 0; i < cd->cols; i++) {
      if ( !hide[i]) {
	gcd.y_max_value[idx] = cd->y_max_value_axis[i];
	gcd.y_min_value[idx] = cd->y_min_value_axis[i];
	gcd.y_data[idx] = &cd->y_data[i][min_index];
	gcd.color[idx] = cd->color[i];
	gcd.fillcolor[idx] = cd->fillcolor[i];
	gcd.curve_type[idx] = glow_eCurveType_Inherit;
	if ( cd->y_value_type[i] == pwr_eType_Boolean)
	  gcd.curve_type[idx] = glow_eCurveType_DigSquare;
	else
	  gcd.curve_type[idx] = glow_eCurveType_Inherit;
	  
	idx++;
	if ( idx == TREND_MAX_CURVES - 1)
	  break;
      }
    }
    gcd.x_data[0] = &cd->x_data[0][min_index];
    gcd.x_max_value[0] = cd->x_max_value_axis[0];
    gcd.x_min_value[0] = cd->x_min_value_axis[0];
    
    gcd.curves = idx;
    gcd.rows[0] = max_index - min_index + 1;
    gcd.x_reverse = cd->x_reverse;
    
    // Get number of horizontal lines from first not hidden float
    idx = 1;
    for ( i = 0; i < cd->cols; i++) {
      if ( cd->y_value_type[i] != pwr_eType_Boolean && !hide[i]) {
	idx = i;
	break;
      }
    }
    
    grow_SetTrendLines( curve_object,
	              // int(cd->max_value_axis[0] - cd->min_value_axis[0] - 1),
			cd->x_trend_lines[0] - 2,
			2 * cd->y_trend_lines[idx] - 3);

    if ( cd->x_reverse) {
      minval = cd->x_max_value_axis[0];
      maxval = cd->x_min_value_axis[0];
    }
    else {
      minval = cd->x_min_value_axis[0];
      maxval = cd->x_max_value_axis[0];
    }

    grow_SetAxisConf( curve_axisobject, minval, maxval, 
		      // 10 * int( cd->max_value_axis[0] - cd->min_value_axis[0]) + 1, 
		      cd->x_axis_lines[0],
		      cd->x_axis_linelongq[0], cd->x_axis_valueq[0], 270, cd->x_format[0]);

    grow_CurveConfigure( curve_object, &gcd); 
  }
  else if ( cd->type == curve_eDataType_MultiTrend) {
    gcd.type = glow_eCurveDataType_SeparateX;


    idx = 0;
    for ( i = 0; i < cd->cols; i++) {
      if ( !hide[i]) {

	// Get max and min index in x
	max_index = -1;
	min_index = -1;
	if ( cd->x_min_value_axis[i] > cd->x_min_value[i] || 
	     cd->x_max_value_axis[i] < cd->x_max_value[i]) {
	  for ( j = 0; j < cd->rows[i]; j++) {
	    if ( min_index == -1 && cd->x_data[i][j] >= cd->x_min_value_axis[i] )
	      min_index = j;
	    if ( max_index == -1 && cd->x_data[i][j] >= cd->x_max_value_axis[i] )
	      max_index = j - 1;
	  }
	  if ( min_index == -1)
	    min_index = 0;
	  if ( max_index == -1)
	    max_index = cd->rows[i] - 1; 
	}
	else {
	  max_index = cd->rows[i] - 1;
	  min_index = 0;
	}

	gcd.y_max_value[idx] = cd->y_max_value_axis[i];
	gcd.y_min_value[idx] = cd->y_min_value_axis[i];
	gcd.y_data[idx] = &cd->y_data[i][min_index];
	gcd.color[idx] = cd->color[i];
	gcd.fillcolor[idx] = cd->fillcolor[i];
	if ( cd->y_value_type[i] == pwr_eType_Boolean)
	  gcd.curve_type[idx] = glow_eCurveType_DigSquare;
	else
	  gcd.curve_type[idx] = glow_eCurveType_Inherit;

	gcd.x_data[idx] = &cd->x_data[i][min_index];
	gcd.x_max_value[idx] = cd->x_max_value_axis[i];
	gcd.x_min_value[idx] = cd->x_min_value_axis[i];
	gcd.rows[idx] = max_index - min_index + 1;

	idx++;
	if ( idx == TREND_MAX_CURVES - 1)
	  break;
      }
    }
    
    gcd.curves = idx;
    gcd.x_reverse = cd->x_reverse;
    
    // Get number of horizontal lines from first not hidden float
    idx = 1;
    for ( i = 0; i < cd->cols; i++) {
      if ( cd->y_value_type[i] != pwr_eType_Boolean && !hide[i]) {
	idx = i;
	break;
      }
    }
    
    grow_SetTrendLines( curve_object,
	              // int(cd->max_value_axis[0] - cd->min_value_axis[0] - 1),
			cd->x_trend_lines[0] - 2,
			2 * cd->y_trend_lines[idx] - 3);

    if ( cd->x_reverse) {
      minval = cd->x_max_value_axis[0];
      maxval = cd->x_min_value_axis[0];
    }
    else {
      minval = cd->x_min_value_axis[0];
      maxval = cd->x_max_value_axis[0];
    }

    grow_SetAxisConf( curve_axisobject, minval, maxval, 
		      // 10 * int( cd->max_value_axis[0] - cd->min_value_axis[0]) + 1, 
		      cd->x_axis_lines[0],
		      cd->x_axis_linelongq[0], cd->x_axis_valueq[0], 270, cd->x_format[0]);

    grow_CurveConfigure( curve_object, &gcd); 
  }
  return 1;
}

void GeCurve::redraw()
{
  grow_Redraw( growcurve_ctx);
}

void GeCurve::points_added( unsigned int no_of_points)
{
  int i, idx;
  glow_sCurveData gcd;
  int max_index, min_index;

  gcd.type = glow_eCurveDataType_CommonX;

  // Get max and min index in x */
  max_index = -1;
  min_index = -1;
  if ( cd->x_min_value_axis[0] > cd->x_min_value[0] || 
       cd->x_max_value_axis[0] < cd->x_max_value[0]) {
    for ( i = 0; i < cd->rows[0]; i++) {
      if ( min_index == -1 && cd->x_data[0][i] >= cd->x_min_value_axis[0] )
        min_index = i;
      if ( max_index == -1 && cd->x_data[0][i] >= cd->x_max_value_axis[0] )
        max_index = i - 1;
    }
    if ( min_index == -1)
      min_index = 0;
    if ( max_index == -1)
      max_index = cd->rows[0] - 1; 
  }
  else {
    max_index = cd->rows[0] - 1;
    min_index = 0;
  }

  idx = 0;
  for ( i = 0; i < cd->cols; i++) {
    if ( !hide[i]) {
      gcd.y_max_value[idx] = cd->y_max_value_axis[i];
      gcd.y_min_value[idx] = cd->y_min_value_axis[i];
      gcd.y_data[idx] = &cd->y_data[i][min_index];
      gcd.color[idx] = cd->color[i];
      gcd.fillcolor[idx] = cd->fillcolor[i];
      idx++;
      if ( idx == TREND_MAX_CURVES - 1)
	break;
    }
  }
  gcd.x_max_value[0] = cd->x_max_value_axis[0];
  gcd.x_min_value[0] = cd->x_min_value_axis[0];
  gcd.x_data[0] = &cd->x_data[0][min_index];
  gcd.curves = idx;
  gcd.rows[0] = max_index - min_index + 1;
  gcd.x_reverse = cd->x_reverse;

  grow_CurveAddPoints( curve_object, &gcd, no_of_points);

  // Simulate cursormotion and slidermoved event to update markvalues
  glow_sEvent e;
  e.any.event = glow_eEvent_CursorMotion;
  e.any.x = last_cursor_x;
  growcurve_cb( growcurve_ctx, &e);
  e.any.event = glow_eEvent_SliderMoved;
  e.any.x = last_mark1_x;
  e.object.object = curve_markobject1;
  growcurve_cb( growcurve_ctx, &e);
  e.any.event = glow_eEvent_SliderMoved;
  e.any.x = last_mark2_x;
  e.object.object = curve_markobject2;
  growcurve_cb( growcurve_ctx, &e);

}

int GeCurve::read_file( char *filename)
{
  FILE *fp;
  char line[10000];
  char item_str[CURVE_MAX_COLS][80];
  int nr;
  int rows = 0;
  pwr_tFileName fname;
  int i, j;
  int skip_line;

  dcli_translate_filename( fname, filename);

  fp = fopen( fname, "r");
  if ( !fp)
    return 0;
  
  // Attribute names in first line
  if ( !dcli_read_line( line, sizeof(line), fp)) {  
    fclose( fp);
    return 0;
  }
  nr = dcli_parse( line, " 	", "", (char *)item_str, 
		sizeof( item_str) / sizeof( item_str[0]), 
                sizeof( item_str[0]), 0);
  if ( nr == 0) {
    fclose( fp);
    return 0;
  }

  //printf( "line: %s\n", line);
  //for ( i = 0; i < nr; i++)
  //  printf( "item: %s\n", item_str[i]);

  while( dcli_read_line( line, sizeof(line), fp)) {
    rows++;
  }    
  fclose( fp);

  // printf( "Rows: %d\n", rows);

  cd = new GeCurveData( curve_eDataType_LogFile);

  cd->x_reverse = 0;
  cd->cols = nr - 1;
  for ( i = 0; i < nr; i++) {
    cd->rows[i] = rows;
    if ( i == 0) {
      strcpy( cd->x_name, item_str[i]);
      cd->x_data[i] = (double *) malloc( rows * sizeof( double));
      cd->x_axis_type[i] = curve_eAxis_x;
    }
    else {
      strcpy( cd->y_name[i-1], item_str[i]);
      cd->y_data[i-1] = (double *) malloc( rows * sizeof( double));
      cd->y_axis_type[i-1] = curve_eAxis_y;
    }
  }

  fp = fopen( fname, "r");
  dcli_read_line( line, sizeof(line), fp);

  j = 0;
  while( dcli_read_line( line, sizeof(line), fp)) {
    dcli_parse( line, " 	", "", (char *)item_str, 
		sizeof( item_str) / sizeof( item_str[0]), 
                sizeof( item_str[0]), 0);
    skip_line = 0;
    for ( i = 0; i < cd->cols + 1; i++) {
      if ( i == 0)
	nr = sscanf( item_str[i], "%lf", &cd->x_data[i][j]);
      else
	nr = sscanf( item_str[i], "%lf", &cd->y_data[i-1][j]);
      if ( nr != 1) {
        if ( i == 0) {
          printf( "Unreadble line %d\n", j);
          skip_line = 1;
          cd->rows[0]--;
          break;
        }
        else
          cd->y_data[i-1][j] = 0;
      }
    }
    if ( skip_line)
      continue;
    j++;
  }    
  fclose( fp);

  return 1;
}

void GeCurve::set_time( pwr_tTime time)
{
  char time_str[40];
  char full_title[500];

  time_AtoAscii( &time, time_eFormat_DateAndTime, time_str, sizeof(time_str));

  sprintf( full_title, "%s   %s", title, time_str);
  write_title( full_title);
}

void GeCurve::set_title( char *str)
{
  strcpy( title, str);
  write_title( str);
}

void GeCurve::measure_window( double *ll_x, double *ll_y, double *ur_x, double *ur_y)
{
  grow_MeasureWindow( growcurve_ctx, ll_x, ll_y, ur_x, ur_y);
}

void GeCurve::set_curvedata( GeCurveData *curve_data)
{
  if ( cd)
    delete cd;
  cd = curve_data;
}

GeCurve::~GeCurve()
{
}

GeCurve::GeCurve( void 	*gc_parent_ctx, 
		  char	*curve_name,
		  char  *filename,
                  GeCurveData *curve_data,
                  int   pos_right,
		  int 	gc_width,
		  int	gc_height,
		  unsigned int gc_options,
		  int   gc_color_theme) :
  parent_ctx(gc_parent_ctx), growcurve_ctx(0), background_dark(glow_eCtColor_DiagramFillcolor),
  background_bright(glow_eDrawType_Color21),
  border_dark(glow_eCtColor_DiagramBordercolor),
  border_bright(glow_eDrawType_Color22),
  cd(0), axis_window_width(0), auto_refresh(1), axis_displayed(1),
  minmax_idx(0), close_cb(0), help_cb(0), increase_period_cb(0), decrease_period_cb(0), reload_cb(0),
  prev_period_cb(0), next_period_cb(0), add_cb(0), madd_cb(0), remove_cb(0), export_cb(0), new_cb(0),
  save_cb(0), open_cb(0), snapshot_cb(0),
  initial_right_position(pos_right), last_cursor_x(0), last_mark1_x(0), last_mark2_x(0),
  last_mark1_time(pwr_cNTime), last_mark2_time(pwr_cNTime),
  deferred_configure_axes(0), center_from_window(0), options(gc_options), layout_mask(0), 
  color_theme(gc_color_theme), current_period(time_ePeriod_OneHour), fill_curves(0)
{
  pwr_tStatus sts;

  memset( hide, 0, sizeof(hide));
  memset( name_rect, 0, sizeof(name_rect));
  memset( hide_rect, 0, sizeof(hide_rect));
  memset( scale_rect, 0, sizeof(scale_rect));
  memset( hide_l1, 0, sizeof(hide_l1));
  memset( hide_l2, 0, sizeof(hide_l2));
  memset( cursor_annot, 0, sizeof(cursor_annot));
  memset( mark1_annot, 0, sizeof(mark1_annot));
  memset( mark2_annot, 0, sizeof(mark2_annot));
  curve_color = background_dark;
  curve_border = border_dark;
  for ( int i = TREND_MAX_CURVES; i < CURVE_MAX_COLS; i++)
    hide[i] = 1;

  if ( filename) {
    sts = read_file( filename);
    if ( EVEN(sts)) return;

    cd->get_borders();
    cd->get_default_axis();
    cd->select_color( curve_color == background_dark);
  }
  else if ( curve_data) {
    cd = curve_data;
    cd->select_color( curve_color == background_dark);
  }

  char color_theme_file[80];
  sprintf( color_theme_file, "pwr_colortheme%d", color_theme);
  grow_SetDefaultColorTheme( color_theme_file);
}

GeCurveData::GeCurveData( curve_eDataType datatype) :
  type(datatype), cols(0), x_reverse(0), time_format(curve_eTimeFormat_Float)
{
  memset( x_data, 0, sizeof(x_data));
  memset( y_data, 0, sizeof(y_data));
  for ( int i = 0; i < CURVE_MAX_COLS; i++) {
    strcpy( y_unit[i], "");
    strcpy( y_format[i], "");
    strcpy( y_name[i], "");
    strcpy( y_description[i], "");
    rows[i] = 0;
    y_max_value[i] = 0;
    y_min_value[i] = 0;
    y_min_value_axis[i] = 0;
    y_max_value_axis[i] = 0;
    y_trend_lines[i] = 0;
    y_axis_lines[i] = 0;
    y_axis_linelongq[i] = 0;
    y_axis_valueq[i] = 0;
    
    y_axis_width[i] = 0;

    strcpy( x_unit[i], "");
    strcpy( x_format[i], "");
    x_max_value[i] = 0;
    x_min_value[i] = 0;
    x_min_value_axis[i] = 0;
    x_max_value_axis[i] = 0;
    x_trend_lines[i] = 0;
    x_axis_lines[i] = 0;
    x_axis_linelongq[i] = 0;
    x_axis_valueq[i] = 0;
  }
  strcpy( x_name, "");
}

GeCurveData::~GeCurveData()
{
  for ( int i = 0; i < cols; i++) {
    free( (char *) y_data[i]);
    if ( x_data[i])
      free( (char *) x_data[i]);
  }
}

void GeCurveData::get_borders()
{
  for ( int i = 0; i < cols; i++) {
    y_max_value[i] = 1e-37;
    y_min_value[i] = 1e37;

    y_value_type[i] = pwr_eType_Boolean;

    for ( int j = 0; j < rows[i]; j++) {
      if ( y_data[i][j] < y_min_value[i])
        y_min_value[i] = y_data[i][j];
      if ( y_data[i][j] > y_max_value[i])
        y_max_value[i] = y_data[i][j];
      if ( y_value_type[i] == pwr_eType_Boolean && 
           !( y_data[i][j] == 1 || y_data[i][j] == 0)) {
        y_value_type[i] = pwr_eType_Float64;
        // printf( "Not Boolean %s: %f\n", name[i], data[i][j]);
      }
    }
  } 

  if ( type == curve_eDataType_MultiTrend) {
    for ( int i = 0; i < cols; i++) {
      x_max_value[i] = 1e-37;
      x_min_value[i] = 1e37;

      x_value_type[i] = pwr_eType_Float64;

      for ( int j = 0; j < rows[i]; j++) {
	if ( x_data[i][j] < x_min_value[i])
	  x_min_value[i] = x_data[i][j];
	if ( x_data[i][j] > x_max_value[i])
	  x_max_value[i] = x_data[i][j];
      }
    }
  } 
  else {
    for ( int i = 0; i < 1; i++) {
      x_max_value[i] = 1e-37;
      x_min_value[i] = 1e37;

      x_value_type[i] = pwr_eType_Float64;

      for ( int j = 0; j < rows[0]; j++) {
	if ( x_data[i][j] < x_min_value[i])
	  x_min_value[i] = x_data[i][j];
	if ( x_data[i][j] > x_max_value[i])
	  x_max_value[i] = x_data[i][j];
      }
    }
  } 
}

void GeCurveData::get_default_axis()
{
  double min_value, max_value;

  for ( int i = 0; i < cols; i++) {
    if ( GeCurve::get_saved_minmax( y_name[i], &min_value, &max_value))
      scale( y_axis_type[i], y_value_type[i], min_value,  max_value, 
	     &y_min_value_axis[i], &y_max_value_axis[i], &y_trend_lines[i], &y_axis_lines[i], 
	     &y_axis_linelongq[i], &y_axis_valueq[i], y_format[i], 
	     &y_axis_width[i], 0, 0);
    else 
      scale( y_axis_type[i], y_value_type[i], y_min_value[i],  y_max_value[i], 
	     &y_min_value_axis[i], &y_max_value_axis[i], &y_trend_lines[i], &y_axis_lines[i], 
	     &y_axis_linelongq[i], &y_axis_valueq[i], y_format[i], 
	     &y_axis_width[i], 0, 0);
  }
  int i = 0;
  double axis_width;

  if ( type != curve_eDataType_MultiTrend) {
    scale( x_axis_type[i], x_value_type[i], x_min_value[i],  x_max_value[i], 
	   &x_min_value_axis[i], &x_max_value_axis[i], &x_trend_lines[i], &x_axis_lines[i], 
	   &x_axis_linelongq[i], &x_axis_valueq[i], x_format[i], 
	   &axis_width, 0, 0);
  }
  else {
    double min_value = 1e37;
    double max_value = -1e37;

    for ( i = 0; i < cols; i++) {
      if ( x_min_value[i] < min_value)
	min_value = x_min_value[i];
      if ( x_max_value[i] > max_value)
	max_value = x_max_value[i];
    }
    scale( x_axis_type[0], x_value_type[0], min_value, max_value, 
	   &x_min_value_axis[0], &x_max_value_axis[0], &x_trend_lines[0], &x_axis_lines[0], 
	   &x_axis_linelongq[0], &x_axis_valueq[0], x_format[0], 
	   &axis_width, 0, 0);
    for ( i = 1; i < cols; i++) {
      x_min_value_axis[i] = x_min_value_axis[0];
      x_max_value_axis[i] = x_max_value_axis[0];
    }
  }
}

void GeCurveData::select_color( bool dark_bg)
{
  int j;

  for ( int i = 0; i < cols; i++) {
    j = i % 9;
    switch( j) {
    case 0:
      // Orange
      if ( dark_bg)
	color[i] = glow_eDrawType_Color144;
      else
	color[i] = glow_eDrawType_Color146;
      axiscolor[i] = glow_eDrawType_Color135;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color137;
      else
	fillcolor[i] = glow_eDrawType_Color133;
      break;
    case 1:
      // YellowGreen
      if ( dark_bg)
	color[i] = glow_eDrawType_Color85;
      else
	color[i] = glow_eDrawType_Color87;
      axiscolor[i] = glow_eDrawType_Color75;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color67;
      else
	fillcolor[i] = glow_eDrawType_Color64;
      break;
    case 2:
      // Yellow
      if ( dark_bg)
	color[i] = glow_eDrawType_Color115;
      else
	color[i] = glow_eDrawType_Color117;
      axiscolor[i] = glow_eDrawType_Color105;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color107;
      else
	fillcolor[i] = glow_eDrawType_Color104;
      break;
    case 3:
      // Blue
      color[i] = glow_eDrawType_Color235;
      axiscolor[i] = glow_eDrawType_Color225;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color227;
      else
	fillcolor[i] = glow_eDrawType_Color214;
      break;
    case 4:
      // Violet
      color[i] = glow_eDrawType_Color205;
      axiscolor[i] = glow_eDrawType_Color195;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color197;
      else
	fillcolor[i] = glow_eDrawType_Color184;
      break;
    case 5:
      // Red
      color[i] = glow_eDrawType_Color175;
      axiscolor[i] = glow_eDrawType_Color165;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color167;
      else
	fillcolor[i] = glow_eDrawType_Color154;
      break;
    case 6:
      // Green
      color[i] = glow_eDrawType_Color295;
      axiscolor[i] = glow_eDrawType_Color285;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color287;
      else
	fillcolor[i] = glow_eDrawType_Color274;
      break;
    case 7:
      // Gray
      color[i] = glow_eDrawType_Color35;
      axiscolor[i] = glow_eDrawType_Color35;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color37;
      else
	fillcolor[i] = glow_eDrawType_Color34;
      break;
    case 8:
      // Seablue
      color[i] = glow_eDrawType_Color265;
      axiscolor[i] = glow_eDrawType_Color255;
      if ( dark_bg)
	fillcolor[i] = glow_eDrawType_Color257;
      else
	fillcolor[i] = glow_eDrawType_Color254;
      break;
    }
  }
}

void GeCurveData::scale( int axis_type, int value_type, 
     double min_value, double max_value, 
     double *min_value_axis, double *max_value_axis, 
     int *trend_lines, int *axis_lines, int *axis_linelongq, int *axis_valueq, 
     char *format, double *axis_width, int not_zero, int allow_odd)
{
  double value, maxval, minval;
  int i_value;
  int n, max_n, min_n;
  int min_lines, max_lines;
  int min_zero, max_zero;
  int format_int, format_dec;
  int trendlinequot = 2;
  int axlinequot = 2;
  int axvaluequot = 2;
  
  time_format = curve_eTimeFormat_Float;

  // Scale 0 - 10 for boolean
  if ( value_type == pwr_eType_Boolean) {
    maxval = 10;
    minval = 0;
    i_value = 10;
    max_lines = i_value;
    min_lines = 0;
    n = 0;
  }
  else {      
    n = 0;
    if ( (type == curve_eDataType_LogFile || type == curve_eDataType_DsTrend || 
	  type == curve_eDataType_MultiTrend) 
         && axis_type == curve_eAxis_x) {
      // Time axis
      if ( max_value - min_value < 300) {
        i_value = int(max_value + 0.99);
        maxval = i_value;
        max_lines = i_value;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value - 0.99);
        minval = i_value;
        min_lines = i_value;
      }
      else if ( max_value - min_value < 1000) {
        i_value = int(max_value/10) * 10 + 10;
        maxval = i_value;
        max_lines = i_value / 10;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/10) * 10 - 10;
        minval = i_value;
        min_lines = i_value / 10;
      }
      else if ( max_value - min_value < 3000) {
        i_value = int(max_value/50) * 50 + 50;
        maxval = i_value;
        max_lines = i_value / 50;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/50) * 50 - 50;
        minval = i_value;
        min_lines = i_value / 50;
      }
      else if ( max_value - min_value < 10000) {
        i_value = int(max_value/100) * 100 + 100;
        maxval = i_value;
        max_lines = i_value / 100;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/100) * 100 - 100;
        minval = i_value;
        min_lines = i_value / 100;
      }
      else if ( max_value - min_value < 30000) {
        i_value = int(max_value/600) * 600 + 600;
        maxval = i_value;
        max_lines = i_value / 60;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/60) * 60 - 60;
        minval = i_value;
        min_lines = i_value / 60;
	time_format = curve_eTimeFormat_HourMinute;
	trendlinequot = 2;
	axlinequot = 10;
	axvaluequot = 10;
      }
      else if ( max_value - min_value < 60000) {
        i_value = int(max_value/600) * 600 + 600;
        maxval = i_value;
        max_lines = i_value / 600;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/600) * 600 - 600;
        minval = i_value;
        min_lines = i_value / 600;
	axlinequot = 6;
	axvaluequot = 2;
	time_format = curve_eTimeFormat_HourMinute;
      }
      else if ( max_value - min_value < 140000) {
        i_value = int(max_value/600) * 600 + 600;
        maxval = i_value;
        max_lines = i_value / 600;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/600) * 600 - 600;
        minval = i_value;
        min_lines = i_value / 600;
	axlinequot = 6;
	axvaluequot = 12;
	time_format = curve_eTimeFormat_HourMinute;
      }
      else {
        i_value = int(max_value/3600) * 3600 + 3600;
        maxval = i_value;
        max_lines = i_value / 3600;

        if ( fabs( min_value) < DBL_EPSILON)
          i_value = 0;
        else
          i_value = int(min_value/3600) * 3600 - 3600;
        minval = i_value;
        min_lines = i_value / 3600;
	axlinequot = 2 * int((max_value - min_value)/140000);
	axvaluequot = 2 * int((max_value - min_value)/140000);
	time_format = curve_eTimeFormat_DayHour;
      }
    }
    else {
      min_zero = 0;
      max_zero = 0;

      // Power for max_value
      if ( (max_value < DBL_EPSILON && !not_zero) ||
           fabs(max_value) < DBL_EPSILON) {
        maxval = 0;
        max_lines = 0;
        max_n = 0;
        max_zero = 1;
      }
      else {
        value = fabs(max_value);
        n = 0;
        if ( value >= 1) {
          while ( value / 10 > 1) {
            value = value / 10;
            n++;
          }
          max_n = n;
        }
        else {
          while ( value * 10 < 10) {
            value = value * 10;
            n++;
          }
          max_n = -n;
        }
      }

      // Power for min_value
      if ( (min_value > -DBL_EPSILON && !not_zero) ||
           fabs(min_value) < DBL_EPSILON) {
        minval = 0;
        min_lines = 0;
        min_n = 0;
        min_zero = 1;
      }
      else {
        value = fabs(min_value);
        n = 0;
        if ( value >= 1) {
          while ( value / 10 > 1) {
            value = value / 10;
            n++;
          }
          min_n = n;
        }
        else {
          while ( value * 10 < 10) {
            value = value * 10;
            n++;
          }
	  min_n = -n;
        }
      }

      if ( min_zero) {
        // Use power for max_value

        i_value = int( max_value * pow(10, -max_n)) + 1;
        if ( fabs(double(i_value-1) - max_value * pow(10, -n)) < 1e-10)
          i_value--;
        if ( ODD(i_value) && i_value != 5 && !allow_odd) 
          i_value += 1;
        maxval = double(i_value) * pow( 10, max_n);
        max_lines = i_value;
        n = max_n;
      }
      else if ( max_zero) {
        // Use power for min_value

        i_value = int( min_value * pow(10, -min_n)) + ((min_value < 0) ? -1 : 1);
        if ( fabs(double(i_value+1) - min_value * pow(10, -n)) < 1e-10)
          i_value++;
        if ( ODD(i_value) && i_value != 5 && !allow_odd) 
          i_value += 1;
        minval = double(i_value) * pow( 10, min_n);
        min_lines = i_value;
        n = min_n;
      }
      else {
        // Use largest power of min and max
        if ( max_n > min_n)
	  n = max_n;
	else
	  n = min_n;

        if ( max_value > 0) {
          i_value = int( max_value * pow(10, -n)) + 1;
          if ( fabs(double(i_value-1) - max_value * pow(10, -n)) < 1e-10)
            i_value--;
        }
        else
          i_value = int( max_value * pow(10, -n));
        if ( ODD(i_value) && i_value != 5 && !allow_odd) 
          i_value += 1;
        maxval = double(i_value) * pow( 10, n);
        max_lines = i_value;

        if ( min_value < 0) {
          i_value = int( min_value * pow(10, -n)) - 1;
          if ( fabs(double(i_value+1) - min_value * pow(10, -n)) < 1e-10)
            i_value++;
        }
        else
          i_value = int( min_value * pow(10, -n));
        if ( ODD(i_value) && i_value != 5 && !allow_odd) 
          i_value -= 1;
        minval = double(i_value) * pow( 10, n);
        min_lines = i_value;
      }
    }
  }
  *max_value_axis = maxval;
  *min_value_axis = minval;
  *trend_lines = ABS(max_lines - min_lines) + 1;
  *axis_lines = (*trend_lines - 1) * trendlinequot + 1;
  *axis_linelongq = axlinequot;
  *axis_valueq = axvaluequot;

  switch ( time_format) {
  case curve_eTimeFormat_Float:
    // Float format
    format_int = ABS(n) + 1;
    if ( n > 0)
      format_dec = 0;
    else {
      format_dec = ABS(n);
      format_int++;
    }
    if ( minval < 0)
      format_int++;
    
    sprintf( format, "%%%d.%df", format_int, format_dec);
    *axis_width = 0.65 * format_int + 0.4;
    break;
  case curve_eTimeFormat_HourMinute:
    // Hour and minute format
    format_int = ABS(n) + 1;
    strcpy( format, "%2t");
    *axis_width = 0.65 * format_int + 0.4;
    break;
  case curve_eTimeFormat_DayHour:
    // Days and hour format
    format_int = ABS(n) + 1;
    strcpy( format, "%3t");
    *axis_width = 0.65 * format_int + 0.4;
    break;
  }
  //printf( "%f	%f	%f	%f	%3d %5s %4.1f\n", 
  //            min_value, max_value, 
  //            *min_value_axis, *max_value_axis, *trend_lines, format, *axis_width);
  return;
}


void GeCurve::x_to_points( double x, double *t, double *values)
{
  int row;
  double time;

  if ( cd->type != curve_eDataType_MultiTrend) {
    // Time is a date
    if ( !cd->x_reverse)
      time = cd->x_min_value_axis[0] + x *
	(cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
    else
      time = cd->x_min_value_axis[0] + (200.0 - x) *
	(cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
      
    // Approximate row
    row = int ((time - cd->x_min_value[0]) / 
	       (cd->x_max_value[0] - cd->x_min_value[0]) *
	       (cd->rows[0] - 1) + 0.5);
    if ( row > cd->rows[0] - 1)
      row = cd->rows[0] - 1;
    else if ( row < 0)
      row = 0;
    else {
      // Find exact row
      double b1, b2;
      int r = row;
      for (int i = 0;; i++) {
	if ( r == 0) {
	  b2 = (cd->x_data[0][row] + cd->x_data[0][r+1]) / 2;
	  if ( time < b2)
	    break;
	  r++;
	}
	else if ( r == cd->rows[0] - 1) {
	  b1 = (cd->x_data[0][r] + cd->x_data[0][r-1]) / 2;
	  if ( time >= b1)
	    break;
	  r--;
	}
	else {
	  b1 = (cd->x_data[0][r] + cd->x_data[0][r-1]) / 2;
	  b2 = (cd->x_data[0][r] + cd->x_data[0][r+1]) / 2;
	  if ( b1 <= time && time < b2)
	    break;
	  if ( b1 <= time)
	    r++;
	  else
	    r--;
	}
	if ( i > cd->rows[0]) {
	  // Corrupt data, se original row
	  r = row;
	  break;
	}	  
      }
      row = r;
    }
    for ( int i = 0; i < cd->cols; i++)
      values[i] = cd->y_data[i][row];
      
    *t = cd->x_data[0][row];
  }
  else {
    // Time is a date
    if ( !cd->x_reverse)
      time = cd->x_min_value_axis[0] + x *
	(cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
    else
      time = cd->x_min_value_axis[0] + (200.0 - x) *
	(cd->x_max_value_axis[0] - cd->x_min_value_axis[0]) / 200;
      
    // Approximate row
    for ( int j = 0; j < cd->cols; j++) {
      row = int ((time - cd->x_min_value[j]) / 
		 (cd->x_max_value[j] - cd->x_min_value[j]) *
		 (cd->rows[j] - 1) + 0.5);
      if ( row > cd->rows[j] - 1)
	row = cd->rows[j] - 1;
      else if ( row < 0)
	row = 0;
      else {
	// Find exact row
	double b1, b2;
	int r = row;
	for (int i = 0;; i++) {
	  if ( r == 0) {
	    b2 = (cd->x_data[j][row] + cd->x_data[j][r+1]) / 2;
	    if ( time < b2)
	      break;
	    r++;
	  }
	  else if ( r == cd->rows[j] - 1) {
	    b1 = (cd->x_data[j][r] + cd->x_data[0][r-1]) / 2;
	    if ( time >= b1)
	      break;
	    r--;
	  }
	  else {
	    b1 = (cd->x_data[j][r] + cd->x_data[j][r-1]) / 2;
	    b2 = (cd->x_data[j][r] + cd->x_data[j][r+1]) / 2;
	    if ( b1 <= time && time < b2)
	      break;
	    if ( b1 <= time)
	      r++;
	    else
	      r--;
	  }
	  if ( i > cd->rows[j]) {
	    // Corrupt data, se original row
	    r = row;
	    break;
	  }
	}
	row = r;
      }
      values[j] = cd->y_data[j][row];
      if ( j == 0)
	*t = cd->x_data[j][row];
    }
  }
}

void GeCurve::update_color_theme( int ct)
{
  char color_theme_file[80];
  int sts;

  sprintf( color_theme_file, "pwr_colortheme%d", ct);

  sts = grow_ReadCustomColorFile( grownames_ctx, color_theme_file);
  if ( EVEN(sts)) return;

  sts = grow_ReadCustomColorFile( growcurve_ctx, color_theme_file);
  if ( EVEN(sts)) return;

  sts = grow_ReadCustomColorFile( growaxis_ctx, color_theme_file);
  if ( EVEN(sts)) return;

  grow_SetDefaultColorTheme( color_theme_file);

  color_theme = ct;
}