/*
 * An embeddable Bonobo component to display text/plain.
 *
 * Author:
 *   Nat Friedman (nat@nat.org)
 *
 */
#include <config.h>

#include <bonobo.h>

#undef BONOBO_TEXT_PLAIN_TEST_UI_HANDLER

#ifdef BONOBO_TEXT_PLAIN_TEST_UI_HANDLER
/* XPM */
static char * bolt_xpm[] = {
"10 18 4 1",
" 	c None",
".	c #000000",
"+	c #F2FF00",
"@	c #000000",
"   .......",
"  .++++++.",
"  .+++++@.",
" .+++++@. ",
" .++++@.  ",
"..+++@.   ",
".+++@.... ",
".+++++++. ",
"....+++@. ",
"  .+++@.  ",
"..+++@.   ",
".++@@.    ",
".+++++.   ",
"...++@.   ",
" .++@.    ",
".++@.     ",
".+@.      ",
"...       "};
#endif

/*
 * BonoboControl data
 */
typedef struct {
        BonoboControl     *bonobo_object;

	gchar             *text;
        guint              text_len;

        /* TRUE if data is coming in from ProgressiveDataSink. */
        gboolean           progressive_data;

        /*
         * The total amount of the text data we're expecting from the
         * ProgressiveDataSink interface.
         */
        size_t             total_size;

        GtkWidget         *progress;
        GtkWidget         *text_widget;
	guint              text_widget_text_len;

        gboolean           active;

        /* UI stuff. */
        BonoboUIComponent *uic;
        Bonobo_UIContainer remote_uic;

        /* Internal widgets. */
        GtkWidget         *sw;
        GtkWidget         *table;
} bonobo_object_data_t;

static void
destroy_control (bonobo_object_data_t *bonobo_object_data)
{
	g_free (bonobo_object_data);
}

static void
free_text (bonobo_object_data_t *bonobo_object_data)
{
	 if (bonobo_object_data->text != NULL)
		 g_free (bonobo_object_data->text);
	 bonobo_object_data->text = NULL;
	 bonobo_object_data->text_len = 0;
}

static void
blank_control (bonobo_object_data_t *bonobo_object_data)
{
	if (bonobo_object_data->text_widget_text_len == 0)
		return;

	gtk_text_freeze (GTK_TEXT (bonobo_object_data->text_widget));

	/*
	 * Seek to the end of the text and delete it all.
	 */
	gtk_text_set_point (GTK_TEXT (bonobo_object_data->text_widget),
			    bonobo_object_data->text_widget_text_len);

	gtk_text_backward_delete (GTK_TEXT (bonobo_object_data->text_widget),
				  bonobo_object_data->text_widget_text_len);
		
	
	/*
	 * FIXME: This is a nasty hack.  I can't figure out how
	 * to get a GtkText widget to clear its focus area without
	 * doing something like this, though.
	 */
	gdk_window_clear_area (GTK_TEXT (bonobo_object_data->text_widget)->text_area,
			       0, 0,
			       bonobo_object_data->text_widget->allocation.width,
			       bonobo_object_data->text_widget->allocation.height);

	gtk_text_thaw (GTK_TEXT (bonobo_object_data->text_widget));

#ifndef NO_PROGRESS_METER
	/* Hide the progress meter. */
	gtk_widget_hide (bonobo_object_data->progress);
#endif /* ! NO_PROGRESS_METER */

	bonobo_object_data->text_widget_text_len = 0;
}

static void
update_control (bonobo_object_data_t *bonobo_object_data)
{
	/*
	 * 1. Erase the control.
	 */
	blank_control (bonobo_object_data);

	/*
	 * 2. Draw the new text data into all the control.
	 */
	gtk_text_freeze (GTK_TEXT (bonobo_object_data->text_widget));

	/*
	 * 2.1 Insert the new text.
	 */
	gtk_text_insert (GTK_TEXT (bonobo_object_data->text_widget),
			 NULL, NULL, NULL,
			 bonobo_object_data->text,
			 bonobo_object_data->text_len);

	bonobo_object_data->text_widget_text_len = bonobo_object_data->text_len;

	/*
	 * 2.2 Jump back to the beginning so that the control is
	 * positioned at the beginning of the text.
	 */
	gtk_text_set_point (GTK_TEXT (bonobo_object_data->text_widget), 0);

	gtk_text_thaw (GTK_TEXT (bonobo_object_data->text_widget));
}
		  
