/* gtkedit.c - front end for gtk/gnome version

   Copyright (C) 1996-2000 the Free Software Foundation

   Authors: 1996, 1997 Paul Sheer

   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., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
*/

#define _GTK_EDIT_C

#include <config.h>
#include <gnome.h>
#include <ctype.h>
#include <string.h>
#include <pwd.h>
#include "gdk/gdkkeysyms.h"
#include "gtk/gtkmain.h"
#include "gtk/gtkselection.h"
#include "gtk/gtksignal.h"
#include "edit.h"
#include "mousemark.h"
#include "mad.h"

#define EDIT_BORDER_ROOM         1
#define MIN_EDIT_WIDTH_LINES     20
#define MIN_EDIT_HEIGHT_LINES    10

#define GDK_FONT_XFONT(font) (((GdkFontPrivate*) font)->xfont)

int edit_key_emulation = 0;

static GtkWidgetClass *parent_class = NULL;

WEdit *edit_init (WEdit * edit, int lines, int columns, const char *filename, const char *text, const char *dir, unsigned long text_size);
void edit_destroy_callback (CWidget * w);
int edit_translate_key (unsigned int x_keycode, long x_key, int x_state, int *cmd, int *ch);
void gtk_edit_alloc_colors (GtkEdit *edit, GdkColormap *colormap);
static void gtk_edit_set_position (GtkEditable *editable, gint position);
void edit_mouse_mark (WEdit * edit, XEvent * event, int double_click);
void edit_move_to_prev_col (WEdit * edit, long p);
int edit_load_file_from_filename (WEdit *edit, char *exp);


guchar gtk_edit_font_width_per_char[256];
int gtk_edit_option_text_line_spacing;
int gtk_edit_option_font_ascent;
int gtk_edit_option_font_descent;
int gtk_edit_option_font_mean_width;
int gtk_edit_fixed_font;

static void clear_focus_area (GtkEdit *edit, gint area_x, gint area_y, gint area_width, gint area_height)
{
    return;
}

void gtk_edit_freeze (GtkEdit *edit)
{
    return;
}

void       gtk_edit_insert          (GtkEdit       *edit,
				     GdkFont       *font,
				     GdkColor      *fore,
				     GdkColor      *back,
				     const char    *chars,
				     gint           length)
{
    while (length-- > 0)
	edit_insert (GTK_EDIT (edit)->editor, *chars++);
}

void       gtk_edit_set_editable    (GtkEdit       *text,
				     gint           editable)
{
    return;
}

void       gtk_edit_thaw            (GtkEdit       *text)
{
    return;
}


void gtk_edit_configure_font_dimensions (GtkEdit * edit)
{
    XFontStruct *f;
    XCharStruct s;
    char *p;
    char q[256];
    unsigned char t;
    int i, direction;
    f = GDK_FONT_XFONT (edit->editable.widget.style->font);
    p = _ ("The Quick Brown Fox Jumps Over The Lazy Dog");
    for (i = ' '; i <= '~'; i++)
	q[i - ' '] = i;
    if (XmbTextEscapement (f, "M", 1) == XmbTextEscapement (f, "M", 1))
	gtk_edit_fixed_font = 1;
    else
	gtk_edit_fixed_font = 0;
    XTextExtents (f, q, '~' - ' ', &direction, &gtk_edit_option_font_ascent, &gtk_edit_option_font_descent, &s);
    gtk_edit_option_font_mean_width = XmbTextEscapement (f, p, strlen (p)) / strlen (p);
    for (i = 0; i < 256; i++) {
	t = (unsigned char) i;
	if (i > f->max_char_or_byte2 || i < f->min_char_or_byte2) {
	    gtk_edit_font_width_per_char[i] = 0;
	} else {
	    gtk_edit_font_width_per_char[i] = XmbTextEscapement (f, (char *) &t, 1);
	}
    }
}

void gtk_edit_set_adjustments (GtkEdit * edit,
			       GtkAdjustment * hadj,
			       GtkAdjustment * vadj)
{
    g_return_if_fail (edit != NULL);
    g_return_if_fail (GTK_IS_EDIT (edit));

    if (edit->hadj && (edit->hadj != hadj)) {
	gtk_signal_disconnect_by_data (GTK_OBJECT (edit->hadj), edit);
	gtk_object_unref (GTK_OBJECT (edit->hadj));
    }
    if (edit->vadj && (edit->vadj != vadj)) {
	gtk_signal_disconnect_by_data (GTK_OBJECT (edit->vadj), edit);
	gtk_object_unref (GTK_OBJECT (edit->vadj));
    }
    if (!hadj)
	hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));

    if (!vadj)
	vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));

    if (edit->hadj != hadj) {
	edit->hadj = hadj;
	gtk_object_ref (GTK_OBJECT (edit->hadj));
	gtk_object_sink (GTK_OBJECT (edit->hadj));

#if 0
	gtk_signal_connect (GTK_OBJECT (edit->hadj), "changed",
			    (GtkSignalFunc) gtk_edit_adjustment,
			    edit);
	gtk_signal_connect (GTK_OBJECT (edit->hadj), "value_changed",
			    (GtkSignalFunc) gtk_edit_adjustment,
			    edit);
	gtk_signal_connect (GTK_OBJECT (edit->hadj), "disconnect",
			    (GtkSignalFunc) gtk_edit_disconnect,
			    edit);
#endif
    }
    if (edit->vadj != vadj) {
	edit->vadj = vadj;
	gtk_object_ref (GTK_OBJECT (edit->vadj));
	gtk_object_sink (GTK_OBJECT (edit->vadj));

#if 0
	gtk_signal_connect (GTK_OBJECT (edit->vadj), "changed",
			    (GtkSignalFunc) gtk_edit_adjustment,
			    edit);
	gtk_signal_connect (GTK_OBJECT (edit->vadj), "value_changed",
			    (GtkSignalFunc) gtk_edit_adjustment,
			    edit);
	gtk_signal_connect (GTK_OBJECT (edit->vadj), "disconnect",
			    (GtkSignalFunc) gtk_edit_disconnect,
			    edit);
#endif
    }
}

