/* GmcCharGrid Widget - Simple character grid for the gmc viewer * * Copyright (C) 1997 The Free Software Foundation * * Author: Federico Mena */ #include #include #include "gmc-chargrid.h" #define DEFAULT_WIDTH 80 #define DEFAULT_HEIGHT 25 #define DEFAULT_FONT "fixed,-*-*-medium-r-normal-*-12-*-*-*-*-*-*-*,*" #define CHARS(cgrid) ((char *) cgrid->chars) #define ATTRS(cgrid) ((struct attr *) cgrid->attrs) struct attr { gulong fg; gulong bg; int fg_set : 1; int bg_set : 1; }; enum { SIZE_CHANGED, LAST_SIGNAL }; typedef void (* GmcCharGridSignal1) (GtkObject *object, guint arg1, guint arg2, gpointer data); static void gmc_char_grid_marshal_signal_1 (GtkObject *object, GtkSignalFunc func, gpointer func_data, GtkArg *args); static void gmc_char_grid_class_init (GmcCharGridClass *class); static void gmc_char_grid_init (GmcCharGrid *cgrid); static void gmc_char_grid_destroy (GtkObject *object); static void gmc_char_grid_realize (GtkWidget *widget); static void gmc_char_grid_size_request (GtkWidget *widget, GtkRequisition *requisition); static void gmc_char_grid_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void gmc_char_grid_draw (GtkWidget *widget, GdkRectangle *area); static gint gmc_char_grid_expose (GtkWidget *widget, GdkEventExpose *event); static void gmc_char_grid_real_size_changed (GmcCharGrid *cgrid, guint width, guint height); static GtkWidgetClass *parent_class; static guint cgrid_signals[LAST_SIGNAL] = { 0 }; guint gmc_char_grid_get_type (void) { static guint cgrid_type = 0; if (!cgrid_type) { GtkTypeInfo cgrid_info = { "GmcCharGrid", sizeof (GmcCharGrid), sizeof (GmcCharGridClass), (GtkClassInitFunc) gmc_char_grid_class_init, (GtkObjectInitFunc) gmc_char_grid_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; cgrid_type = gtk_type_unique (gtk_widget_get_type (), &cgrid_info); } return cgrid_type; } static void gmc_char_grid_class_init (GmcCharGridClass *class) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass *) class; widget_class = (GtkWidgetClass *) class; parent_class = gtk_type_class (gtk_widget_get_type ()); cgrid_signals[SIZE_CHANGED] = gtk_signal_new ("size_changed", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GmcCharGridClass, size_changed), gmc_char_grid_marshal_signal_1, GTK_TYPE_NONE, 2, GTK_TYPE_UINT, GTK_TYPE_UINT); gtk_object_class_add_signals (object_class, cgrid_signals, LAST_SIGNAL); object_class->destroy = gmc_char_grid_destroy; widget_class->realize = gmc_char_grid_realize; widget_class->size_request = gmc_char_grid_size_request; widget_class->size_allocate = gmc_char_grid_size_allocate; widget_class->draw = gmc_char_grid_draw; widget_class->expose_event = gmc_char_grid_expose; class->size_changed = gmc_char_grid_real_size_changed; } static void gmc_char_grid_init (GmcCharGrid *cgrid) { cgrid->width = 0; cgrid->height = 0; cgrid->chars = NULL; cgrid->attrs = NULL; cgrid->frozen = FALSE; cgrid->font = NULL; cgrid->gc = NULL; cgrid->char_width = 0; cgrid->char_height = 0; cgrid->char_y = 0; } GtkWidget * gmc_char_grid_new (void) { GmcCharGrid *cgrid; cgrid = gtk_type_new (gmc_char_grid_get_type ()); gmc_char_grid_set_font (cgrid, DEFAULT_FONT); gmc_char_grid_set_size (cgrid, DEFAULT_WIDTH, DEFAULT_HEIGHT); return GTK_WIDGET (cgrid); } static void gmc_char_grid_destroy (GtkObject *object) { GmcCharGrid *cgrid; g_return_if_fail (object != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (object)); cgrid = GMC_CHAR_GRID (object); if (cgrid->chars) g_free (cgrid->chars); if (cgrid->attrs) g_free (cgrid->attrs); if (cgrid->font) gdk_font_unref (cgrid->font); if (cgrid->gc) gdk_gc_destroy (cgrid->gc); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void gmc_char_grid_realize (GtkWidget *widget) { GdkWindowAttr attributes; gint attributes_mask; GmcCharGrid *cgrid; g_return_if_fail (widget != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (widget)); cgrid = GMC_CHAR_GRID (widget); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); attributes.x = widget->allocation.x + (widget->allocation.width - cgrid->char_width * cgrid->width) / 2; attributes.y = widget->allocation.y + (widget->allocation.height - cgrid->char_height * cgrid->height) / 2; attributes.width = cgrid->width * cgrid->char_width; attributes.height = cgrid->height * cgrid->char_height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_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, widget); cgrid->gc = gdk_gc_new (cgrid->widget.window); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); } static void gmc_char_grid_size_request (GtkWidget *widget, GtkRequisition *requisition) { GmcCharGrid *cgrid; g_return_if_fail (widget != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (widget)); g_return_if_fail (requisition != NULL); cgrid = GMC_CHAR_GRID (widget); requisition->width = cgrid->width * cgrid->char_width; requisition->height = cgrid->height * cgrid->char_height; } static void gmc_char_grid_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GmcCharGrid *cgrid; int w, h; g_return_if_fail (widget != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; cgrid = GMC_CHAR_GRID (widget); w = allocation->width / cgrid->char_width; h = allocation->height / cgrid->char_height; if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (widget->window, allocation->x + (allocation->width - cgrid->char_width * cgrid->width) / 2, allocation->y + (allocation->height - cgrid->char_height * cgrid->height) / 2, cgrid->width * cgrid->char_width, cgrid->height * cgrid->char_height); gmc_char_grid_set_size (cgrid, MAX (w, 1), MAX (h, 1)); } static void update_strip (GmcCharGrid *cgrid, int x, int y, int width) { int i; char *chars; struct attr *attrs; int first; gulong color; gulong ocolor; GdkColor gcolor; chars = CHARS (cgrid) + (cgrid->width * y + x); attrs = ATTRS (cgrid) + (cgrid->width * y + x); /* First paint the background and then paint the text. We do it in two passes * so that we can paint runs of same-background and foreground more efficiently. */ i = 0; while (i < width) { first = i; ocolor = attrs[i].bg_set ? attrs[i].bg : GTK_WIDGET (cgrid)->style->bg[GTK_STATE_NORMAL].pixel; do { i++; color = attrs[i].bg_set ? attrs[i].bg : GTK_WIDGET (cgrid)->style->bg[GTK_STATE_NORMAL].pixel; } while ((i < width) && (color == ocolor)); gcolor.pixel = ocolor; gdk_gc_set_foreground (cgrid->gc, &gcolor); gdk_draw_rectangle (cgrid->widget.window, cgrid->gc, TRUE, (first + x) * cgrid->char_width, y * cgrid->char_height, cgrid->char_width * (i - first), cgrid->char_height); } /* Now paint the text */ i = 0; while (i < width) { first = i; ocolor = attrs[i].fg_set ? attrs[i].fg : GTK_WIDGET (cgrid)->style->fg[GTK_STATE_NORMAL].pixel; do { i++; color = attrs[i].fg_set ? attrs[i].fg : GTK_WIDGET (cgrid)->style->fg[GTK_STATE_NORMAL].pixel; } while ((i < width) && (color == ocolor)); gcolor.pixel = ocolor; gdk_gc_set_foreground (cgrid->gc, &gcolor); gdk_draw_text (cgrid->widget.window, cgrid->font, cgrid->gc, (first + x) * cgrid->char_width, y * cgrid->char_height + cgrid->char_y, &chars[first], i - first); } } static void update_region (GmcCharGrid *cgrid, int x, int y, int width, int height) { int i; if ((width == 0) || (height == 0)) return; x = MAX (0, x); y = MAX (0, y); width = MIN (width, cgrid->width - x); height = MIN (height, cgrid->height - y); for (i = 0; i < height; i++) update_strip (cgrid, x, y + i, width); } static void paint (GmcCharGrid *cgrid, GdkRectangle *area) { int x1, y1, x2, y2; /* This logic is shamelessly ripped from gtkterm :-) - Federico */ if (area->width > 1) area->width--; else if (area->x > 0) area->x--; if (area->height > 1) area->height--; else if (area->y > 0) area->y--; x1 = area->x / cgrid->char_width; y1 = area->y / cgrid->char_height; x2 = (area->x + area->width) / cgrid->char_width; y2 = (area->y + area->height) / cgrid->char_height; update_region (cgrid, x1, y1, (x2 - x1) + 1, (y2 - y1) + 1); } static void gmc_char_grid_draw (GtkWidget *widget, GdkRectangle *area) { GmcCharGrid *cgrid; GdkRectangle w_area; GdkRectangle p_area; g_return_if_fail (widget != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (widget)); g_return_if_fail (area != NULL); if (GTK_WIDGET_DRAWABLE (widget)) { cgrid = GMC_CHAR_GRID (widget); /* Offset the area because the window does not fill thea allocation */ area->x -= (widget->allocation.width - cgrid->char_width * cgrid->width) / 2; area->y -= (widget->allocation.height - cgrid->char_height * cgrid->height) / 2; w_area.x = 0; w_area.y = 0; w_area.width = cgrid->char_width * cgrid->width; w_area.height = cgrid->char_height * cgrid->height; if (gdk_rectangle_intersect (area, &w_area, &p_area)) paint (cgrid, &p_area); } } static gint gmc_char_grid_expose (GtkWidget *widget, GdkEventExpose *event) { g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GMC_IS_CHAR_GRID (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (GTK_WIDGET_DRAWABLE (widget)) paint (GMC_CHAR_GRID (widget), &event->area); return FALSE; } void gmc_char_grid_clear (GmcCharGrid *cgrid, int x, int y, int width, int height, GdkColor *bg) { int x1, y1, x2, y2; int xx, yy; char *ch; struct attr *attrs; g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); x1 = MAX (x, 0); y1 = MAX (y, 0); x2 = MIN (x + width, cgrid->width); y2 = MIN (y + height, cgrid->height); ch = CHARS (cgrid) + (y1 * cgrid->width); attrs = ATTRS (cgrid) + (y1 * cgrid->width); for (yy = y1; yy < y2; yy++) { for (xx = x1; xx < x2; xx++) { ch[xx] = ' '; attrs[xx].bg = bg ? bg->pixel : 0; attrs[xx].bg_set = bg ? TRUE : FALSE; } ch += cgrid->width; attrs += cgrid->width; } if (GTK_WIDGET_DRAWABLE (cgrid) && !cgrid->frozen) update_region (cgrid, x, y, width, height); } void gmc_char_grid_put_char (GmcCharGrid *cgrid, int x, int y, GdkColor *fg, GdkColor *bg, char ch) { char *chars; struct attr *attrs; g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); if ((x < 0) || (x >= cgrid->width) || (y < 0) || (y >= cgrid->height)) return; chars = CHARS (cgrid) + (y * cgrid->width + x); attrs = ATTRS (cgrid) + (y * cgrid->width + x); *chars = ch; attrs->fg = fg ? fg->pixel : 0; attrs->fg_set = fg ? TRUE : FALSE; attrs->bg = bg ? bg->pixel : 0; attrs->bg_set = bg ? TRUE : FALSE; if (GTK_WIDGET_DRAWABLE (cgrid) && !cgrid->frozen) update_region (cgrid, x, y, 1, 1); } void gmc_char_grid_put_string (GmcCharGrid *cgrid, int x, int y, GdkColor *fg, GdkColor *bg, char *str) { g_return_if_fail (str != NULL); gmc_char_grid_put_text (cgrid, x, y, fg, bg, str, strlen (str)); } void gmc_char_grid_put_text (GmcCharGrid *cgrid, int x, int y, GdkColor *fg, GdkColor *bg, char *text, int length) { char *chars; struct attr *attrs; int i, pos; g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); g_return_if_fail (text != NULL); if ((x < 0) || (x >= cgrid->width) || (y < 0) || (y >= cgrid->height)) return; chars = CHARS (cgrid) + (cgrid->width * y + x); attrs = ATTRS (cgrid) + (cgrid->width * y + x); for (i = 0, pos = x; (i < length) && (pos < cgrid->width); i++, pos++) { *chars++ = *text++; attrs->fg = fg ? fg->pixel : 0; attrs->fg_set = fg ? TRUE : FALSE; attrs->bg = bg ? bg->pixel : 0; attrs->bg_set = bg ? TRUE : FALSE; attrs++; } if (GTK_WIDGET_DRAWABLE (cgrid) && !cgrid->frozen) update_region (cgrid, x, y, i, 1); } void gmc_char_grid_set_font (GmcCharGrid *cgrid, const char *font_name) { g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); if (cgrid->font) gdk_font_unref (cgrid->font); cgrid->font = gdk_fontset_load (font_name); if (!cgrid->font) cgrid->font = gdk_fontset_load (DEFAULT_FONT); cgrid->char_width = gdk_char_width (cgrid->font, ' '); /* assume monospaced font! */ cgrid->char_height = cgrid->font->ascent + cgrid->font->descent; cgrid->char_y = cgrid->font->ascent; gtk_widget_queue_resize (GTK_WIDGET (cgrid)); } void gmc_char_grid_set_size (GmcCharGrid *cgrid, guint width, guint height) { gtk_signal_emit (GTK_OBJECT (cgrid), cgrid_signals[SIZE_CHANGED], width, height); } void gmc_char_grid_get_size (GmcCharGrid *cgrid, guint *width, guint *height) { g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); if (width) *width = cgrid->width; if (height) *height = cgrid->height; } void gmc_char_grid_freeze (GmcCharGrid *cgrid) { g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); cgrid->frozen = TRUE; } void gmc_char_grid_thaw (GmcCharGrid *cgrid) { g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); cgrid->frozen = FALSE; if (GTK_WIDGET_DRAWABLE (cgrid)) gtk_widget_queue_draw (GTK_WIDGET (cgrid)); } static void gmc_char_grid_marshal_signal_1 (GtkObject *object, GtkSignalFunc func, gpointer func_data, GtkArg *args) { GmcCharGridSignal1 rfunc; rfunc = (GmcCharGridSignal1) func; (*rfunc) (object, GTK_VALUE_UINT (args[0]), GTK_VALUE_UINT (args[1]), func_data); } static void gmc_char_grid_real_size_changed (GmcCharGrid *cgrid, guint width, guint height) { int i; char *chars; struct attr *attrs; g_return_if_fail (cgrid != NULL); g_return_if_fail (GMC_IS_CHAR_GRID (cgrid)); g_return_if_fail ((width > 0) && (height > 0)); if ((width == cgrid->width) && (height == cgrid->height)) return; if (cgrid->chars) g_free (cgrid->chars); if (cgrid->attrs) g_free (cgrid->attrs); cgrid->width = width; cgrid->height = height; chars = g_new (char, width * height); cgrid->chars = chars; attrs = g_new (struct attr, width * height); cgrid->attrs = attrs; for (i = 0; i < (width * height); i++) { chars[i] = ' '; attrs[i].fg = 0; attrs[i].bg = 0; attrs[i].fg_set = FALSE; attrs[i].bg_set = FALSE; } gtk_widget_queue_resize (GTK_WIDGET (cgrid)); }