static void
progressive_update (bonobo_object_data_t *bonobo_object_data, char *buff, size_t count)
{
	/*
	 * 1. Append the new text data to the bonobo_object's text buffer.
	 */
	bonobo_object_data->text = g_realloc (bonobo_object_data->text,
					      bonobo_object_data->text_len
					      + count);

	memcpy (bonobo_object_data->text + bonobo_object_data->text_len,
		buff, count);

	bonobo_object_data->text_len += count;

	/*
	 * 2. Append the new text data to the end of the control.
	 */
	gtk_text_freeze (GTK_TEXT (bonobo_object_data->text_widget));

	/*
	 * 2.1 Jump to the end and append the new text.
	 */
	gtk_text_set_point (GTK_TEXT (bonobo_object_data->text_widget),
			    bonobo_object_data->text_widget_text_len);

	gtk_text_insert (GTK_TEXT (bonobo_object_data->text_widget),
			 NULL, NULL, NULL,
			 buff, count);

	bonobo_object_data->text_widget_text_len += count;

	/*
	 * 2.2 Jump back to the beginning so that when the
	 * file is done loading, the control is positioned at the
	 * beginning.
	 */
	gtk_text_set_point (GTK_TEXT (bonobo_object_data->text_widget), 0);

#ifndef NO_PROGRESS_METER
	if (bonobo_object_data->total_size != 0) {
		gtk_progress_bar_update (GTK_PROGRESS_BAR (bonobo_object_data->progress),
					 (float) bonobo_object_data->text_len /
					 bonobo_object_data->total_size);
	}

#endif /* ! NO_PROGRESS_METER */

	gtk_text_thaw (GTK_TEXT (bonobo_object_data->text_widget));

}

/*
 * Callbacks attached to signals.
 */
static void
control_system_exception_cb (BonoboControl *control, CORBA_Object corba_object,
			     CORBA_Environment *ev, gpointer data)
{
	g_warning ("Control system exception");
	bonobo_object_unref (BONOBO_OBJECT (control));
}

static void
embeddable_system_exception_cb (BonoboEmbeddable *embeddable, CORBA_Object corba_object,
				CORBA_Environment *ev, gpointer data)
{
	g_warning ("Embeddable system exception");
	bonobo_object_unref (BONOBO_OBJECT (embeddable));
}

static void
bonobo_object_destroy_cb (BonoboControl *bonobo_object,
			  bonobo_object_data_t *bonobo_object_data)
{
	free_text (bonobo_object_data);
	destroy_control (bonobo_object_data);
}

static gboolean
text_inserted_cb (GtkText *text_widget,
		  const gchar *text,
		  gint length,
		  gint *position,
		  bonobo_object_data_t *bonobo_object_data)
{
	if (length == 0)
		return FALSE;

	bonobo_object_data->text_widget_text_len += length;

	/*
	 * Update the BonoboObject's representation of the data being
	 * displayed.
	 */
	bonobo_object_data->text = g_realloc (bonobo_object_data->text,
					      bonobo_object_data->text_len + length);

	memmove (bonobo_object_data->text + *position + length,
		 bonobo_object_data->text + *position,
		 bonobo_object_data->text_len - *position);
	bonobo_object_data->text_len += length;

	memcpy (bonobo_object_data->text + *position, text, length);

	return FALSE;
}

static gboolean
text_deleted_cb (GtkText *text,
		 gint start_pos, gint end_pos,
		 bonobo_object_data_t *bonobo_object_data)
{
	bonobo_object_data->text_len -= end_pos - start_pos;

	/*
	 * Update the BonoboObject's representation of the data being
	 * displayed.
	 */
	memmove (bonobo_object_data->text + start_pos,
		 bonobo_object_data->text + end_pos,
		 bonobo_object_data->text_len - end_pos);
	bonobo_object_data->text_len -= end_pos - start_pos;
	bonobo_object_data->text = (char *) g_realloc (bonobo_object_data->text,
						       bonobo_object_data->text_len);
			
	return FALSE;
}

#ifdef BONOBO_TEXT_PLAIN_TEST_UI_HANDLER
static void
verb_ClearText_cb (BonoboUIComponent *uic, gpointer user_data, const char *cname)
{
	bonobo_object_data_t *bonobo_object_data = user_data;

	free_text (bonobo_object_data);
	blank_control (bonobo_object_data);
}
#endif