GtkWidget *gtk_edit_new (GtkAdjustment * hadj,
			 GtkAdjustment * vadj)
{
    GtkEdit *edit;
    edit = gtk_type_new (gtk_edit_get_type ());
    gtk_edit_set_adjustments (edit, hadj, vadj);
    gtk_edit_configure_font_dimensions (edit);
    return GTK_WIDGET (edit);
}

static void gtk_edit_realize (GtkWidget * widget)
{
    GtkEdit *edit;
    GtkEditable *editable;
    GdkWindowAttr attributes;
    GdkColormap *colormap;
    gint attributes_mask;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));

    edit = GTK_EDIT (widget);
    editable = GTK_EDITABLE (widget);
    GTK_WIDGET_SET_FLAGS (edit, GTK_REALIZED);

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual (widget);
    colormap = attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.event_mask = gtk_widget_get_events (widget);
    attributes.event_mask |= (GDK_EXPOSURE_MASK |
			      GDK_BUTTON_PRESS_MASK |
			      GDK_BUTTON_RELEASE_MASK |
			      GDK_BUTTON_MOTION_MASK |
			      GDK_ENTER_NOTIFY_MASK |
			      GDK_LEAVE_NOTIFY_MASK |
			      GDK_KEY_RELEASE_MASK |
			      GDK_KEY_PRESS_MASK);
    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

    widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
    gdk_window_set_user_data (widget->window, edit);

    attributes.x = (widget->style->klass->xthickness + EDIT_BORDER_ROOM);
    attributes.y = (widget->style->klass->ythickness + EDIT_BORDER_ROOM);
    attributes.width = widget->allocation.width - attributes.x * 2;
    attributes.height = widget->allocation.height - attributes.y * 2;

    edit->text_area = gdk_window_new (widget->window, &attributes, attributes_mask);
    gdk_window_set_user_data (edit->text_area, edit);

    widget->style = gtk_style_attach (widget->style, widget->window);

    gtk_edit_alloc_colors (edit, colormap);

    /* Can't call gtk_style_set_background here because it's handled specially */
    gdk_window_set_background (widget->window, &edit->color[1]);
    gdk_window_set_background (edit->text_area, &edit->color[1]);

    edit->gc = gdk_gc_new (edit->text_area);
    gdk_gc_set_exposures (edit->gc, TRUE);
    gdk_gc_set_foreground (edit->gc, &edit->color[26]);
    gdk_gc_set_background (edit->gc, &edit->color[1]);


    gdk_window_show (edit->text_area);

    if (editable->selection_start_pos != editable->selection_end_pos)
	gtk_editable_claim_selection (editable, TRUE, GDK_CURRENT_TIME);

#if 0
    if ((widget->allocation.width > 1) || (widget->allocation.height > 1))
	recompute_geometry (edit);
#endif
}

static void gtk_edit_unrealize (GtkWidget * widget)
{
    GtkEdit *edit;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));

    edit = GTK_EDIT (widget);

    gdk_window_set_user_data (edit->text_area, NULL);
    gdk_window_destroy (edit->text_area);
    edit->text_area = NULL;

    gdk_gc_destroy (edit->gc);
    edit->gc = NULL;

    if (GTK_WIDGET_CLASS (parent_class)->unrealize)
	(*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void gtk_edit_destroy (GtkObject * object)
{
    GtkEdit *edit;

    g_return_if_fail (object != NULL);
    g_return_if_fail (GTK_IS_EDIT (object));

    edit = (GtkEdit *) object;
    if (edit->hadj) {
	gtk_object_unref (GTK_OBJECT (edit->hadj));
	edit->hadj = NULL;
    }
    if (edit->vadj) {
	gtk_object_unref (GTK_OBJECT (edit->vadj));
	edit->vadj = NULL;
    }
    if (edit->timer) {
	gtk_timeout_remove (edit->timer);
	edit->timer = 0;
    }
    if (edit->funcs) {
	free (edit->funcs);
    }
    edit_destroy_callback (edit);

    GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

static void gtk_edit_style_set (GtkWidget * widget,
				GtkStyle * previous_style)
{
    GtkEdit *edit;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));

    edit = GTK_EDIT (widget);
    if (GTK_WIDGET_REALIZED (widget)) {
	gdk_window_set_background (widget->window, &edit->color[1]);
	gdk_window_set_background (edit->text_area, &edit->color[1]);

#if 0
	if ((widget->allocation.width > 1) || (widget->allocation.height > 1))
	    recompute_geometry (edit);
#endif
    }
    if (GTK_WIDGET_DRAWABLE (widget))
	gdk_window_clear (widget->window);
}

