/** \file single.c
 * Single line entry field for text
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis, 2012 Martin Fischer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define GTK_DISABLE_SINGLE_INCLUDES
#define GDK_DISABLE_DEPRECATED
// #define GTK_DISABLE_DEPRECATED
#define GSEAL_ENABLE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <gtk/gtk.h>
#include <glib-object.h>

#include "gtkint.h"


/*
 *****************************************************************************
 *
 * Text Boxes
 *
 *****************************************************************************
 */

struct wString_t {
	WOBJ_COMMON
	char *valueP;			/**< pointer to result buffer */
	wIndex_t valueL;	 	/**< maximum length */
	wStringCallBack_p action;  	/**< callback for changes */
	wBool_t notice_activate; /** if flag set to observe enter key **/
	wBool_t enter_pressed;	/**< flag if enter was pressed */
	wBool_t hasSignal;		/** needs signal to be suppressed */
	int count;				/** number of 100ms since last entry **/
};

/**
 * Set the string value in a string entry field
 *
 * \param b 	IN widget to be updated
 * \param arg 	IN new string value
 * \return
 */

void wStringSetValue(
        wString_p b,
        const char *arg)
{
	if (b->widget == NULL) {
		abort();
	}

	// the contents should not be changed programatically while
	// the user is editing it
	if( (b->option&BO_IGNFOCUS) || !(gtk_widget_has_focus(b->widget))) {
		if (b->hasSignal) {
			gtk_signal_handler_block_by_data(GTK_OBJECT(b->widget), b);
		}
		gtk_entry_set_text(GTK_ENTRY(b->widget), arg);
		if (b->hasSignal) {
			gtk_signal_handler_unblock_by_data(GTK_OBJECT(b->widget), b);
		}
	}
}

/**
 * Set the width of the entry field
 *
 * \param b 	IN widget to be updated
 * \param w 	IN new width
 * \return
 */

void wStringSetWidth(
        wString_p b,
        wWinPix_t w)
{
	gtk_widget_set_size_request(b->widget, w, -1);
	b->w = w;
}

/**
 * Return the entered value
 *
 * \param b IN entry field
 * \return   the entered text
 */

const char *wStringGetValue(
        wString_p b)
{
	if ( !b->widget ) {
		abort();
	}

	return gtk_entry_get_text(GTK_ENTRY(b->widget));
}


/**
 * Do the current active string's action when a button was pushed
 * Used to validate input
 */
static wString_p stringControl = NULL;
void wlibStringUpdate()
{
	if ( stringControl && stringControl->action ) {
		stringControl->action( wStringGetValue(stringControl), stringControl->data );
		stringControl = NULL;
	}
}


/**
 * Signal handler for 'activate' signal: enter pressed - callback with the current value and then
 * select the whole default value
 *
 * \param widget 	IN the edit field
 * \param b 		IN the widget data structure
 * \return
 */

static gboolean stringActivated(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringActivated: %s\n", b->labelStr );
	}
	const char *s;
	const char * output = "\n";

	if ( !b ) {
		return( FALSE );
	}

	s = wStringGetValue(b);

	if (b->valueP) {
		strcpy(b->valueP, s);
	}

	if (b->action) {
		b->enter_pressed = TRUE;
		b->action( output, b->data);
	}

	// select the complete default value to make editing it easier
	gtk_editable_select_region( GTK_EDITABLE( widget ), 0, -1 );
	return( TRUE );
}

static gboolean stringExposed(GtkWidget* widget, GdkEventExpose * event,
                              gpointer g )
{
	wControl_p b = (wControl_p)g;
	return wControlExpose(widget,event,b);
}


/**
 * Signal handler for changes in an entry field
 *
 * \param widget 		IN
 * \param entry field 	IN
 * \return
 */

static int stringChanged(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringChanged: %s\n", b->labelStr);
	}
	stringControl = b;
	return FALSE;
}