static void
create_control_menus (bonobo_object_data_t *bonobo_object_data)
{
	BonoboControl *control = bonobo_object_data->bonobo_object;
	Bonobo_UIContainer remote_uic;

#ifdef BONOBO_TEXT_PLAIN_TEST_UI_HANDLER
	BonoboUINode *parent, *node;
#endif

	/*
	 * Get our container's UIContainer server.
	 */
	remote_uic = bonobo_control_get_remote_ui_container (control);

	/*
	 * We have to deal gracefully with containers
	 * which don't have a UIContainer running.
	 */
	if (remote_uic == CORBA_OBJECT_NIL) {
		g_warning ("No UI container!");
		return;
	}

	/*
	 * Give our BonoboUIComponent object a reference to the
	 * container's UIContainer server.
	 */
	bonobo_ui_component_set_container (bonobo_object_data->uic, remote_uic);

	/*
	 * Unref the UI container we have been passed.
	 */
	bonobo_object_release_unref (remote_uic, NULL);

#ifdef BONOBO_TEXT_PLAIN_TEST_UI_HANDLER
	parent = bonobo_ui_util_new_menu (TRUE, "text-plain", "_text-plain",
					  "text-plain component", NULL);
	node = bonobo_ui_util_new_menu (FALSE, "ClearText", _("_Clear Text"),
					_("Clears the text in the component"),
					"ClearText");
	bonobo_ui_util_xml_set_pix_xpm (node, (const char **) bolt_xpm);
	bonobo_ui_node_add_child (parent, node);

	bonobo_ui_component_add_verb (bonobo_object_data->uic, "ClearText",
				      verb_ClearText_cb, bonobo_object_data);

	bonobo_ui_component_set_tree (bonobo_object_data->uic, "/menu", parent, NULL);
#endif
}

static void
control_activate_cb (BonoboControl *control, gboolean activate, gpointer data)
{
	bonobo_object_data_t *bonobo_object_data = (bonobo_object_data_t *) data;

	/*
	 * If we have been activated, create our menus.  If we have
	 * been deactivated, destroy them.
	 */
	if (activate)
		create_control_menus (bonobo_object_data);
	else
		bonobo_ui_component_unset_container (bonobo_object_data->uic);

	/*
	 * Notify the ControlFrame that we accept to be activated or
	 * deactivated (we are an acquiescent BonoboControl, yes we are).
	 */
	bonobo_control_activate_notify (control, activate);
}

/*
 * Bonobo::PersistStream
 *
 * These two functions implement the Bonobo::PersistStream load and
 * save methods which allow data to be loaded into and out of the
 * BonoboObject.
 */
static void
stream_read (Bonobo_Stream stream, bonobo_object_data_t *bonobo_object_data,
	     CORBA_Environment *ev)
{
	Bonobo_Stream_iobuf *buffer;

	do {
#define READ_CHUNK_SIZE 65536
		Bonobo_Stream_read (stream, READ_CHUNK_SIZE,
				    &buffer, ev);
		if (ev->_major != CORBA_NO_EXCEPTION)
			return;

		bonobo_object_data->text = g_realloc (bonobo_object_data->text,
						      bonobo_object_data->text_len +
						      buffer->_length);

		memcpy (bonobo_object_data->text
			+ bonobo_object_data->text_len,
			buffer->_buffer, buffer->_length);

		bonobo_object_data->text_len += buffer->_length;

		if (buffer->_length <= 0)
			break;

		CORBA_free (buffer);
	} while (1);

	CORBA_free (buffer);
} /* stream_read */

/*
 * This function implements the Bonobo::PersistStream:load method.
 */
static void
pstream_load (BonoboPersistStream *ps, const Bonobo_Stream stream,
	      Bonobo_Persist_ContentType type, void *data,
	      CORBA_Environment *ev)
{
	bonobo_object_data_t *bonobo_object_data = data;

	if (*type && g_strncasecmp (type, "text/", 5) != 0) {	    
		CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
				     ex_Bonobo_Persist_WrongDataType, NULL);
		return;
	}

	/*
	 * 1. Free the old text data and blank the control.
	 */
	free_text (bonobo_object_data);
	blank_control (bonobo_object_data);

	/*
	 * 2. Read the new text data.
	 */
	stream_read (stream, bonobo_object_data, ev);
	if (ev->_major != CORBA_NO_EXCEPTION)
		return;

	/*
	 * 3. Update the display.
	 */
	update_control (bonobo_object_data);

} /* pstream_load */

/*
 * This function implements the Bonobo::PersistStream:save method.
 */