static void gtk_edit_draw_focus (GtkWidget * widget)
{
    GtkEdit *edit;
    gint width, height;
    gint x, y;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));

    edit = GTK_EDIT (widget);

    if (GTK_WIDGET_DRAWABLE (widget)) {
	gint ythick = widget->style->klass->ythickness;
	gint xthick = widget->style->klass->xthickness;
	gint xextra = EDIT_BORDER_ROOM;
	gint yextra = EDIT_BORDER_ROOM;

/*      TDEBUG (("in gtk_edit_draw_focus\n")); */

	x = 0;
	y = 0;
	width = widget->allocation.width;
	height = widget->allocation.height;

	if (GTK_WIDGET_HAS_FOCUS (widget)) {
	    x += 1;
	    y += 1;
	    width -= 2;
	    height -= 2;
	    xextra -= 1;
	    yextra -= 1;

	    gdk_draw_rectangle (widget->window,
				widget->style->fg_gc[GTK_STATE_NORMAL],
				FALSE, 0, 0,
				widget->allocation.width - 1,
				widget->allocation.height - 1);
	}
	gtk_draw_shadow (widget->style, widget->window,
			 GTK_STATE_NORMAL, GTK_SHADOW_IN,
			 x, y, width, height);

	x += xthick;
	y += ythick;
	width -= 2 * xthick;
	height -= 2 * ythick;

	if (widget->style->bg_pixmap[GTK_STATE_NORMAL]) {
	    /* top rect */
	    clear_focus_area (edit, x, y, width, yextra);
	    /* left rect */
	    clear_focus_area (edit, x, y + yextra,
			      xextra, y + height - 2 * yextra);
	    /* right rect */
	    clear_focus_area (edit, x + width - xextra, y + yextra,
			      xextra, height - 2 * ythick);
	    /* bottom rect */
	    clear_focus_area (edit, x, x + height - yextra, width, yextra);
	} else if (!GTK_WIDGET_HAS_FOCUS (widget)) {
	    gdk_draw_rectangle (widget->window,
			 widget->style->base_gc[GTK_STATE_NORMAL], FALSE,
				x, y,
				width - 1,
				height - 1);
	}
    }
#if 0
    else {
	TDEBUG (("in gtk_edit_draw_focus (undrawable !!!)\n"));
    }
#endif
}

static void gtk_edit_size_request (GtkWidget * widget,
				   GtkRequisition * requisition)
{
    gint xthickness;
    gint ythickness;
    gint char_height;
    gint char_width;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));
    g_return_if_fail (requisition != NULL);

    xthickness = widget->style->klass->xthickness + EDIT_BORDER_ROOM;
    ythickness = widget->style->klass->ythickness + EDIT_BORDER_ROOM;

    char_height = MIN_EDIT_HEIGHT_LINES * (widget->style->font->ascent +
					   widget->style->font->descent);

    char_width = MIN_EDIT_WIDTH_LINES * (gdk_text_width (widget->style->font,
					    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
							 26)
					 / 26);

    requisition->width = char_width + xthickness * 2;
    requisition->height = char_height + ythickness * 2;
}

static void gtk_edit_size_allocate (GtkWidget * widget,
				    GtkAllocation * allocation)
{
    GtkEdit *edit;
    GtkEditable *editable;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));
    g_return_if_fail (allocation != NULL);

    edit = GTK_EDIT (widget);
    editable = GTK_EDITABLE (widget);

    widget->allocation = *allocation;
    if (GTK_WIDGET_REALIZED (widget)) {
	gdk_window_move_resize (widget->window,
				allocation->x, allocation->y,
				allocation->width, allocation->height);

	gdk_window_move_resize (edit->text_area,
		     widget->style->klass->xthickness + EDIT_BORDER_ROOM,
		     widget->style->klass->ythickness + EDIT_BORDER_ROOM,
	   widget->allocation.width - (widget->style->klass->xthickness +
				       EDIT_BORDER_ROOM) * 2,
	  widget->allocation.height - (widget->style->klass->ythickness +
				       EDIT_BORDER_ROOM) * 2);

#if 0
	recompute_geometry (edit);
#endif
    }
}

static void gtk_edit_draw (GtkWidget * widget,
			   GdkRectangle * area)
{
    GtkEdit *edit;
    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_EDIT (widget));
    g_return_if_fail (area != NULL);

    if (GTK_WIDGET_DRAWABLE (widget)) {
/* convert the gtk expose to a coolwidget expose */
	XExposeEvent xexpose;
	xexpose.x = area->x;
	xexpose.y = area->y;
	xexpose.width = area->width;
	xexpose.height = area->height;
	edit = GTK_EDIT (widget);
	edit_render_expose (edit->editor, &xexpose);
	edit_status (edit->editor);
    }
}

void gtk_edit_set_colors (GtkEdit *win)
{
    edit_set_foreground_colors (
				 color_palette (option_editor_fg_normal),
				   color_palette (option_editor_fg_bold),
				   color_palette (option_editor_fg_italic)
	);
    edit_set_background_colors (
				 color_palette (option_editor_bg_normal),
			       color_palette (option_editor_bg_abnormal),
				 color_palette (option_editor_bg_marked),
			color_palette (option_editor_bg_marked_abnormal),
			     color_palette (option_editor_bg_highlighted)
	);
    edit_set_cursor_color (
			      color_palette (option_editor_fg_cursor)
	);
}