static int stringPreeditChanged(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringPreeditChanged: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringFocusOutEvent(
        GtkEntry *widget,
        GdkEvent * event,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringFocusOut: %s\n", b->labelStr );
	}
	if (b->action) {
		const char *s;
		s = gtk_entry_get_text(GTK_ENTRY(b->widget));
		b->action(s, b->data);
	}
	gtk_editable_select_region( GTK_EDITABLE( widget ), 0, 0 );
	return FALSE;
}
static int stringFocusInEvent(
        GtkEntry *widget,
        GdkEvent * event,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringFocusIn: %s\n", b->labelStr );
	}
	stringControl = b;
	return FALSE;
}
static int stringLeaveNotifyEvent(
        GtkEntry *widget,
        GdkEvent * event,
        wString_p b)
{
	if ( debugWindow >= 3 ) {
		printf( "stringLeaveNotfyEvent: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringEventAfter(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 3 ) {
		printf( "stringEventAfter: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringEvent(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 3 ) {
		printf( "stringEvent: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringKeyPressEvent(
        GtkEntry *widget,
        GdkEvent * event,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringKeyPressEvent: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringStateChanged(
        GtkEntry *widget,
        int state,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringStateChanged: %s\n", b->labelStr );
	}
	return FALSE;
}
static int stringActivate(
        GtkEntry *widget,
        wString_p b)
{
	if ( debugWindow >= 1 ) {
		printf( "stringActivate: %s\n", b->labelStr );
	}
	return stringChanged( widget, b );
}
/**
 * Create a single line entry field for a string value
 *
 * \param 	parent	IN	parent widget
 * \param 	x		IN	x position
 * \param 	y		IN	y position
 * \param 	helpStr	IN	help anchor
 * \param 	labelStr IN label
 * \param	option	IN	option (supported BO_READONLY )
 * \param	width	IN	width of entry field
 * \param	valueP	IN	default value
 * \param	valueL	IN 	maximum length of entry
 * \param	action	IN	application callback function
 * \param 	data	IN	application data
 * \return  the created widget
 */

wString_p wStringCreate(
        wWin_p	parent,
        wWinPix_t	x,
        wWinPix_t	y,
        const char 	 *helpStr,
        const char	 *labelStr,
        long	option,
        wWinPix_t	width,
        char	*valueP,
        wIndex_t valueL,
        wStringCallBack_p action,
        void 	*data)
{
	wString_p b;

	// create and initialize the widget
	b = (wString_p)wlibAlloc(parent, B_TEXT, x, y, labelStr, sizeof *b, data);
	b->valueP = valueP;
	b->action = action;
	b->option = option;
	b->valueL = valueL;
	b->hasSignal = 0;
	wlibComputePos((wControl_p)b);

	// create the gtk entry field and set maximum length if desired
	b->widget = (GtkWidget *)gtk_entry_new();
	if (b->widget == NULL) { abort(); }

	if( valueL ) {
		gtk_entry_set_max_length( GTK_ENTRY( b->widget ), valueL );
	}

	// it is assumed that the parent is a fixed layout widget and the entry can
	// be placed at a specific position
	gtk_fixed_put(GTK_FIXED(parent->widget), b->widget, b->realX, b->realY);

	// set minimum size for widget
	if (width) {
		gtk_widget_set_size_request(b->widget, width, -1);
	}

	// get the resulting size
	wlibControlGetSize((wControl_p)b);

	// if desired, place a label in front of the created widget
	if (labelStr) {
		b->labelW = wlibAddLabel((wControl_p)b, labelStr);
	}

	if (option & BO_READONLY) {
		gtk_editable_set_editable(GTK_EDITABLE(b->widget), FALSE);
	}

	// set the default text	and select it to make replacing it easier
	if (b->valueP) {
		wStringSetValue(b, b->valueP);
		// select the text only if text is editable
	}

	// show
	gtk_widget_show(b->widget);

	// add the new widget to the list of created widgets
	wlibAddButton((wControl_p)b);

	// link into help
	wlibAddHelpString(b->widget, helpStr);

	g_signal_connect(GTK_OBJECT(b->widget), "changed", G_CALLBACK(stringChanged),
	                 b);
	g_signal_connect(GTK_OBJECT(b->widget), "preedit-changed",
	                 G_CALLBACK(stringPreeditChanged), b);
	g_signal_connect(GTK_OBJECT(b->widget), "focus-out-event",
	                 G_CALLBACK(stringFocusOutEvent), b);
	g_signal_connect(GTK_OBJECT(b->widget), "focus-in-event",
	                 G_CALLBACK(stringFocusInEvent), b);
	g_signal_connect(GTK_OBJECT(b->widget), "leave-notify-event",
	                 G_CALLBACK(stringLeaveNotifyEvent), b);
	g_signal_connect(GTK_OBJECT(b->widget), "event", G_CALLBACK(stringEvent), b);
	g_signal_connect(GTK_OBJECT(b->widget), "event-after",
	                 G_CALLBACK(stringEventAfter), b);
	g_signal_connect(GTK_OBJECT(b->widget), "key-press-event",
	                 G_CALLBACK(stringKeyPressEvent), b);
	g_signal_connect(GTK_OBJECT(b->widget), "state-changed",
	                 G_CALLBACK(stringStateChanged), b);
	g_signal_connect(GTK_OBJECT(b->widget), "activate", G_CALLBACK(stringActivate),
	                 b);

	//if (option&BO_ENTER)
	g_signal_connect(GTK_OBJECT(b->widget), "activate", G_CALLBACK(stringActivated),
	                 b);
	b->hasSignal = 1;
	g_signal_connect_after(GTK_OBJECT(b->widget), "expose-event",
	                       G_CALLBACK(stringExposed), b);

	// set the default text	and select it to make replacing it easier
	if (b->valueP) {
		wStringSetValue(b, b->valueP);
		// select the text only if text is editable
	}

	gtk_widget_add_events( b->widget, GDK_FOCUS_CHANGE_MASK );

	return b;
}