static void
pstream_save (BonoboPersistStream *ps, const Bonobo_Stream stream,
	      Bonobo_Persist_ContentType type, void *data,
	      CORBA_Environment *ev)
{
	bonobo_object_data_t *bonobo_object_data = data;
	Bonobo_Stream_iobuf *buffer;
	size_t pos;

	if (*type && g_strcasecmp (type, "text/plain") != 0) {	    
		CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
				     ex_Bonobo_Persist_WrongDataType, NULL);
		return;
	}

	/*
	 * Write the text data into the stream.
	 *
	 * FIXME: Do we really _have_ to double-buffer the text?
	 */
	buffer = Bonobo_Stream_iobuf__alloc ();

	data = CORBA_sequence_CORBA_octet_allocbuf (bonobo_object_data->text_len);
	memcpy (data, bonobo_object_data->text, bonobo_object_data->text_len);

	buffer->_buffer = data;
	buffer->_length = bonobo_object_data->text_len;

	pos = 0;
	while (pos < bonobo_object_data->text_len) {
		
		Bonobo_Stream_write (stream, buffer, ev);

		if (ev->_major != CORBA_NO_EXCEPTION) {
			CORBA_free (buffer);
			CORBA_free (data);
			return;
		}

		pos += buffer->_length;
	}

	CORBA_free (buffer);
	CORBA_free (data);
} /* pstream_save */

static CORBA_long
pstream_get_max_size (BonoboPersistStream *ps, void *data,
		      CORBA_Environment *ev)
{
	bonobo_object_data_t *bonobo_object_data = data;

	return bonobo_object_data->text_len;
}

static Bonobo_Persist_ContentTypeList *
pstream_get_content_types (BonoboPersistStream *ps, void *closure,
			   CORBA_Environment *ev)
{
	return bonobo_persist_generate_content_types (1, "text/plain");
}

/*
 * Bonobo::ProgressiveDataSink
 *
 * These functions implement the ProgressiveDataSink interface
 * methods, which are used to send a slow stream of data to the widget
 * and have it update its controls progressively.
 */

static int
progressive_start (BonoboProgressiveDataSink *psink, void *data)
{
	bonobo_object_data_t * bonobo_object_data =
		(bonobo_object_data_t *) data;

	bonobo_object_data->progressive_data = TRUE;
	
	free_text (bonobo_object_data);
	blank_control (bonobo_object_data);

#ifndef NO_PROGRESS_METER
	gtk_progress_bar_update (GTK_PROGRESS_BAR (bonobo_object_data->progress), 0.0);
	gtk_widget_show (bonobo_object_data->progress);
#endif /* ! NO_PROGRESS_METER */	

	return 0;
} /* progressive_start */

static int
progressive_end (BonoboProgressiveDataSink *psink, void *data)
{
	bonobo_object_data_t * bonobo_object_data =
		(bonobo_object_data_t *) data;

	bonobo_object_data->progressive_data = FALSE;

#ifndef NO_PROGRESS_METER
	gtk_widget_hide (bonobo_object_data->progress);
#endif /* ! NO_PROGRESS_METER */

	return 0;
} /* progressive_end */

static int
progressive_set_size (BonoboProgressiveDataSink *psink,
		      CORBA_long size, void *data)
{
	bonobo_object_data_t * bonobo_object_data =
		(bonobo_object_data_t *) data;

	bonobo_object_data->total_size = size;

	return 0;
} /* progressive_set_size */

static int
progressive_add_data (BonoboProgressiveDataSink *psink,
		      const Bonobo_ProgressiveDataSink_iobuf *buffer,
		      void *data)
{
	bonobo_object_data_t *bonobo_object_data =
		(bonobo_object_data_t *) data;

	progressive_update (bonobo_object_data, (char *) buffer->_buffer,
			    (size_t) buffer->_length);

	return 0;
} /* progressive_add_data */

