Rev 679 | Blame | Last modification | View Log | Download | RSS feed
/** R : A Computer Langage for Statistical Data Analysis* Copyright (C) 1998-2004 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*/#ifdef HAVE_CONFIG_H# include <config.h> /* for HAVE_STRING_H / HAVE_STRINGS_H */#endif/* Avoid braced-groups warning from -Wall */#define G_STMT_START do#define G_STMT_END while(0)#include <gdk/gdkkeysyms.h>#include <stdio.h>#if defined (HAVE_STRING_H)# include <string.h>#elif defined (HAVE_STRINGS_H)# include <strings.h>#endif#include "gtkconsole.h"enum {ARG_0, /* Skip 0, an invalid argument ID */ARG_INPUT_COLOR_GDK,ARG_OUTPUT_COLOR_GDK,ARG_BG_COLOR_GDK,ARG_FONT};enum{CONSOLE_LINE_READY,CONSOLE_LINE_READY_WITH_VALUE,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);static void gtk_console_set_arg(GtkObject* object,GtkArg* arg,guint arg_id);/* object arguments */static void gtk_console_set_bg_color (GtkConsole * console,GdkColor const * color);static void gtk_console_set_input_color (GtkConsole * console,GdkColor const * color);static void gtk_console_set_output_color (GtkConsole * console,GdkColor const *color);static void gtk_console_set_font (GtkConsole * console, gchar const * font);/* 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,/* reserved */ NULL,/* reserved */ NULL,(GtkClassInitFunc) 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_line_ready),gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);console_signals[CONSOLE_LINE_READY_WITH_VALUE] =gtk_signal_new ("console_line_ready_with_value",GTK_RUN_LAST,object_class->type,GTK_SIGNAL_OFFSET (GtkConsoleClass, console_line_ready_with_value),gtk_marshal_NONE__STRING, GTK_TYPE_NONE, 1, GTK_TYPE_STRING);console_signals[CONSOLE_CHAR_READY] =gtk_signal_new ("console_char_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_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_line_ready_with_value = NULL;klass->console_input_enabled = NULL;klass->console_input_disabled = NULL;/* Object arguments */gtk_object_add_arg_type("GtkConsole::input_color_gdk",GTK_TYPE_GDK_COLOR,GTK_ARG_WRITABLE,ARG_INPUT_COLOR_GDK);gtk_object_add_arg_type("GtkConsole::output_color_gdk",GTK_TYPE_GDK_COLOR,GTK_ARG_WRITABLE,ARG_OUTPUT_COLOR_GDK);gtk_object_add_arg_type("GtkConsole::bg_color_gdk",GTK_TYPE_GDK_COLOR,GTK_ARG_WRITABLE,ARG_BG_COLOR_GDK);gtk_object_add_arg_type("GtkConsole::font",GTK_TYPE_STRING,GTK_ARG_WRITABLE,ARG_FONT);object_class->set_arg = gtk_console_set_arg;}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;/* register object arguments *//* 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);}static voidgtk_console_set_arg (GtkObject *object, GtkArg *arg, guint arg_id){GtkConsole *console;GdkColor *pcolor;console = GTK_CONSOLE (object);pcolor = GTK_VALUE_BOXED(*arg);switch (arg_id){case ARG_INPUT_COLOR_GDK:gtk_console_set_input_color (console, pcolor);break;case ARG_OUTPUT_COLOR_GDK:gtk_console_set_output_color (console, pcolor);break;case ARG_BG_COLOR_GDK:gtk_console_set_bg_color (console, pcolor);break;case ARG_FONT:gtk_console_set_font (console, GTK_VALUE_STRING (*arg));break;default:break;}}/* ************************************** ** 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. */voidgtk_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 */voidgtk_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 > 0 ? prompt_len : strlen(prompt));}/* 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 */voidgtk_console_disable_input (GtkConsole * object){g_return_if_fail (object);g_return_if_fail (GTK_IS_CONSOLE (object));object->input_enabled = FALSE;}static void_write_buffer (GtkConsole *object, gchar *str, guint str_len){/* Write str to output buffer of object. If str is too long,then it is split into multiple writes */int str_written = 0;if(str_len < 0 && str)str_len = strlen(str);/* Run out of buffer space? Then flush buffer */if ((object->buffer_index > 0) &&(object->buffer_index + str_len > CONSOLE_MAX_BUF - 1)){gtk_console_flush (object);}/* Copy str to output buffer */strncpy (object->out_buf + object->buffer_index, str,CONSOLE_MAX_BUF - object->buffer_index - 1);str_written = MIN (CONSOLE_MAX_BUF - object->buffer_index - 1, str_len);object->buffer_index += str_written;object->out_buf[object->buffer_index] = '\0';/* Didn't write whole of str? Then try again */if (str_written < str_len)_write_buffer(object, str + str_written, str_len - str_written);}voidgtk_console_write (GtkConsole * object, gchar * buf, guint buf_len){/* NOTE: This also disables input.It must be enabled somewhere. Where? - MTP */g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));gtk_console_disable_input (object);_write_buffer(object, buf, buf_len);switch (object->buffer_type){case CONSOLE_BUF_NONE:/* Always flush */gtk_console_flush (object);break;case CONSOLE_BUF_LINE:/* Flush if output contains a newline character */if (strchr (object->out_buf, '\n') != NULL){gtk_console_flush (object);}break;case CONSOLE_BUF_BLOCK:/* Don't flush until buffer is full (handled by _write_buffer) */break;default:g_assert_not_reached();}}voidgtk_console_flush (GtkConsole * object){g_return_if_fail (object != NULL);g_return_if_fail (GTK_IS_CONSOLE (object));gtk_text_freeze (GTK_TEXT (object));/* 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,&(object->output_color), 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)));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. */gbooleangtk_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;}voidgtk_console_clear_history (GtkConsole * object){object->history = NULL;object->history_num_items = 0;object->history_cur = NULL;}gbooleangtk_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;}guintgtk_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;}gbooleangtk_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;}gbooleangtk_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;}static voidgtk_console_set_bg_color (GtkConsole * console, GdkColor const * color){GtkStyle *new_style;g_return_if_fail (GTK_IS_CONSOLE(console));console->bg_color = *color;new_style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET(console)));new_style->base[GTK_STATE_NORMAL] = *color;gtk_widget_set_style(GTK_WIDGET(console), new_style);}static voidgtk_console_set_input_color (GtkConsole * console, GdkColor const * color){GtkStyle *new_style;g_return_if_fail (GTK_IS_CONSOLE(console));console->input_color = *color;new_style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET(console)));new_style->text[GTK_STATE_NORMAL] = *color;gtk_widget_set_style(GTK_WIDGET(console), new_style);}static voidgtk_console_set_output_color (GtkConsole * console, GdkColor const * color){g_return_if_fail (GTK_IS_CONSOLE(console));console->output_color = *color;}static voidgtk_console_set_font (GtkConsole * console, gchar const *font){GtkStyle *new_style;new_style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET(console)));gdk_font_unref(new_style->font);new_style->font = gdk_fontset_load(font);gtk_widget_set_style(GTK_WIDGET(console), new_style);}/* *************** ** Signal handlers ** *************** */static voidkey_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 voidkey_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);}}}static void_extend_selection_back (GtkWidget * widget, int start, int pos){/* Utility function for extending selection (backwards only) */GtkEditable *editable;g_return_if_fail (widget);g_return_if_fail (GTK_IS_EDITABLE (widget));editable = GTK_EDITABLE (widget);if (editable->has_selection){if (pos == editable->selection_start_pos)/* Sanity check */{gtk_editable_select_region(editable, start,editable->selection_end_pos);}}else{gtk_editable_select_region (editable, start, pos);}}/* FIXME: Completion will have to be accomplished by a callback.The callback should have the same syntax as the readlinecompletion callback. */static gintgtk_console_key_press (GtkWidget * widget, GdkEventKey * event){gint pos, start;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);if (GTK_CONSOLE (widget)->input_enabled == FALSE)return FALSE;pos = gtk_editable_get_position (GTK_EDITABLE (widget));start = GTK_CONSOLE (widget)->input_start_index;if (pos >= start)/* stuff that we only change if the point is in the input area */{gboolean signal_caught = FALSE;if (event->state & GDK_CONTROL_MASK){switch(event->keyval){case 'A': case 'a':/* Beginning of line */gtk_editable_set_position (GTK_EDITABLE (widget), start);if(event->state & GDK_SHIFT_MASK){_extend_selection_back (widget, start, pos);}signal_caught = TRUE;break;case 'B': case 'b': /* Backward one character */case GDK_Left: /* Backward one word */case 'H': case 'h': /* Delete backward character */case 'W': case 'w': /* Delete backwards word */if (pos == start){signal_caught = TRUE;}break;case 'N': case 'n':/* Next line => next history item*/key_gdk_down (GTK_CONSOLE (widget));signal_caught = TRUE;break;case 'P': case 'p':/* Previous line => previous history item */key_gdk_up (GTK_CONSOLE (widget));signal_caught = TRUE;break;case 'U': case 'u':/* Delete line */gtk_editable_delete_text(GTK_EDITABLE(widget),start,gtk_text_get_length(GTK_TEXT(widget)));signal_caught = TRUE;break;}}else if (event->state & GDK_MOD1_MASK){switch (event->keyval){case 'B': case 'b':/* Back one word */if (pos == start){signal_caught = TRUE;}break;}}else /* No control or alt key */{switch (event->keyval){case GDK_Tab:/* do completion */break;case GDK_Up:/* previous history item */key_gdk_up (GTK_CONSOLE (widget));signal_caught = TRUE;break;case GDK_Down:/* next history item */key_gdk_down (GTK_CONSOLE (widget));signal_caught = TRUE;break;case GDK_Left:if (pos == start){signal_caught = TRUE;}break;case GDK_Home:if (!(event->state & GDK_SHIFT_MASK)){gtk_editable_set_position (GTK_EDITABLE (widget), start);signal_caught = TRUE;}break;}}if (signal_caught){gtk_signal_emit_stop_by_name (GTK_OBJECT(widget),"key_press_event");return TRUE;}}if (GTK_CONSOLE (widget)->input_enabled == TRUE)/* things that apply wherever the point is */{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 voidgtk_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 voidgtk_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 voidgtk_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){char *line;line = gtk_editable_get_chars (GTK_EDITABLE (console), console->input_start_index, -1);gtk_signal_emit (GTK_OBJECT (console),console_signals[CONSOLE_LINE_READY_WITH_VALUE], line);g_free(line);gtk_signal_emit (GTK_OBJECT (console),console_signals[CONSOLE_LINE_READY]);console->line_available = FALSE;}return;}