static gint
 gtk_edit_expose (GtkWidget * widget,
		  GdkEventExpose * event)
{
    GtkEdit *edit;
    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GTK_IS_EDIT (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    if (GTK_WIDGET_DRAWABLE (widget)) {
/* convert the gtk expose to a coolwidget expose */
	XExposeEvent xexpose;
	xexpose.x = event->area.x;
	xexpose.y = event->area.y;
	xexpose.width = event->area.width;
	xexpose.height = event->area.height;
	edit = GTK_EDIT (widget);
	gtk_edit_set_colors (edit);
	edit_render_expose (edit->editor, &xexpose);
	edit_status (edit->editor);
    }
    return FALSE;
}

static gint
 gtk_edit_button_press_release (GtkWidget * widget,
				GdkEventButton * event)
{
    int double_click = 0;
    XEvent xevent;
    GtkEdit *edit;
    GtkEditable *editable;
    static GdkAtom ctext_atom = GDK_NONE;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GTK_IS_EDIT (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    if (ctext_atom == GDK_NONE)
	ctext_atom = gdk_atom_intern ("COMPOUND_TEXT", FALSE);

    edit = GTK_EDIT (widget);
    editable = GTK_EDITABLE (widget);

    if (!GTK_WIDGET_HAS_FOCUS (widget))
	if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
	    gtk_widget_grab_focus (widget);

    switch (event->type) {
    case GDK_MOTION_NOTIFY:
	xevent.type = MotionNotify;
	break;
    case GDK_BUTTON_PRESS:
	xevent.type = ButtonPress;
	break;
    case GDK_2BUTTON_PRESS:
	xevent.type = ButtonPress;
	double_click = 1;
	break;
    case GDK_BUTTON_RELEASE:
	xevent.type = ButtonRelease;
	break;
    case GDK_3BUTTON_PRESS:
    default:
	return FALSE;
	break;
    }
    xevent.xbutton.window = (guint32) event->window;
    xevent.xbutton.x = event->x;
    xevent.xbutton.y = event->y;
    xevent.xbutton.state = event->state;

    edit_mouse_mark (edit->editor, &xevent, double_click);
    return FALSE;
}

static gint
 gtk_edit_button_motion (GtkWidget * widget,
			 GdkEventMotion * event)
{
    return gtk_edit_button_press_release (widget, (GdkEventButton *) event);
}

static guint toggle_bit (guint x, guint mask)
{
    unsigned long m = -1;
    if ((x & mask))
	return x & (m - mask);
    else
	return x | mask;
}

int mod_type_key (guint x)
{
    switch ((guint) x) {
    case GDK_Shift_L:
    case GDK_Shift_R:
    case GDK_Control_L:
    case GDK_Control_R:
    case GDK_Caps_Lock:
    case GDK_Shift_Lock:
    case GDK_Meta_L:
    case GDK_Meta_R:
    case GDK_Alt_L:
    case GDK_Alt_R:
    case GDK_Super_L:
    case GDK_Super_R:
    case GDK_Hyper_L:
    case GDK_Hyper_R:
	return 1;
    }
    return 0;
}

/* get a 15 bit "almost unique" key sym that includes keyboard modifier
   info in the top 3 bits */
short key_sym_mod (gint key, gint state)
{
    if (key && !mod_type_key (key)) {
	key = toggle_bit (key, 0x1000 * ((state & GDK_SHIFT_MASK) != 0));
	key = toggle_bit (key, 0x2000 * ((state & GDK_CONTROL_MASK) != 0));
	key = toggle_bit (key, 0x4000 * ((state & GDK_MOD1_MASK) != 0));
	key &= 0x7FFF;
    } else
	key = 0;
    return key;
}

static gint
 gtk_edit_key_press (GtkWidget * widget,
		     GdkEventKey * event)
{
    GtkEdit *edit;
    GtkEditable *editable;
    gint command = 0, insert = -1, r = 0;
    guint key, state;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GTK_IS_EDIT (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    edit = GTK_EDIT (widget);
    editable = GTK_EDITABLE (widget);

    if (!edit_translate_key (0, event->keyval, event->state, &command, &insert)) {
	return FALSE;
    }
    key = event->keyval;
    state = event->state;
    if (!command && insert < 0) {	/* no translation took place, so lets see if we have a macro */
	if ((key == GDK_r || key == GDK_R) && (state & ControlMask)) {
	    command = edit->editor->macro_i < 0 ? CK_Begin_Record_Macro : CK_End_Record_Macro;
	} else {
	    command = key_sym_mod (key, state);
	    if (command > 0)
		command = CK_Macro (command);
	}
    }
    r = edit_execute_key_command (edit->editor, command, insert);
    if (r)
	edit_update_screen (edit->editor);

    return r;
}

/**********************************************************************/
/*                              Widget Crap                           */
/**********************************************************************/

char *home_dir = 0;

static void get_home_dir (void)
{
    if (home_dir)		/* already been set */
	return;
    home_dir = getenv ("HOME");
    if (home_dir)
	if (*home_dir) {
	    home_dir = strdup (home_dir);
	    return;
	}
    home_dir = (getpwuid (geteuid ()))->pw_dir;
    if (home_dir)
	if (*home_dir) {
	    home_dir = strdup (home_dir);
	    return;
	}
    fprintf (stderr, _("gtkedit.c: HOME environment variable not set and no passwd entry - aborting\n"));
    abort ();
}


static void gtk_edit_class_init (GtkEditClass * class)
{
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;
    GtkEditableClass *editable_class;

    object_class = (GtkObjectClass *) class;
    widget_class = (GtkWidgetClass *) class;
    editable_class = (GtkEditableClass *) class;

    parent_class = gtk_type_class (gtk_editable_get_type ());

    object_class->destroy = gtk_edit_destroy;

    widget_class->realize = gtk_edit_realize;
    widget_class->unrealize = gtk_edit_unrealize;
    widget_class->style_set = gtk_edit_style_set;
    widget_class->draw_focus = gtk_edit_draw_focus;
    widget_class->size_request = gtk_edit_size_request;
    widget_class->size_allocate = gtk_edit_size_allocate;
    widget_class->draw = gtk_edit_draw;
    widget_class->expose_event = gtk_edit_expose;
    widget_class->button_press_event = gtk_edit_button_press_release;
    widget_class->button_release_event = gtk_edit_button_press_release;
    widget_class->motion_notify_event = gtk_edit_button_motion;

    widget_class->key_press_event = gtk_edit_key_press;
#if 0
    widget_class->focus_in_event = gtk_edit_focus_in;
    widget_class->focus_out_event = gtk_edit_focus_out;
#endif
    widget_class->focus_in_event = 0;
    widget_class->focus_out_event = 0;

#if 0
    editable_class->insert_text = gtk_edit_insert_text;
    editable_class->delete_text = gtk_edit_delete_text;
    editable_class->update_text = gtk_edit_update_text;
    editable_class->get_chars = gtk_edit_get_chars;
    editable_class->set_selection = gtk_edit_set_selection;
#endif
    editable_class->set_position = gtk_edit_set_position;

    editable_class->insert_text = 0;
    editable_class->delete_text = 0;
    editable_class->update_text = 0;
    editable_class->get_chars = 0;

#if 0
    editable_class->set_position = 0;
#endif

    get_home_dir ();
}

extern struct mouse_funcs edit_mouse_funcs;

static void gtk_edit_init (GtkEdit * edit)
{
    static made_directory = 0;

    GTK_WIDGET_SET_FLAGS (edit, GTK_CAN_FOCUS);

    edit->editor = edit_init (0, 80, 25, 0, "", "/", 0);
    edit->editor->macro_i = -1;
    edit->editor->widget = edit;
    edit->timer = 0;
    edit->menubar = 0;
    edit->status = 0;
    edit->options = 0;
    edit->funcs = mouse_funcs_new (edit->editor, &edit_mouse_funcs);

    gtk_edit_configure_font_dimensions (edit);

    if (!made_directory) {
	mkdir (catstrs (home_dir, EDIT_DIR, 0), 0700);
	made_directory = 1;
    }
    GTK_EDITABLE (edit)->editable = TRUE;
}

guint
gtk_edit_get_type (void)
{
    static guint edit_type = 0;

    if (!edit_type) {
	GtkTypeInfo edit_info =
	{
	    "GtkEdit",
	    sizeof (GtkEdit),
	    sizeof (GtkEditClass),
	    (GtkClassInitFunc) gtk_edit_class_init,
	    (GtkObjectInitFunc) gtk_edit_init,
	    (GtkArgSetFunc) NULL,
	    (GtkArgGetFunc) NULL,
	};

	edit_type = gtk_type_unique (gtk_editable_get_type (), &edit_info);
    }
    return edit_type;
}

#include <libgnomeui/gtkcauldron.h>
#include <libgnomeui/gnome-stock.h>

char *gtk_edit_dialog_get_save_file (guchar * dir, guchar * def, guchar * title)
{
    char *s;
    s = gtk_dialog_cauldron (
				title, GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_GRAB,
	    " ( ( (Filename:)d | %Fgxf )f )xf / ( %Bxfgrq || %Bxfgq )f ",
				&def, "filename", title,
				GNOME_STOCK_BUTTON_OK,
				GNOME_STOCK_BUTTON_CANCEL
	);
    if (s == GTK_CAULDRON_ESCAPE || !s || s == GNOME_STOCK_BUTTON_CANCEL)
	return 0;
    return def;
}

char *gtk_edit_dialog_get_load_file (guchar * dir, guchar * def, guchar * title)
{
    char *s;
    s = gtk_dialog_cauldron (
				title, GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_GRAB,
	    " ( ( (Filename:)d | %Fgxf )f )xf / ( %Bxfgrq || %Bxfgq )f ",
				&def, "filename", title,
				GNOME_STOCK_BUTTON_OK,
				GNOME_STOCK_BUTTON_CANCEL
	);
    if (s == GTK_CAULDRON_ESCAPE || !s || s == GNOME_STOCK_BUTTON_CANCEL)
	return 0;
    return def;
}

void gtk_edit_dialog_message (guchar * heading, char *fmt,...)
{
    gchar s[8192];
    va_list ap;
    va_start (ap, fmt);
    vsprintf (s, fmt, ap);
    va_end (ap);
    gtk_dialog_cauldron (
			    heading, GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_GRAB,
			    " [ ( %Ld )xf ]xf / ( %Bxfgq )f ",
			    s,
			    GNOME_STOCK_BUTTON_CANCEL
	);
    return;
}

int gtk_edit_dialog_query (guchar * heading, guchar * first,...)
{
    char *buttons[16];
    char s[1024], *r;
    int n;
    va_list ap;
    va_start (ap, first);
    n = 0;
    while ((buttons[n++] = va_arg (ap, char *)) && n < 15);
    va_end (ap);
    buttons[n] = 0;
    strcpy (s, " [ ( %Lxf )xf ]xf / ( ");
    n = 0;
    while (buttons[n]) {
	strcat (s, " %Bqxf ");
	if (!buttons[n])
	    break;
	strcat (s, " ||");
	n++;
    }
    strcat (s, " )f");
    r = gtk_dialog_cauldron (heading, GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_IGNOREENTER | GTK_CAULDRON_GRAB, s, first,
			  buttons[0], buttons[1], buttons[2], buttons[3],
			  buttons[4], buttons[5], buttons[6], buttons[7],
			buttons[8], buttons[9], buttons[10], buttons[11],
		      buttons[12], buttons[13], buttons[14], buttons[15]
	);
    n = 0;
    if (r == GTK_CAULDRON_ESCAPE || !r || r == GNOME_STOCK_BUTTON_CANCEL)
	return -1;
    while (buttons[n]) {
	if (!strcmp (buttons[n], r))
	    return n;
	n++;
    }
    return -1;
}

void gtk_edit_dialog_error (guchar * heading, char *fmt, ...)
{
    gchar s[8192];
    va_list ap;
    va_start (ap, fmt);
    vsprintf (s, fmt, ap);
    va_end (ap);
    gtk_dialog_cauldron (
			    heading, GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_GRAB,
			    " [ ( %Ld )xf ]xf / ( %Bxfgq )f",
			    s,
			    GNOME_STOCK_BUTTON_CANCEL
	);
    return;
}



struct color_matrix_struct {
    unsigned int R, G, B;
} color_matrix[27] =
{
    {0, 0, 0},
    {0, 0, 128},
    {0, 0, 255},
    {0, 139, 0},
    {0, 139, 139},
    {0, 154, 205},
    {0, 255, 0},
    {0, 250, 154},
    {0, 255, 255},
    {139, 37, 0},
    {139, 0, 139},
    {125, 38, 205},
    {139, 117, 0},
    {127, 127, 127},
    {123, 104, 238},
    {127, 255, 0},
    {135, 206, 235},
    {127, 255, 212},
    {238, 0, 0},
    {238, 18, 137},
    {238, 0, 238},
    {205, 102, 0},
    {248, 183, 183},
    {224, 102, 255},
    {238, 238, 0},
    {238, 230, 133},
    {248, 248, 255}
};

void gtk_edit_alloc_colors (GtkEdit *edit, GdkColormap *colormap)
{
    int i;
    for (i = 0; i < 27; i++) {
	edit->color[i].red = (gushort) color_matrix[i].R << 8;
	edit->color[i].green = (gushort) color_matrix[i].G << 8;
	edit->color[i].blue = (gushort) color_matrix[i].B << 8;
	if (!gdk_color_alloc (colormap, &edit->color[i]))
	    g_warning ("cannot allocate color");
    }
    edit->color_last_pixel = 27;
}

int allocate_color (WEdit *edit, gchar *color)
{
    GtkEdit *win;
    win = (GtkEdit *) edit->widget;
    if (!color)
	return NO_COLOR;
    if (*color >= '0' && *color <= '9') {
	return atoi (color);
    } else {
	int i;
	GdkColor c;
	if (!color)
	    return NO_COLOR;
	if (!gdk_color_parse (color, &c))
	    return NO_COLOR;
	if (!gdk_color_alloc (gtk_widget_get_colormap(GTK_WIDGET (edit->widget)), &c))
	    return NO_COLOR;
	for (i = 0; i < (GTK_EDIT (edit->widget))->color_last_pixel; i++)
	    if (color_palette (i) == c.pixel)
		return i;
	GTK_EDIT (edit->widget)->color[(GTK_EDIT (edit->widget))->color_last_pixel].pixel = c.pixel;
	return (GTK_EDIT (edit->widget))->color_last_pixel++;
    }
}

static void gtk_edit_set_position (GtkEditable * editable, gint position)
{
    WEdit *edit;
    edit = GTK_EDIT(editable)->editor;
    edit_cursor_move (edit, position - edit->curs1);
    edit_move_to_prev_col (edit, 0);
    edit->force |= REDRAW_PAGE;
    edit->search_start = 0;
    edit_update_curs_row (edit);
}


/* returns 1 on error */
gint gtk_edit_load_file_from_filename (GtkWidget * edit, const gchar * filename)
{
    return edit_load_file_from_filename (GTK_EDIT (edit)->editor, (char *) filename);
}

void gtk_edit_set_top_line (GtkWidget * e, int line)
{
    WEdit *edit;
    edit = GTK_EDIT (e)->editor;
    edit_move_display (edit, line - edit->num_widget_lines / 2 - 1);
    edit->force |= REDRAW_COMPLETELY;
}

void gtk_edit_set_cursor_line (GtkWidget * e, int line)
{
    WEdit *edit;
    edit = GTK_EDIT (e)->editor;
    edit_move_to_line (edit, line - 1);
    edit->force |= REDRAW_COMPLETELY;
}

void about_cb (GtkWidget * widget, void *data)
{
    gtk_dialog_cauldron ("About", GTK_CAULDRON_TOPLEVEL | GTK_CAULDRON_GRAB, " [ (Mcedit - an editor for the midnight commander\n\
ported from Cooledit - a user friendly text editor for the X Window System.)xf ]xf / ( %Bgqxf )f ", GNOME_STOCK_BUTTON_OK);
    return;
}

void gtk_edit_command (GtkEdit * edit, gint command)
{
    int r;
    gtk_widget_grab_focus (GTK_WIDGET (edit));
    r = edit_execute_key_command (edit->editor, command, -1);
    if (r)
	edit_update_screen (edit->editor);
}

void gtk_edit_quit (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Exit); }

void gtk_edit_load_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Load); }
void gtk_edit_new_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_New); }
void gtk_edit_save_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Save); }
void gtk_edit_save_as_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Save_As); }
void gtk_edit_insert_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Insert_File); }
void gtk_edit_copy_to_file (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Save_Block); }