static BonoboObject *
generic_factory (BonoboGenericFactory *this, void *data)
{
	BonoboControl *bonobo_object;
	BonoboPersistStream *stream;
	BonoboProgressiveDataSink *psink;
	bonobo_object_data_t *bonobo_object_data;

	bonobo_object_data = g_new0 (bonobo_object_data_t, 1);
	if (!bonobo_object_data)
		return NULL;

	bonobo_object_data->text = NULL;
	bonobo_object_data->text_len = 0;

	bonobo_object_data->text_widget = gtk_text_new (NULL, NULL);

	/*
	 * This table will contain the GtkText widget and, below it,
	 * a progress meter which will appear while progressive data
	 * is being loaded into the BonoboObject.
	 */
	bonobo_object_data->table = gtk_table_new (2, 1, FALSE);

	/*
	 * Make the widget editable and catch the relevant change
	 * signals so that we can update the other controls.
	 */
	gtk_text_set_editable (GTK_TEXT (bonobo_object_data->text_widget), TRUE);

	gtk_signal_connect (GTK_OBJECT (bonobo_object_data->text_widget), "insert_text",
			    GTK_SIGNAL_FUNC (text_inserted_cb), bonobo_object_data);
	gtk_signal_connect (GTK_OBJECT (bonobo_object_data->text_widget), "delete_text",
			    GTK_SIGNAL_FUNC (text_deleted_cb), bonobo_object_data);


	/*
	 * Stuff it into a scrolled window.
	 */
	bonobo_object_data->sw = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (bonobo_object_data->sw),
					GTK_POLICY_AUTOMATIC,
					GTK_POLICY_AUTOMATIC);
	gtk_container_add (GTK_CONTAINER (bonobo_object_data->sw), bonobo_object_data->text_widget);

	gtk_table_attach (GTK_TABLE (bonobo_object_data->table), bonobo_object_data->sw,
			  0, 1, 0, 1,
			  (GTK_EXPAND | GTK_FILL), (GTK_EXPAND | GTK_FILL),
			  0, 0);

	/*
	 * Now create the GtkProgress bar which will be displayed when
	 * data is being loaded into the BonoboObject with the
	 * Bonobo::ProgressiveDataSink interface.
	 */
	bonobo_object_data->progress = gtk_progress_bar_new ();
	gtk_table_attach (GTK_TABLE (bonobo_object_data->table), bonobo_object_data->progress,
			  0, 1, 1, 2,
			  (GTK_EXPAND | GTK_FILL), 0,
			  0, 0);

        gtk_widget_show_all (bonobo_object_data->table);

	if (! bonobo_object_data->progressive_data)
		gtk_widget_hide (bonobo_object_data->progress);

	/*
	 * Now create the BonoboControl object.
	 */
	bonobo_object = bonobo_control_new (bonobo_object_data->table);
	bonobo_object_data->bonobo_object = bonobo_object;
	if (bonobo_object == NULL) {
		gtk_widget_destroy (bonobo_object_data->table);
		g_free (bonobo_object_data);
		return NULL;
	}

	bonobo_object_data->uic = bonobo_control_get_ui_component (bonobo_object);

	gtk_signal_connect (GTK_OBJECT (bonobo_object), "system_exception",
			    GTK_SIGNAL_FUNC (control_system_exception_cb), bonobo_object_data);

	gtk_signal_connect (GTK_OBJECT (bonobo_object), "activate",
			    GTK_SIGNAL_FUNC (control_activate_cb), bonobo_object_data);

	gtk_signal_connect (GTK_OBJECT (bonobo_object), "destroy",
			    GTK_SIGNAL_FUNC (bonobo_object_destroy_cb),
			    bonobo_object_data);

	gtk_signal_connect (GTK_OBJECT (bonobo_object), "system_exception",
			    GTK_SIGNAL_FUNC (embeddable_system_exception_cb),
			    bonobo_object_data);

	bonobo_object_data->bonobo_object = bonobo_object;

	/*
	 * Register the Bonobo::PersistStream interface.
	 */
	stream = bonobo_persist_stream_new (pstream_load, pstream_save,
					    pstream_get_max_size,
					    pstream_get_content_types,
					    bonobo_object_data);
	if (!stream) {
		bonobo_object_unref (BONOBO_OBJECT (bonobo_object));
		g_free (bonobo_object_data);
		return NULL;
	}

	bonobo_object_add_interface (BONOBO_OBJECT (bonobo_object),
				     BONOBO_OBJECT (stream));


	/*
	 * Register the Bonobo::ProgressiveDataSink interface.
	 */
	psink = bonobo_progressive_data_sink_new (progressive_start,
						  progressive_end,
						  progressive_add_data,
						  progressive_set_size,
						  bonobo_object_data);
	if (!psink) {
		bonobo_object_unref (BONOBO_OBJECT (bonobo_object));
		g_free (bonobo_object_data);
		return NULL;
	}

	bonobo_object_add_interface (BONOBO_OBJECT (bonobo_object),
				     BONOBO_OBJECT (psink));

	return BONOBO_OBJECT (bonobo_object);
} /* generic_factory */


BONOBO_OAF_FACTORY ("OAFIID:Bonobo_Sample_Text_Factory",
		    "bonobo-text-plain", VERSION,
		    generic_factory,
		    NULL)


syntax highlighted by Code2HTML, v. 0.9.1