Rev 13974 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R : A Computer Langage for Statistical Data Analysis* Copyright (C) 1998-1999 Lyndon Drake* and the R Development Core Team** 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*/#include <gdk/gdkkeysyms.h>#include <stdio.h>#include <string.h>#include "gtkconsole.h"enum {CONSOLE_LINE_READY,CONSOLE_CHAR_READY,CONSOLE_INPUT_ENABLED,CONSOLE_INPUT_DISABLED,LAST_SIGNAL};/* widget stuff */static void gtk_console_class_init (GtkConsoleClass *klass);static void gtk_console_init (GtkConsole *console);static void gtk_console_destroy (GtkObject *object);/* events */static gint gtk_console_key_press (GtkWidget *widget,GdkEventKey *event);static void gtk_console_insert_text (GtkConsole *console, const gchar *new_text, gint new_text_length, gint *position);static void gtk_console_delete_text (GtkConsole *console, gint start_pos, gint end_pos);static void gtk_console_changed_post (GtkConsole *console);static GtkWidgetClass *parent_class = NULL;static guint console_signals[LAST_SIGNAL] = { 0 };/* ********************* ** Basic Class Functions ** ********************* */guintgtk_console_get_type (){static guint console_type = 0;if (!console_type){GtkTypeInfo console_info ={"GtkConsole",sizeof (GtkConsole),sizeof (GtkConsoleClass),(GtkClassInitFunc) gtk_console_class_init,(GtkObjectInitFunc) gtk_console_init,(GtkArgSetFunc) NULL,(GtkArgGetFunc) NULL,};console_type = gtk_type_unique (gtk_text_get_type (), &console_info);}return console_type;}static voidgtk_console_class_init (GtkConsoleClass *klass){GtkObjectClass *object_class;object_class = (GtkObjectClass*) klass;/* init local data */parent_class = gtk_type_class (gtk_text_get_type ());/* New signals */console_signals[CONSOLE_LINE_READY] =gtk_signal_new ("console_line_ready",GTK_RUN_LAST,object_class->type,GTK_SIGNAL_OFFSET (GtkConsoleClass, console_char_ready),gtk_marshal_NONE__NONE,GTK_TYPE_NONE, 0);console_signals[CONSOLE_CHAR_READY] =gtk_signal_new ("console_char_ready",GTK_RUN_LAST,object_class->type,GTK_SIGNAL_OFFSET (GtkConsoleClass, console_line_ready),gtk_marshal_NONE__NONE,GTK_TYPE_NONE, 0);console_signals[CONSOLE_INPUT_ENABLED] =gtk_signal_new ("console_input_enabled",GTK_RUN_LAST,object_class->type,GTK_SIGNAL_OFFSET (GtkConsoleClass, console_input_enabled),gtk_marshal_NONE__NONE,GTK_TYPE_NONE, 0);console_signals[CONSOLE_INPUT_DISABLED] =gtk_signal_new ("console_input_disabled",GTK_RUN_LAST,object_class->type,GTK_SIGNAL_OFFSET (GtkConsoleClass, console_input_disabled),gtk_marshal_NONE__NONE,GTK_TYPE_NONE, 0);gtk_object_class_add_signals(object_class, console_signals, LAST_SIGNAL);/* setup signals */object_class->destroy = gtk_console_destroy;klass->console_char_ready = NULL;klass->console_line_ready = NULL;klass->console_input_enabled = NULL;klass->console_input_disabled = NULL;}static voidgtk_console_init (GtkConsole *console){/* Initialise variables */console -> history = NULL;console -> history_num_items = 0;console -> history_cur = NULL;console -> input_enabled = FALSE;console -> input_start_index = FALSE;console -> line_available = FALSE;console -> buffer_type = CONSOLE_BUF_NONE;console -> buffer_index = 0;/* to keep track of changes*/gtk_signal_connect (GTK_OBJECT (console), "insert_text",GTK_SIGNAL_FUNC (gtk_console_insert_text),NULL);gtk_signal_connect (GTK_OBJECT (console), "delete_text",GTK_SIGNAL_FUNC (gtk_console_delete_text),NULL);gtk_signal_connect (GTK_OBJECT (console), "changed",GTK_SIGNAL_FUNC (gtk_console_changed_post),NULL);gtk_signal_connect (GTK_OBJECT (console), "key_press_event",GTK_SIGNAL_FUNC (gtk_console_key_press),NULL);}GtkWidget*gtk_console_new (GtkAdjustment *hadj,GtkAdjustment *vadj){GtkConsole *console;console = gtk_type_new (gtk_console_get_type ());gtk_text_set_adjustments (GTK_TEXT (console), hadj, vadj);return GTK_WIDGET (console);}static voidgtk_console_destroy (GtkObject *object){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));GTK_OBJECT_CLASS(parent_class)->destroy (object);}/* ************************************** ** Public functions to access the console ** ************************************** *//* Applications can use this to read the input. It'sprobably only useful if a signal has been receivedindicating that there's something to read. */void gtk_console_read(GtkConsole *object, gchar *buf, guint buf_len, gboolean add_to_history){gchar *text_chars;gchar *term_ptr;gchar *history_buf;g_return_if_fail (buf != NULL);/* make buf null-terminated, in case we can't copy */*buf = '\0';g_return_if_fail (buf_len > 0);g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));/* copy up to buf_len characters into buf,* starting from input_start_index */text_chars = gtk_editable_get_chars(GTK_EDITABLE(object), object -> input_start_index, -1);g_return_if_fail (text_chars != NULL);strncpy(buf, text_chars, buf_len);/* add command to history */term_ptr = strchr(text_chars, '\n');if((add_to_history == TRUE) && (term_ptr != NULL) && (*text_chars != '\n')) {history_buf = g_malloc(term_ptr - text_chars + 1);*term_ptr = '\0';strncpy(history_buf, text_chars, term_ptr - text_chars + 1);GTK_CONSOLE(object)->history = g_list_prepend(GTK_CONSOLE(object)->history, history_buf);GTK_CONSOLE(object)->history_num_items++;}g_free(text_chars);}/* Prompt the user for input */void gtk_console_enable_input(GtkConsole *object, gchar *prompt, guint prompt_len){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));gtk_console_flush(object);gtk_text_freeze(GTK_TEXT(object));/* Enable input */object -> input_enabled = TRUE;/* Append prompt to end of text box */if((prompt != NULL) && (*prompt != '\0') && (prompt_len != 0)) {gtk_text_set_point(GTK_TEXT(object), gtk_text_get_length(GTK_TEXT(object)));gtk_text_insert(GTK_TEXT(object), NULL, NULL, NULL, prompt, prompt_len);}/* Update input_start_index */object -> input_start_index = gtk_text_get_length(GTK_TEXT(object));/* Move point to end of text box */gtk_text_set_point(GTK_TEXT(object), gtk_text_get_length(GTK_TEXT(object)));gtk_text_thaw(GTK_TEXT(object));gtk_editable_set_position(GTK_EDITABLE(object), gtk_text_get_length(GTK_TEXT(object)));object->buffer_index = 0;/* reset history index */object->history_cur = NULL;}/* Disable input */void gtk_console_disable_input(GtkConsole *object){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));object -> input_enabled = FALSE;}/* Write buf out.* gtk_console_write also disables input.* Buffers output a line at a time, up to 1Kb** FIXME: if buf_len is greater than CONSOLE_MAX_BUF, the excess* is silently discarded. We should split such buf's into* multiple writes.*/void gtk_console_write(GtkConsole *object, gchar *buf, guint buf_len){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));gtk_console_disable_input(object);switch(object->buffer_type) {case CONSOLE_BUF_LINE:/* run out of buffer space - flush the buffer to free some space */if((object->buffer_index > 0) && (object->buffer_index + buf_len > CONSOLE_MAX_BUF - 1)) {gtk_console_flush(object);}/* copy */strncpy(object->out_buf + object->buffer_index, buf, CONSOLE_MAX_BUF - object->buffer_index - 1);object->buffer_index = MIN(CONSOLE_MAX_BUF - 1, object->buffer_index + buf_len);object->out_buf[object->buffer_index] = '\0';/* check for newline */if(strchr(object->out_buf, '\n') != NULL) {gtk_console_flush(object);}break;case CONSOLE_BUF_BLOCK:/* run out of buffer space - flush the buffer to free some space */if((object->buffer_index > 0) && (object->buffer_index + buf_len > CONSOLE_MAX_BUF - 1)) {gtk_console_flush(object);}/* copy */strncpy(object->out_buf + object->buffer_index, buf, CONSOLE_MAX_BUF - object->buffer_index - 1);object->buffer_index = MIN(CONSOLE_MAX_BUF - 1, object->buffer_index + buf_len);object->out_buf[object->buffer_index] = '\0';break;default: /* no buffering */strncpy(object->out_buf, buf, CONSOLE_MAX_BUF - 1);object->out_buf[CONSOLE_MAX_BUF - 1] = '\0';gtk_console_flush(object);object->buffer_index = 0;object->out_buf[0] = '\0';break;}}void gtk_console_flush(GtkConsole *object){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));gtk_text_freeze(GTK_TEXT(object));/* FIXME: Update text properties *//* Append up to buf_len characters from buf to end of text box */gtk_text_set_point(GTK_TEXT(object), gtk_text_get_length(GTK_TEXT(object)));gtk_text_insert(GTK_TEXT(object), NULL, NULL, NULL, object->out_buf, strlen(object->out_buf));/* Move point to end of text box */gtk_text_set_point(GTK_TEXT(object), gtk_text_get_length(GTK_TEXT(object)));/* FIXME: Update text properties */gtk_text_thaw(GTK_TEXT(object));gtk_editable_set_position(GTK_EDITABLE(object), gtk_text_get_length(GTK_TEXT(object)));object->buffer_index = 0;object->out_buf[0] = '\0';}/* FIXME: return an error message if things go wrong *//* FIXME: if errmsg is null, don't return an error message *//* FIXME: read maxitems from history first, and use them to *//* fill up to maxitems after current history is written. *//* However, the simplest way to do that is for the app *//* to restore the history first. */gboolean gtk_console_save_history(GtkConsole *object, gchar *filename, guint maxitems, gchar *errmsg){FILE *hist_file;GList *cur_item;gint n;g_return_val_if_fail (object != NULL, FALSE);g_return_val_if_fail (GTK_IS_CONSOLE (object), FALSE);hist_file = fopen(filename, "w");/* check file OK */if(hist_file == NULL)return FALSE;if(maxitems > object->history_num_items)maxitems = object->history_num_items;cur_item = g_list_last(object->history);for(n = 0; (n < maxitems) && (cur_item != NULL); n++, cur_item = cur_item->prev) {fputs(cur_item->data, hist_file);fputs("\n", hist_file);}fclose(hist_file);return TRUE;}void gtk_console_clear_history(GtkConsole *object){object -> history = NULL;object -> history_num_items = 0;object -> history_cur = NULL;}gboolean gtk_console_restore_history(GtkConsole *object, gchar *filename, guint maxitems, gchar *errmsg){FILE *hist_file;gchar line_buf[CONSOLE_MAX_BUF];gchar *history_buf, *term_ptr;gint n, buf_len;g_return_val_if_fail (object != NULL, FALSE);g_return_val_if_fail (GTK_IS_CONSOLE (object), FALSE);hist_file = fopen(filename, "r");/* check file ok */if(hist_file == NULL)return FALSE;for(n = 0; (n < maxitems) && (!ferror(hist_file)) && (!feof(hist_file)); n++) {/* read line */line_buf[0] = '\0';if(fgets(line_buf, CONSOLE_MAX_BUF - 1, hist_file) != NULL) {line_buf[CONSOLE_MAX_BUF - 1] = '\0';/* remove \n if it's there */term_ptr = strchr(line_buf, '\n');if(term_ptr != NULL)*term_ptr = '\0';/* copy to real place */buf_len = strlen(line_buf) + 1;history_buf = g_malloc(buf_len);*history_buf = '\0';strncpy(history_buf, line_buf, buf_len - 1);*(history_buf + buf_len - 1) = '\0';/* add it to the list */object->history = g_list_prepend(object->history, history_buf);object->history_num_items++;}}object->history_cur = NULL;return TRUE;}guint gtk_console_get_input_start_index(GtkConsole *object){g_return_val_if_fail(object != NULL, 0);g_return_val_if_fail(GTK_IS_CONSOLE(object), 0);return object->input_start_index;}gboolean gtk_console_get_line_available(GtkConsole *object){g_return_val_if_fail(object != NULL, FALSE);g_return_val_if_fail(GTK_IS_CONSOLE(object), FALSE);return object->line_available;}gboolean gtk_console_get_input_enabled(GtkConsole *object){g_return_val_if_fail(object != NULL, FALSE);g_return_val_if_fail(GTK_IS_CONSOLE(object), FALSE);return object->input_enabled;}/* *************** ** Signal handlers ** *************** */static void key_gdk_up(GtkConsole *console){gint insert_pos;gchar *history_buf;GList *next = NULL;if(console->history_cur == NULL) {next = console->history;}else if(console->history_cur->next != NULL) {next = console->history_cur->next;}if(next != NULL) {console->history_cur = next;/* delete any existing input */if(gtk_text_get_length(GTK_TEXT(console)) > console->input_start_index)gtk_editable_delete_text(GTK_EDITABLE(console), console->input_start_index, gtk_text_get_length(GTK_TEXT(console)));history_buf = console->history_cur->data;if(history_buf != NULL) {insert_pos = gtk_text_get_length(GTK_TEXT(console));gtk_editable_insert_text(GTK_EDITABLE(console), history_buf, strlen(history_buf), &insert_pos);}}}static void key_gdk_down(GtkConsole *console){gint insert_pos;gchar *history_buf;if(console->history_cur != NULL) {console->history_cur = console->history_cur->prev;/* delete any existing input */if(gtk_text_get_length(GTK_TEXT(console)) > console->input_start_index)gtk_editable_delete_text(GTK_EDITABLE(console), console->input_start_index, gtk_text_get_length(GTK_TEXT(console)));if(console->history_cur != NULL) {history_buf = console->history_cur->data;insert_pos = gtk_text_get_length(GTK_TEXT(console));gtk_editable_insert_text(GTK_EDITABLE(console), history_buf, strlen(history_buf), &insert_pos);}}}/* FIXME: Completion will have to be accomplished by a callback.The callback should have the same syntax as the readlinecompletion callback. */static gint gtk_console_key_press (GtkWidget *widget,GdkEventKey *event){g_return_val_if_fail (widget != NULL, FALSE);g_return_val_if_fail (event != NULL, FALSE);g_return_val_if_fail (GTK_IS_CONSOLE (widget), FALSE);/* stuff that we only change if the point is in the input area */if(gtk_editable_get_position(GTK_EDITABLE(widget)) >= GTK_CONSOLE(widget)->input_start_index) {if(GTK_CONSOLE(widget)->input_enabled == TRUE) {switch(event->keyval) {case GDK_Tab:/* do completion */break;case GDK_Up:/* previous history item */key_gdk_up(GTK_CONSOLE(widget));gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");return TRUE;break;case GDK_Down:/* next history item */key_gdk_down(GTK_CONSOLE(widget));gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");return TRUE;break;case GDK_Left:if (!(event->state & GDK_CONTROL_MASK) && !(event->state & GDK_SHIFT_MASK) && (gtk_editable_get_position(GTK_EDITABLE(widget)) == GTK_CONSOLE(widget)->input_start_index)) {gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");return TRUE;}break;case GDK_Home:if (!(event->state & GDK_CONTROL_MASK) && !(event->state & GDK_SHIFT_MASK)) {gtk_editable_set_position(GTK_EDITABLE(widget), GTK_CONSOLE(widget)->input_start_index);gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");return TRUE;}break;}}}/* things that apply wherever the point is */if(GTK_CONSOLE(widget)->input_enabled == TRUE) {switch(event->keyval) {case GDK_Return:gtk_editable_set_position(GTK_EDITABLE(widget), gtk_text_get_length(GTK_TEXT(widget)));GTK_CONSOLE(widget)->line_available = TRUE;break;}}return FALSE;}static void gtk_console_insert_text (GtkConsole *console,const gchar *new_text,gint new_text_length,gint *position){g_return_if_fail(console != NULL);g_return_if_fail(GTK_IS_CONSOLE(console));if(console->input_enabled == FALSE) {gtk_signal_emit_stop_by_name(GTK_OBJECT(console), "insert_text");}else if(*position < console->input_start_index) {*position = console->input_start_index;}}static void gtk_console_delete_text (GtkConsole *console,gint start_pos,gint end_pos){g_return_if_fail(console != NULL);g_return_if_fail(GTK_IS_CONSOLE(console));if((console->input_enabled == FALSE) || (start_pos < console->input_start_index) || (end_pos < console->input_start_index)) {gtk_signal_emit_stop_by_name(GTK_OBJECT(console), "delete_text");gtk_widget_queue_draw(GTK_WIDGET(console));}}static void gtk_console_changed_post (GtkConsole *console){g_return_if_fail(console != NULL);g_return_if_fail(GTK_IS_CONSOLE(console));if((gtk_text_get_length(GTK_TEXT(console)) - console->input_start_index) > 0) {gtk_signal_emit(GTK_OBJECT(console), console_signals[CONSOLE_CHAR_READY]);}if(console->line_available == TRUE) {gtk_signal_emit(GTK_OBJECT(console), console_signals[CONSOLE_LINE_READY]);console->line_available = FALSE;}return;}