void gtk_edit_clip_cut (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_XCut); }
void gtk_edit_clip_copy (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_XStore); }
void gtk_edit_clip_paste (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_XPaste); }

void gtk_edit_toggle_mark (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Mark); }
void gtk_edit_search (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Find); }
void gtk_edit_search_again (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Find_Again); }
void gtk_edit_replace (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Replace); }
void gtk_edit_copy (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Copy); }
void gtk_edit_move (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Move); }
void gtk_edit_delete (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Remove); }
void gtk_edit_undo (GtkEdit * widget, void *data) { GtkEdit *edit = (GtkEdit *) data ; gtk_edit_command (edit, CK_Undo); }

#if 0
struct _GnomeUIInfo {
    GnomeUIInfoType type;
    gchar *label;
    gchar *hint;		/* For toolbar items, the tooltip. For menu items, the status bar message */

    /* For an item, toggleitem, or radioitem, procedure to call when activated.    
       For a subtree, point to the GnomeUIInfo array for that subtree.
       For a radioitem lead entry, point to the GnomeUIInfo array for
       the radio item group.  For the radioitem array, procedure to
       call when activated. For a help item, specifies the help node to load
       (or NULL for main prog's name) 
       For builder data, point to the GnomeUIBuilderData structure for the following items */
    gpointer moreinfo;
    gpointer user_data;
    gpointer unused_data;
    GnomeUIPixmapType pixmap_type;
    /* Either 
     * a pointer to the char for the pixmap (GNOME_APP_PIXMAP_DATA),
     * a char* for the filename (GNOME_APP_PIXMAP_FILENAME),
     * or a char* for the stock pixmap name (GNOME_APP_PIXMAP_STOCK).
     */
    gpointer pixmap_info;
    guint accelerator_key;	/* Accelerator key... Set to 0 to ignore */
    GdkModifierType ac_mods;	/* An OR of the masks for the accelerator */
    GtkWidget *widget;		/* Filled in by gnome_app_create* */
};

