#ifndef GLISTENER_H #define GLISTENER_H #include #include #include #include #include #include #include #include #include #include #if (GTK_CHECK_VERSION(3, 0, 0)) #include #else #include #endif #ifndef GDK_CURSOR_NEW #if GTK_CHECK_VERSION(3, 16, 0) #if GTK_CHECK_VERSION(3, 94, 0) #define GDK_WATCH "wait" #define GDK_LEFT_PTR "default" #define GDK_CURSOR_NEW(Type) gdk_cursor_new_from_name(Type, NULL) #else #define GDK_CURSOR_NEW(Type) gdk_cursor_new_for_display(gdk_display_get_default(), Type) #endif #else #define GDK_CURSOR_NEW(Type) gdk_cursor_new((GdkCursorType)Type) #endif #endif typedef struct glistener glistener; typedef enum {GLISTENER_STRING, GLISTENER_COMMENT, GLISTENER_BLOCK_COMMENT, GLISTENER_ATOM, GLISTENER_LIST, GLISTENER_BRACKET, GLISTENER_CHARACTER } glistener_colorizer_t; glistener *glistener_new(GtkWidget *parent, void (*initializations)(glistener *g, GtkWidget *new_listener)); void glistener_append_text (glistener *g, const char *msg); void glistener_insert_text (glistener *g, const char *text); char *glistener_text (glistener *g, int start, int end); void glistener_append_prompt (glistener *g); void glistener_set_prompt (glistener *g, const char *str); void glistener_set_prompt_tag (glistener *g, GtkTextTag *m); int glistener_prompt_position (glistener *g); void glistener_set_cursor_shape (glistener *g, GdkCursor *cursor_shape); int glistener_cursor_position (glistener *g); void glistener_set_cursor_position (glistener *g, int position); void glistener_scroll_to_end (glistener *g); void glistener_post_status (glistener *g, const char *msg); void glistener_clear_status (glistener *g); void glistener_clear (glistener *g); bool glistener_write (glistener *g, FILE *fp); void glistener_set_highlight_tag (glistener *g, GtkTextTag *m); void glistener_set_font (glistener *g, PangoFontDescription *font); #if (!GTK_CHECK_VERSION(3, 0, 0)) void glistener_set_text_color (glistener *g, GdkColor *p); void glistener_set_background_color(glistener *g, GdkColor *p); #else void glistener_set_text_color (glistener *g, GdkRGBA *p); void glistener_set_background_color(glistener *g, GdkRGBA *p); #endif void glistener_key_bindings (glistener *g, gpointer cls); void glistener_is_schemish (glistener *g, bool filtering); void glistener_set_completer (glistener *g, void (*completer)(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data)); void glistener_set_helper (glistener *g, const char *(*help)(glistener *g, const char *text)); void glistener_set_checker (glistener *g, const char *(*check)(glistener *g, const char *text)); void glistener_set_evaluator (glistener *g, void (*eval)(glistener *g, const char *text)); void glistener_set_colorizer (glistener *g, void (*colorizer)(glistener *g, glistener_colorizer_t type, int start, int end)); #if (GTK_CHECK_VERSION(3, 92, 1)) void glistener_set_keyer (glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEvent *e)); #else void glistener_set_keyer (glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEventKey *e)); #endif /* these are for regression testing */ char *glistener_evaluate (glistener *g); char *glistener_complete (glistener *g); GtkWidget *glistener_text_widget(glistener *g); GtkTextBuffer *glistener_text_buffer(glistener *g); GtkWidget *glistener_status_widget(glistener *g); /* -------------------------------------------------------------------------------- */ /* an annotated version of the same info: * * typedef struct glistener glistener; * * our opaque handle on the info for each listener. You can run any number of listeners * at the same time. For a simple example, see s7.html#glistener. Another is snd-glistener.c * in the Snd package, and tools/gcall.c. * * * -------- * glistener *glistener_new(GtkWidget *parent, void (*initializations)(glistener *g, GtkWidget *new_listener)); * * This creates a new listener. If parent is not NULL, the listener is added to it * via gtk_container_add. The second argument is an optional function that will be called * during the listener initialization, just before the signals are connected. It * can add its own signal connections or set defaults. The "new_listener" argument * passed to it is the new GtkTextView widget. Its parent is a GtkScrolledWindow, * which is placed in a grid (or table in gtk-2) along with the listener's statusbar. * * * -------- * void glistener_append_text(glistener *g, const char *msg); * * This appends "msg" to the end of the listener text. * * * -------- * void glistener_insert_text(glistener *g, const char *text); * * This inserts "text" at the cursor. * * * -------- * char *glistener_text(glistener *g, int start, int end); * * This returns the text in the listener between the offsets (unicode positions) * "start" and "end". * * * -------- * void glistener_append_prompt(glistener *g); * * This appends a prompt at the bottom of the listener text. Everything * depends on the prompt, so you need to send one out at the end of each * evaluation. The usual sequence is: * user types in an expression followed by -> listener-text * we_eval(listener-text) -> some string * glistener_append_text(g, that string); * glistener_append_prompt(g); * * * -------- * void glistener_set_prompt(glistener *g, const char *str); * * This sets the text of the prompt. It defaults to ">" but can be anything. * One way to get, for example, the unicode lower-case lambda followed by ">" as the prompt is: * uint8_t prompt[4] = {0xce, 0xbb, '>', '\0'}; * glistener_set_prompt(g, prompt); * UTF8 lambda is 0xce 0xbb. * * * -------- * void glistener_set_prompt_tag(glistener *g, GtkTextTag *m); * * This sets the GtkTextTag for the prompt. It defaults to NULL, but it's nicer to * set off the prompt in some way (like bold-face). Here's a tag that makes our prompt * bold red: * glistener_set_prompt_tag(g, * gtk_text_buffer_create_tag(buffer, "glistener_prompt_tag", "weight", PANGO_WEIGHT_BOLD, "foreground", "red", NULL)); * * * -------- * int glistener_prompt_position(glistener *g); * * This returns the current (active) prompt offset in the listener text buffer. * * * -------- * void glistener_set_cursor_shape(glistener *g, GdkCursor *cursor_shape); * * This sets the current cursor shape. It is normally an arrow, but changes to * a watch or hour-glass during evaluation: * gdk_cursor_new(GDK_WATCH) or gdk_cursor_new(GDK_LEFT_PTR) * * * -------- * int glistener_cursor_position(glistener *g); * * This returns the cursor offset in the listener. * * * -------- * void glistener_set_cursor_position(glistener *g, int position); * * This moves the listener's cursor to the offset "position". * * * -------- * void glistener_scroll_to_end(glistener *g); * * This scrolls the view of the listener's text to the end of the text. * * * -------- * void glistener_post_status(glistener *g, const char *msg); * * This places "msg" in the listener's statusbar. The previous text, if any * is removed, so you don't need to worry about gtk_statusbar_pop. * * * -------- * void glistener_clear_status(glistener *g); * * This removes all messages from the statusbar. * * * -------- * void glistener_clear(glistener *g); * * This deletes all the text in the listener, leaving only the initial prompt. * * * -------- * bool glistener_write(glistener *g, FILE *fp); * * This writes the current listener text contents to the file "fp". * * * -------- * void glistener_set_highlight_tag(glistener *g, GtkTextTag *m); * * This sets the GtkTexTag for highlighted text, normally marking an unmatched * open parenthesis. The default is bold face, red foreground, but I may change * that to red backgound. See glistener_set_prompt_tag above for an example. * * * -------- * void glistener_set_font(glistener *g, PangoFontDescription *font); * * This sets the listener text font. It defaults to "Monospace 11". Indentation * code assumes fixed-width spacing, but that's not a big deal. * glistener_set_font(g, pango_font_description_from_string("Monospace 10")); * * * -------- * void glistener_set_text_color(glistener *g, GdkRGBA *p); * * This sets the text foreground color, normally black. In gtk 2, the color * is GdkColor*. This snippet makes the text green: * GdkRGBA color; * color.red = 0.0; color.green = 0.97; color.blue = 0.0; color.alpha = 1.0; * glistener_set_text_color(g1, &color); * * * -------- * void glistener_set_background_color(glistener *g, GdkRGBA *p); * * This sets the listener background color, normally white. In gtk 2, use * a GdkColor* instead. The following code gives the listener a light blue * background: * GdkRGBA color; * color.red = 0.94; color.green = 0.97; color.blue = 1.0; color.alpha = 1.0; * glistener_set_background_color(g1, &color); * * * -------- * void glistener_key_bindings(glistener *g, gpointer cls); * * This establishes the listener's emacs-inspired keybindings in the gtk * widget class "cls" (normally gtk_entry). In Snd I use this to make sure * all the text-oriented widgets share the same basic set: * glistener_key_bindings(g, GTK_ENTRY_GET_CLASS(GTK_ENTRY(text))); * * * -------- * void glistener_is_schemish(glistener *g, bool filtering); * * This determines whether the listener tries to parse the text before sending it to * the evaluator. It defaults to true, expecting more-or-less lispish syntax. In other * languages (Ruby and Forth in Snd, for example), set this to false. * * * * -------- * void glistener_set_completer(glistener *g, void (*completer)(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data)) * * The completer is called whenever the user types TAB after what appears to be a * partial symbol name (in a string, the filename completer is called instead). * To find a plausible completion, the listener needs access to the table of currently * known symbols. In s7, s7_for_each_symbol_name runs through the symbol table, * calling a function on each symbol, and stopping when the function returns true, * so symbol completion in s7 is: * * s7_for_each_symbol_name(s7, symbol_func, data); * * The completer function in s7 is: * * static void completer(glistener *g, bool (*symbol_func)(const char *symbol_name, void *data), void *data) * { * s7_for_each_symbol_name(s7, symbol_func, data); * } * * and it is tied into the listener via: * * glistener_set_completer(g1, completer); * * * * -------- * void glistener_set_helper(glistener *g, const char *(*help)(glistener *g, const char *text)) * * The helper is called whenever the listener thinks a help string is in order. It is passed * a string ("text") about which it hopes to get help. If the helper returns NULL, nothing * happens, but otherwise, the returned help is posted in the status area. In s7, it is: * * static const char *helper(glistener *g, const char *text) * { * s7_pointer sym; * sym = s7_symbol_table_find_name(s7, text); * if (sym) * return(s7_help(s7, sym)); * glistener_clear_status(g); * return(NULL); * } * * and it is tied into the listener via: * * glistener_set_helper(g1, helper); * * * * -------- * void glistener_set_evaluator(glistener *g, void (*eval)(glistener *g, const char *text)) * * This is the heart of the listener. It gets a string ("text") and * does something debonair. In s7, it calls s7_eval_c_string, and then * displays the result. Although error handling makes the code complicated, * the basic idea is: * * static void evaluator(glistener *g, const char *text) * { * s7_pointer result; * char *msg; * result = s7_eval_c_string(s7, text); * glistener_append_text(g, "\n"); * msg = s7_object_to_c_string(s7, result); * glistener_append_text(g, msg); * if (msg) free(msg); * glistener_append_prompt(g); * } * * tied into the listener via: * * glistener_set_evaluator(g1, evaluator); * * * * -------- * void glistener_set_checker(glistener *g, const char *(*check)(glistener *g, const char *text)) * * The checker is called when close-paren is typed and the matching open-paren can be found. * "text" in this case is the expression. If it notices something awry in the expression, * it can squawk in the status area. The checker in snd-glistener.c calls s7_read on the text, * then runs various checks on that code (function argument checks and so on). * * * * -------- * void glistener_set_colorizer(glistener *g, void (*colorizer)(glistener *g, glistener_colorizer_t type, int start, int end)) * * The colorizer highlights portions of the code, normally using colors, but the GtkTextBuffer * is at your disposal. This function is called whenever there is any change in the listener * buffer's contents. The "start" and "end" ints are offsets into the buffer giving what the * listener thinks are the current bounds where there is an entity of "type", one of the glistener_colorizer_t * enums given above. See the colorizer function in snd-glistener.c. * * * * -------- * void glistener_set_keyer(glistener *g, bool (*key)(glistener *g, GtkWidget *w, GdkEventKey *e)) * * The keyer is called upon key-press. It is passed the widget involved (the listener * text-view widget), and the GdkEvent. The event has fields giving the key itself, and * any auxiliary keys (control, shift, etc). If you don't want to handle that key, or * want the built-in action to be called, return false. If keyer returns true, any * further handling of the signal is blocked. The default keyer simply returns false. * * glistener rebinds some of the keys to mimic Emacs, then adds a few others: * M-< go to start of text (also up-arrow) * M-> go to end of text (also down-arrow) * M-a go to previous prompt * M-e go to next prompt * M-n restore previous expression, moving back in the history * M-p restore previous expression, moving forward in the history * Tab complete preceding name, if any, else look for indentation opportunity * Return send current expression, if any, to evaluator, else insert return * * * * -------- * char *glistener_evaluate(glistener *g); * char *glistener_complete(glistener *g); * * These two functions are intended for regression testing. glistener_evaluate * simulates typing , and glistener_complete simulates . */ #endif