/* $Id: gtkstatus.c,v 1.12 2003/08/02 14:44:52 j_ali Exp $ */ /* GTK+ NetHack Copyright (c) Issei Numata 1999-2000 Copyright (c) Slash'EM Development Team 2001-2003 GTK+ NetHack may be freely redistributed. See license for details. */ #include #include #include #include "winGTK.h" /****************************************************************************** The NhLight widget is a trivial widget that is used to indicate stat changed ******************************************************************************/ #define NH_TYPE_LIGHT nh_light_get_type() #define NH_LIGHT(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, \ NH_TYPE_LIGHT, NhLight) #define NH_LIGHT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, \ NH_TYPE_LIGHT, NhLightClass) #define NH_IS_LIGHT(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, \ NH_TYPE_LIGHT) #define NH_IS_LIGHT_CLASS(klass) G_TYPE_CHECK_CLASS_TYPE(klass, \ NH_TYPE_LIGHT) #define NH_LIGHT_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS(obj, \ NH_TYPE_LIGHT, NhLightClass) #define NH_LIGHT_MAX_CONTRAST 10 #define NH_LIGHT_WIDTH 4 #define NH_LIGHT_HEIGHT 16 typedef struct _NhLight { GtkMisc misc; int contrast; /* From 0 (none) to 10 (max), or -1 (transp) */ } NhLight; typedef struct _NhLightClass { GtkMiscClass parent_class; } NhLightClass; static gpointer parent_class; GType nh_light_get_type(void); GtkWidget *nh_light_new(void); void nh_light_set_contrast(NhLight *light, int contrast); static void nh_light_class_init(NhLightClass *klass); static void nh_light_init(NhLight *light); static gint nh_light_expose(GtkWidget *widget, GdkEventExpose *expose); static void nh_light_size_request(GtkWidget *widget, GtkRequisition *requisition); GType nh_light_get_type(void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof(NhLightClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc)nh_light_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(NhLight), 0, /* n_preallocs */ (GInstanceInitFunc)nh_light_init }; type = g_type_register_static(GTK_TYPE_MISC, "NhLight", &info, 0); } return type; } GtkWidget *nh_light_new(void) { return g_object_new(NH_TYPE_LIGHT, NULL); } void nh_light_set_contrast(NhLight *light, int contrast) { if (contrast < 0) contrast = 0; else if (contrast > NH_LIGHT_MAX_CONTRAST) contrast = NH_LIGHT_MAX_CONTRAST; if (light->contrast != contrast) { light->contrast = contrast; gtk_widget_queue_draw(GTK_WIDGET(light)); } } static void nh_light_class_init(NhLightClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); parent_class = g_type_class_peek_parent(klass); widget_class->expose_event = nh_light_expose; widget_class->size_request = nh_light_size_request; } static void nh_light_init(NhLight *light) { GTK_WIDGET_SET_FLAGS(light, GTK_NO_WINDOW); light->contrast = 0; } static gint nh_light_expose(GtkWidget *widget, GdkEventExpose *expose) { GtkMisc *misc = GTK_MISC(widget); NhLight *light = NH_LIGHT(widget); GdkGC *gc; GdkColormap *cmap; GdkColor color = {0}; GdkRectangle area; gfloat xalign; gint x, y; guint16 bg; if (GTK_WIDGET_MAPPED(widget)) { area = expose->area; if (!gdk_rectangle_intersect(&area, &widget->allocation, &area)) return FALSE; if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_LTR) xalign = misc->xalign; else xalign = 1.0 - misc->xalign; x = floor(0.5 + widget->allocation.x + misc->xpad + xalign * (widget->allocation.width - widget->requisition.width)); y = floor(0.5 + widget->allocation.y + misc->ypad + misc->yalign * (widget->allocation.height - widget->requisition.height)); gc = gdk_gc_new(widget->window); cmap = gdk_drawable_get_colormap(widget->window); if (!cmap) fprintf(stderr, "nh_light_expose: No colormap!\n"); color.red = bg = widget->style->bg[GTK_STATE_NORMAL].red; color.red += (65535L - bg) * light->contrast / NH_LIGHT_MAX_CONTRAST; color.green = bg = widget->style->bg[GTK_STATE_NORMAL].green; color.green += (65535L - bg) * light->contrast / NH_LIGHT_MAX_CONTRAST; color.blue = bg = widget->style->bg[GTK_STATE_NORMAL].blue; color.blue += (65535L - bg) * light->contrast / NH_LIGHT_MAX_CONTRAST; if (gdk_colormap_alloc_color(cmap, &color, FALSE, TRUE)) gdk_gc_set_foreground(gc, &color); else gdk_gc_set_foreground(gc, &widget->style->bg[GTK_STATE_NORMAL]); gdk_draw_rectangle(widget->window, gc, TRUE, x, y, NH_LIGHT_WIDTH, NH_LIGHT_HEIGHT); g_object_unref(gc); } } static void nh_light_size_request(GtkWidget *widget, GtkRequisition *requisition) { widget->requisition.width = NH_LIGHT_WIDTH + GTK_MISC(widget)->xpad * 2; widget->requisition.height = NH_LIGHT_HEIGHT + GTK_MISC(widget)->ypad * 2; GTK_WIDGET_CLASS(parent_class)->size_request(widget, requisition); } /******************************************************************************/ #define NH_BAR_WIDTH 150 #define NH_BAR_HEIGHT 8 #define STAT_ROWS 8 #define STAT_COLS 2 #define STAT_BARS 2 static GtkWidget *handle; static GtkWidget *frame; static GtkWidget *dlvl; static GtkWidget *stat_table; static GtkWidget *hbox2; static GtkWidget *vbox; static GtkWidget *levi; static GtkWidget *conf; static GtkWidget *blin; static GtkWidget *stun; static GtkWidget *hall; static GtkWidget *hung; static GtkWidget *sick; static GtkWidget *encu; static GtkWidget *slim; static GtkWidget *bar_table; static struct { GtkWidget *hbox, *vbox, *lbl, *area; GdkPixmap *pixmap; GdkGC *gc; } bar[STAT_BARS]; static struct { GtkLabel *label, *value; NhLight *light; } stat_widgets[STAT_COLS][STAT_ROWS]; static gint bar_expose_event(GtkWidget *widget, GdkEventExpose *event) { int i; for(i = 0; i < STAT_BARS; i++) if (widget == bar[i].area) break; if (i == STAT_BARS) return FALSE; gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], bar[i].pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return TRUE; } #if defined(MONITOR_HEAP) && defined(INTERNAL_MALLOC) static int stat_mem() { return monitor_heap_getmem(); } #endif #define STAT_COLUMN(n) (n) #define STAT_BAR(n) (STAT_COLS + (n)) #define STAT_FRAME (STAT_COLS + STAT_BARS + 1) #define STAT_DLEVEL (STAT_COLS + STAT_BARS + 2) #define STAT_HUNGER (STAT_COLS + STAT_BARS + 3) #define STAT_ENCUMBERANCE (STAT_COLS + STAT_BARS + 4) #define STAT_FLAGS (STAT_COLS + STAT_BARS + 5) struct nh_stat_tab { int where; char *label; char *quan; /* Name of quantity from game executable */ char *divisor; /* Value to show as max */ int vi,dvi; /* indecies into values array */ int row; gchar *oldvalue; } stat_tab[] = { {STAT_FRAME, "", "player", NULL}, {STAT_DLEVEL, "", "dlevel", NULL}, {STAT_HUNGER, "", "hunger", NULL}, {STAT_ENCUMBERANCE, "", "encumberance", NULL}, {STAT_FLAGS, "", "flags", NULL}, {STAT_BAR(1), "HP", "hp", "hpmax"}, {STAT_BAR(2), "MP", "pw", "pwmax"}, {STAT_COLUMN(1), "AC", "ac", NULL}, {STAT_COLUMN(1), "GOLD", "gold", NULL}, {STAT_COLUMN(1), "LEVEL", "elevel", NULL}, {STAT_COLUMN(1), "HD", "hitdice", NULL}, {STAT_COLUMN(1), "ALIGN", "align", NULL}, {STAT_COLUMN(1), "TIME", "time", NULL}, {STAT_COLUMN(1), "EXP", "experience", NULL}, {STAT_COLUMN(1), "SCORE", "score", NULL}, {STAT_COLUMN(1), "WEIGHT", "weight", "capacity"}, {STAT_COLUMN(2), "STR", "strength", NULL}, {STAT_COLUMN(2), "DEX", "dexterity", NULL}, {STAT_COLUMN(2), "CON", "constitution", NULL}, {STAT_COLUMN(2), "INT", "intelligence", NULL}, {STAT_COLUMN(2), "WIS", "wisdom", NULL}, {STAT_COLUMN(2), "CHA", "charisma", NULL}, #if defined(MONITOR_HEAP) && defined(INTERNAL_MALLOC) {STAT_COLUMN(2), "MEM", NULL, NULL}, #endif }; static int in_trouble = FALSE; static stat_tab_hp = -1; boolean nh_status_in_trouble(void) { return in_trouble; } char * nh_status_last_displayed(char *quan) { int i; for(i = 0; i < SIZE(stat_tab); i++) if (!strcmp(quan, stat_tab[i].quan)) return stat_tab[i].oldvalue; return NULL; } static void nh_status_reconfig(nv, values) int nv; char **values; { int i, j, k; int rowno[STAT_COLS]; for(i = 0; i < STAT_COLS; i++) { rowno[i] = 0; } for(i = 0; i < SIZE(stat_tab); i++) { stat_tab[i].vi = stat_tab[i].dvi = -1; for(j = 0; j < nv; j++) { if (stat_tab[i].divisor && !strcmp(stat_tab[i].divisor, values[j])) { stat_tab[i].dvi = j; if (!stat_tab[i].quan || stat_tab[i].vi >= 0) break; } else if (stat_tab[i].quan && !strcmp(stat_tab[i].quan, values[j])) { stat_tab[i].vi = j; if (!strcmp(stat_tab[i].quan, "hp")) stat_tab_hp = i; k = stat_tab[i].where - STAT_COLUMN(1); if (k >=0 && k < STAT_COLS) { if (stat_tab[i].row != rowno[k]) { stat_tab[i].row = rowno[k]; g_free(stat_tab[i].oldvalue); stat_tab[i].oldvalue = (gchar *)0; } gtk_label_set_text(stat_widgets[k][rowno[k]++].label, stat_tab[i].label); } if (!stat_tab[i].divisor || stat_tab[i].dvi >= 0) break; } } if (stat_tab[i].vi < 0 && stat_tab[i].oldvalue) { g_free(stat_tab[i].oldvalue); stat_tab[i].oldvalue = (gchar *)0; } } for(i = 0; i < STAT_COLS; i++) { for(j = rowno[i]; j < STAT_ROWS; j++) { gtk_label_set_text(stat_widgets[i][j].label, ""); gtk_label_set_text(stat_widgets[i][j].value, ""); } } } static light_timer(gpointer data) { int i, j; int retval = FALSE; NhLight *light; for(j = 0; j < STAT_ROWS; j++) for(i = 0; i < STAT_COLS; i++) { light = stat_widgets[i][j].light; if (light && light->contrast > 0) { nh_light_set_contrast(light, light->contrast - 1); if (light->contrast > 0) retval = TRUE; } } *(int *)data = retval; return retval; } void GTK_ext_status(reconfig, nv, values) int reconfig, nv; const char **values; { int i, j, val, dval; unsigned long fl; char buf[NH_BUFSIZ]; char *Dummy = NULL; const char *value; gchar *str = NULL, *s; GdkRectangle update_rect; static int light_timer_active = 0; int start_light_timer = 0; if (reconfig) { nh_status_reconfig(nv, values); return; } in_trouble = FALSE; for(i = 0; i < SIZE(stat_tab); i++) { if (stat_tab[i].vi < 0) continue; value = values[stat_tab[i].vi]; if (stat_tab[i].dvi >= 0) s = g_strdup_printf("%s/%s", value, values[stat_tab[i].dvi]); else s = g_strdup(value); j = stat_tab[i].where - STAT_BAR(1); if (j >= 0 && j < 2 && *stat_tab[i].label) str = g_strdup_printf("%s %s", stat_tab[i].label, s); else if (*s && (stat_tab[i].where == STAT_HUNGER || stat_tab[i].where == STAT_ENCUMBERANCE)) str = g_strdup_printf(" %s ", s); else { str = s; s = (gchar *)0; } g_free(s); if (!stat_tab[i].oldvalue || strcmp(str, stat_tab[i].oldvalue)) { switch(stat_tab[i].where) { case STAT_COLUMN(1): case STAT_COLUMN(2): j = stat_tab[i].where - STAT_COLUMN(1); gtk_label_set_text(stat_widgets[j][stat_tab[i].row].value, str); nh_light_set_contrast( stat_widgets[j][stat_tab[i].row].light, NH_LIGHT_MAX_CONTRAST); start_light_timer++; break; case STAT_BAR(1): case STAT_BAR(2): j = stat_tab[i].where - STAT_BAR(1); update_rect.x = 0; update_rect.y = 0; update_rect.width = NH_BAR_WIDTH; update_rect.height = NH_BAR_HEIGHT; sscanf(value, "%d", &val); if (stat_tab[i].dvi >= 0) { sscanf(values[stat_tab[i].dvi], "%d", &dval); if (i == stat_tab_hp && (val <= 5 || val * 7 <= dval)) in_trouble = TRUE; val = dval ? val * NH_BAR_WIDTH / dval : 0; } gdk_draw_rectangle(bar[j].pixmap, bar[j].gc, TRUE, 0, 0, val, NH_BAR_HEIGHT); if (val < NH_BAR_WIDTH) gdk_draw_rectangle(bar[j].pixmap, bar[j].area->style->black_gc, TRUE, val, 0, NH_BAR_WIDTH - val, NH_BAR_HEIGHT); gtk_widget_draw(bar[j].area, &update_rect); gtk_label_set_text(GTK_LABEL(bar[j].lbl), str); break; case STAT_FRAME: gtk_frame_set_label(GTK_FRAME(frame), str); break; case STAT_DLEVEL: gtk_label_set_text(GTK_LABEL(dlvl), str); break; case STAT_HUNGER: gtk_label_set_text(GTK_LABEL(hung), str); break; case STAT_ENCUMBERANCE: gtk_label_set_text(GTK_LABEL(encu), str); break; case STAT_FLAGS: sscanf(value, "%lX", &fl); gtk_label_set_text(GTK_LABEL(levi), (fl & RAW_STAT_LEVITATION) ? " Levitation " : ""); gtk_label_set_text(GTK_LABEL(blin), (fl & RAW_STAT_BLIND) ? " Blind " : ""); gtk_label_set_text(GTK_LABEL(conf), (fl & RAW_STAT_CONFUSION) ? " Confused " : ""); gtk_label_set_text(GTK_LABEL(sick), (fl & RAW_STAT_FOODPOIS) ? " FoodPois " : (fl & RAW_STAT_ILL) ? " Ill " : ""); gtk_label_set_text(GTK_LABEL(stun), (fl & RAW_STAT_STUNNED) ? " Stunned " : ""); gtk_label_set_text(GTK_LABEL(hall), (fl & RAW_STAT_HALLUCINATION) ? " Hallucination " : ""); gtk_label_set_text(GTK_LABEL(slim), (fl & RAW_STAT_SLIMED) ? " Slimed " : ""); break; } g_free(stat_tab[i].oldvalue); stat_tab[i].oldvalue = str; str = (gchar *)0; } g_free(str); } if (!light_timer_active && start_light_timer) g_timeout_add(200, light_timer, &light_timer_active); } void nh_status_destroy() { /* * [ALI] Most objects will be destroyed when the status widget is * destroyed (ie., as part of destroying the main window). */ int i; for(i = 0; i < STAT_BARS; i++) gdk_pixmap_unref(bar[i].pixmap); } GtkWidget * nh_status_new() { extern GtkWidget *main_window; GtkWidget *hbox, *w; GdkPixbuf *light; int i, j; gchar *text[3] = { "", "", NULL}; handle = gtk_handle_box_new(); GTK_HANDLE_BOX(handle)->shrink_on_detach = 1; frame = nh_gtk_new_and_add(gtk_frame_new(NULL), handle, ""); gtk_container_set_border_width(GTK_CONTAINER(frame), NH_PAD); vbox = nh_gtk_new_and_add(gtk_vbox_new(FALSE, 0), frame, ""); gtk_container_set_border_width(GTK_CONTAINER(vbox), NH_PAD); dlvl = nh_gtk_new_and_pack(gtk_label_new(""), vbox, "", FALSE, FALSE, 0); bar_table = nh_gtk_new_and_add(gtk_table_new(2, 2, FALSE), vbox, ""); for(i = 0; i < STAT_BARS; i++) { bar[i].hbox = nh_gtk_new_and_attach(gtk_hbox_new(FALSE, 0), bar_table, "", 0, 1, i, i + 1); for(j = 0; j < SIZE(stat_tab); j++) if (stat_tab[j].where == STAT_BAR(i + 1)) break; bar[i].lbl = nh_gtk_new_and_pack(gtk_label_new(stat_tab[j].label), bar[i].hbox, "", FALSE, FALSE, 0); bar[i].vbox = nh_gtk_new_and_attach(gtk_vbox_new(TRUE, 0), bar_table, "", 1, 2, i, i + 1); bar[i].area = nh_gtk_new_and_pack(gtk_drawing_area_new(), bar[i].vbox, "", FALSE, FALSE, 0); gtk_signal_connect(GTK_OBJECT(bar[i].area), "expose_event", GTK_SIGNAL_FUNC(bar_expose_event), NULL); bar[i].gc = gdk_gc_new(main_window->window); gtk_drawing_area_size(GTK_DRAWING_AREA(bar[i].area), NH_BAR_WIDTH, NH_BAR_HEIGHT); bar[i].pixmap = gdk_pixmap_new(main_window->window, NH_BAR_WIDTH, NH_BAR_HEIGHT, -1); } /* * [ALI] Spacing in the stat table is a little unconventional. What we * want to achive is: * * P< label>PP< value>PPPP< label>PP< value>PPP * * (where P is a padding of width NH_PAD). But we want to achive this * without using a column for the light (so that the table can be * homogeneous which looks better and keeps the resizing simple). * * We achieve this with a combination of table spacing and putting * the value and its light into an hbox. * * |P< label>P|P |< value>PP|P |P< label>P|P |< value>PP|P * * Column 0: Contents: P< label>P, Spacing: P * Column 1: Contents: < value>PP, Spacing: P * Column 2: Contents: P< label>P, Spacing: P * Column 3: Contents: < value>PP, Spacing: N/A * * The final spacing of P on the right is achieved by placing the * table in a horizontal box with an empty box. * * Note: It would be easier to move the padding out of the label * columns and then the now symetric padding could be added by * setting the padding of the table itself: * * P|< label>|PP |< value>PP|PP |< label>|PP |< value>PP|P * * However, this means that the width of the labels will be expanded * to be at least < value>PP rather than < value> * which the current scheme achieves. Ideally, we'd like it to be * simply < value>, but that doesn't seem possible. */ hbox = nh_gtk_new_and_pack(gtk_hbox_new(FALSE, 0), vbox, "", TRUE, FALSE, 0); gtk_box_set_spacing(GTK_BOX(hbox), NH_PAD); stat_table = nh_gtk_new_and_pack( gtk_table_new(STAT_ROWS, STAT_COLS * 2, TRUE), hbox, "", TRUE, FALSE, 0); nh_gtk_new_and_pack(gtk_vbox_new(FALSE, 0), hbox, "", FALSE, FALSE, 0); gtk_table_set_col_spacings(GTK_TABLE(stat_table), NH_PAD); for(j = 0; j < STAT_COLS; j++) for(i = 0; i < STAT_ROWS; i++) { w = nh_gtk_new_and_attach(gtk_label_new(""), stat_table, "", j * 2, j * 2 + 1, i, i + 1); gtk_misc_set_alignment(GTK_MISC(w), 1, 1); gtk_misc_set_padding(GTK_MISC(w), NH_PAD, 0); stat_widgets[j][i].label = GTK_LABEL(w); hbox = nh_gtk_new_and_attach(gtk_hbox_new(FALSE, 0), stat_table, "", j * 2 + 1, j * 2 + 2, i, i + 1); gtk_box_set_spacing(GTK_BOX(hbox), 2 * NH_PAD); w = nh_gtk_new_and_pack(gtk_label_new(""), hbox, "", TRUE, TRUE, 0); gtk_misc_set_alignment(GTK_MISC(w), 1, 1); stat_widgets[j][i].value = GTK_LABEL(w); w = nh_gtk_new_and_pack(nh_light_new(), hbox, "", FALSE, FALSE, 0); stat_widgets[j][i].light = NH_LIGHT(w); } hbox2 = nh_gtk_new_and_pack(gtk_hbox_new(FALSE, 0), vbox, "", FALSE, FALSE, 0); hung = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); levi = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); conf = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); blin = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); stun = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); hall = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); sick = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); slim = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); encu = nh_gtk_new_and_pack(gtk_label_new(""), hbox2, "", FALSE, FALSE, 0); /* Clear HP/MP bars */ for(i = 0; i < STAT_BARS; i++) { GdkRectangle update_rect; update_rect.x = 0; update_rect.y = 0; update_rect.width = NH_BAR_WIDTH; update_rect.height = NH_BAR_HEIGHT; gdk_gc_set_foreground(bar[i].gc, &nh_color[MAP_BLACK]); gdk_draw_rectangle(bar[i].pixmap, bar[i].gc, TRUE, 0, 0, NH_BAR_WIDTH, NH_BAR_HEIGHT); gtk_widget_draw(bar[i].area, &update_rect); gdk_gc_set_foreground(bar[i].gc, &nh_color[i ? CLR_GREEN : CLR_BLUE]); } for(i = 0; i < SIZE(stat_tab); i++) stat_tab[i].vi = stat_tab[i].dvi = -1; return handle; }