#endif

typedef struct _TbItems TbItems;
struct _TbItems {
	char *key, *text, *tooltip, *icon;
	void (*cb) (GtkEdit *, void *);
	GtkWidget *widget; /* will be filled in */
};

#define TB_PROP 7

static TbItems tb_items[] =
{
    {"F1", "Help", "Interactive help browser", GNOME_STOCK_MENU_BLANK, 0, NULL},
    {"F2", "Save", "Save to current file name", GNOME_STOCK_MENU_SAVE, gtk_edit_save_file, NULL},
    {"F3", "Mark", "Toggle In/Off invisible marker to highlight text", GNOME_STOCK_MENU_BLANK, gtk_edit_toggle_mark, NULL},
    {"F4", "Replc", "Find and replace strings/regular expressions", GNOME_STOCK_MENU_SRCHRPL, gtk_edit_replace, NULL},
    {"F5", "Copy", "Copy highlighted block to cursor postition", GNOME_STOCK_MENU_COPY, gtk_edit_copy, NULL},

    {"F6", "Move", "Copy highlighted block to cursor postition", GNOME_STOCK_MENU_BLANK, gtk_edit_move, NULL},
    {"F7", "Find", "Find strings/regular expressions", GNOME_STOCK_MENU_SEARCH, gtk_edit_search, NULL},
    {"F8", "Dlete", "Delete highlighted text", GNOME_STOCK_MENU_BLANK, gtk_edit_delete, NULL},
    {"F9", "Menu", "Pull down menu", GNOME_STOCK_MENU_BLANK, /* gtk_edit_menu*/ 0, NULL},
    {"F10", "Quit", "Exit editor", GNOME_STOCK_MENU_QUIT, gtk_edit_quit, NULL},
    {0, 0, 0, 0, 0, 0}
};

static GtkWidget *create_toolbar (GtkWidget * window, GtkEdit * edit)
{
    GtkWidget *toolbar;
    TbItems *t;
    toolbar = gtk_toolbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH);
    for (t = &tb_items[0]; t->text; t++) {
	t->widget = gtk_toolbar_append_item (GTK_TOOLBAR (toolbar),
					     t->text,
					     t->tooltip,
					     0,
			     gnome_stock_pixmap_widget (window, t->icon),
					     t->cb,
					     t->cb ? edit : 0);
    }
    return toolbar;
}

/* returns 1 on error */
int edit (const char *file, int line)
{
    GtkWidget *app;
    GtkWidget *edit, *statusbar;

    edit = gtk_edit_new (NULL, NULL);
    app = gnome_app_new ("mcedit", (char *) (file ? file : "Mcedit"));

    {
	GnomeUIInfo file_menu[] =
	{
	    {
		GNOME_APP_UI_ITEM, N_ ("Open/Load"), N_ ("Load a different/new file"), gtk_edit_load_file, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_OPEN, 'O', GDK_CONTROL_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("New"), N_ ("Clear the edit buffer"), gtk_edit_new_file, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_NEW, 'N', GDK_CONTROL_MASK, 0
	    },
	    GNOMEUIINFO_SEPARATOR,
	    {
		GNOME_APP_UI_ITEM, N_ ("Save"), N_ ("Save the current edit buffer to filename"), gtk_edit_save_file, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_SAVE, 'S', GDK_CONTROL_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Save As"), N_ ("Save the current edit buffer as filename"), gtk_edit_save_as_file, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_SAVE_AS, 'A', GDK_CONTROL_MASK, 0
	    },
	    GNOMEUIINFO_SEPARATOR,
	    {
		GNOME_APP_UI_ITEM, N_ ("Insert File"), N_ ("Insert text from a file"), gtk_edit_insert_file, edit, 0, 0, 0, 'I', GDK_CONTROL_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Copy to file"), N_ ("copy a block to a file"), gtk_edit_copy_to_file, edit, 0, 0, 0, 'C', GDK_CONTROL_MASK, 0
	    },
	    GNOMEUIINFO_SEPARATOR,
	    {
		GNOME_APP_UI_ITEM, N_ ("Exit"), N_ ("Quit editor"), gtk_edit_quit, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_QUIT, 'Q', GDK_CONTROL_MASK, NULL
	    },
	    GNOMEUIINFO_END
	};

	GnomeUIInfo edit_menu[] =
	{
	    {
		GNOME_APP_UI_ITEM, N_ ("Copy"), N_ ("Copy selection to clipboard"), gtk_edit_clip_copy, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_COPY, 'C', GDK_CONTROL_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Cut"), N_ ("Cut selection to clipboard"), gtk_edit_clip_cut, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_CUT, 'X', GDK_CONTROL_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Paste"), N_ ("Paste clipboard"), gtk_edit_clip_paste, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_PASTE, 'V', GDK_CONTROL_MASK, 0
	    },
	    GNOMEUIINFO_SEPARATOR,
	    {
		GNOME_APP_UI_ITEM, N_ ("Undo"), N_ ("Go back in time one key press"), gtk_edit_undo, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_PASTE, 'U', GDK_CONTROL_MASK, 0
	    },
	    GNOMEUIINFO_END
	};

	GnomeUIInfo search_menu[] =
	{
	    {
		GNOME_APP_UI_ITEM, N_ ("Find"), N_ ("Find string/regular expression"), gtk_edit_search, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_OPEN, 'F', GDK_MOD1_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Find again"), N_ ("Repeat most recent search"), gtk_edit_search_again, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_NEW, 'A', GDK_MOD1_MASK, 0
	    },
	    {
		GNOME_APP_UI_ITEM, N_ ("Search/Replace"), N_ ("Find and replace text/regular expressions"), gtk_edit_replace, edit, 0, GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_SAVE, 'R', GDK_MOD1_MASK, 0
	    },
	    GNOMEUIINFO_END
	};

	GnomeUIInfo help_menu[] =
	{
	    {
		GNOME_APP_UI_ITEM,
		N_ ("About..."), N_ ("Info about GNOME hello"),
		about_cb, NULL, NULL,
		GNOME_APP_PIXMAP_STOCK, GNOME_STOCK_MENU_ABOUT,
		0, (GdkModifierType) 0, NULL
	    },
#if 0
	    GNOMEUIINFO_SEPARATOR,
	    GNOMEUIINFO_HELP ("hello"),
#endif
	    GNOMEUIINFO_END
	};

	GnomeUIInfo main_menu[] =
	{
	    GNOMEUIINFO_SUBTREE (N_ ("File"), file_menu),
	    GNOMEUIINFO_SUBTREE (N_ ("Edit"), edit_menu),
	    GNOMEUIINFO_SUBTREE (N_ ("Search/Replace"), search_menu),
	    GNOMEUIINFO_SUBTREE (N_ ("Help"), help_menu),
	    GNOMEUIINFO_END
	};

	gtk_widget_realize (app);
	statusbar = gtk_label_new (file ? file : "<no-filename>");
	gtk_widget_set_usize (app, 400, 400);
	gnome_app_create_menus (GNOME_APP (app), main_menu);
	gnome_app_set_contents (GNOME_APP (app), edit);
	gnome_app_set_statusbar (GNOME_APP (app), GTK_WIDGET (statusbar));
	GTK_EDIT (edit)->menubar = GNOME_APP (app)->menubar;
	GTK_EDIT (edit)->status = statusbar;
	gnome_app_set_toolbar(GNOME_APP (app), GTK_TOOLBAR(create_toolbar(app, GTK_EDIT (edit))));
	GTK_EDIT(edit)->destroy_me = gtk_widget_destroy;
	GTK_EDIT(edit)->destroy_me_user_data = app;

	gtk_widget_show (edit);
	gtk_widget_realize (edit);
	if (file)
	    if (*file)
		if (gtk_edit_load_file_from_filename (edit, file)) {
		    gtk_widget_destroy (app);
		    return 1;
		}
	gtk_edit_set_cursor_line (edit, line);
	gtk_widget_show (app);
	gtk_widget_grab_focus (edit);
    }
    return 0;
}
