|
Crossfire Client, Trunk
R18666
|
00001 const char * const rcsid_gtk2_info_c = 00002 "$Id: info.c 14516 2011-06-06 21:27:21Z ryo_saeba $"; 00003 /* 00004 Crossfire client, a client program for the crossfire program. 00005 00006 Copyright (C) 2005-2011 Mark Wedel & Crossfire Development Team 00007 00008 This program is free software; you can redistribute it and/or modify 00009 it under the terms of the GNU General Public License as published by 00010 the Free Software Foundation; either version 2 of the License, or 00011 (at your option) any later version. 00012 00013 This program is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00016 GNU General Public License for more details. 00017 00018 You should have received a copy of the GNU General Public License 00019 along with this program; if not, write to the Free Software 00020 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00021 00022 The author can be reached via e-mail to crossfire@metalforge.org 00023 */ 00024 00030 #ifdef HAVE_CONFIG_H 00031 # include <config.h> 00032 #endif 00033 00034 #include <gtk/gtk.h> 00035 #include <glade/glade.h> 00036 00037 #include "client.h" 00038 00039 #include "image.h" 00040 #include "main.h" 00041 #include "gtk2proto.h" 00042 00043 00044 00048 static const char *font_style_names[NUM_FONTS] = { 00049 "info_font_normal", 00050 "info_font_arcane", 00051 "info_font_strange", 00052 "info_font_fixed", 00053 "info_font_hand" 00054 }; 00069 #define NUM_TEXT_VIEWS 2 00070 00071 extern const char * const usercolorname[NUM_COLORS]; 00072 00073 Info_Pane info_pane[NUM_TEXT_VIEWS]; 00074 00075 extern const char * const colorname[NUM_COLORS]; 00076 00077 /* 00078 * The idea behind the msg_type_names is to provide meaningful names that the 00079 * client can use to load/save these values, in particular, the GTK-V2 client 00080 * uses these to find styles on how to draw the different msg types. We could 00081 * set this up as a two dimension array instead, but that probably is not as 00082 * efficient when the number of subtypes varies so wildly. The 0 subtypes are 00083 * used for general cases (describe the entire class of those message types). 00084 * Note also that the names here are verbose - the actual code that uses these 00085 * will expand it further. In practice, there should never be entries with 00086 * both the same type/subtype (each subtype should be unique) - if so, the 00087 * results are probably unpredictable on which one the code would use. 00088 */ 00089 #include "msgtypes.h" 00090 00091 static int max_subtype=0, has_style=0; 00092 00106 static void 00107 message_callback(int orig_color, int type, int subtype, char *message); 00108 00109 GtkWidget *msgctrl_window; 00113 GtkWidget *msgctrl_table; 00117 #define MESSAGE_BUFFER_COUNT 10 00121 #define MESSAGE_BUFFER_SIZE 56 00125 #define COUNT_BUFFER_SIZE 16 00130 #define MESSAGE_COUNT_MAX 16 00135 #define MESSAGE_AGE_MAX 16 00149 struct info_buffer_t 00150 { 00151 int age; 00155 int count; 00160 int orig_color; 00163 int type; 00166 int subtype; 00169 char message[MESSAGE_BUFFER_SIZE]; 00171 } info_buffer[MESSAGE_BUFFER_COUNT]; 00181 typedef struct 00182 { 00183 GtkWidget* ptr; 00185 guint state; 00187 const guint default_state; 00189 } buffer_parameter_t; 00190 00197 struct buffer_control_t 00198 { 00199 buffer_parameter_t count; 00200 buffer_parameter_t timer; 00201 } buffer_control = 00202 { 00203 /* 00204 * { uninitialized_pointer, uninitialized_state, default_value }, 00205 */ 00206 { NULL, 0, MESSAGE_COUNT_MAX }, 00207 { NULL, 0, MESSAGE_AGE_MAX } 00208 }; 00213 typedef struct 00214 { 00215 GtkWidget* ptr; 00217 gboolean state; 00219 } boolean_widget_t; 00220 00225 typedef struct 00226 { 00227 boolean_widget_t buffer; 00230 boolean_widget_t pane[NUM_TEXT_VIEWS]; 00234 } message_control_t; 00235 00236 message_control_t 00237 msgctrl_widgets[MSG_TYPE_LAST-1]; 00252 struct msgctrl_data_t 00253 { 00254 const char * description; 00261 const gboolean buffer; 00267 const gboolean pane[NUM_TEXT_VIEWS]; 00274 } msgctrl_defaults[MSG_TYPE_LAST-1] = 00280 { 00281 /* 00282 * { "description", buffer, { pane[0], pane[1] } }, 00283 */ 00284 { "Books", FALSE, { TRUE, FALSE } }, 00285 { "Cards", FALSE, { TRUE, FALSE } }, 00286 { "Paper", FALSE, { TRUE, FALSE } }, 00287 { "Signs & Magic Mouths", FALSE, { TRUE, FALSE } }, 00288 { "Monuments", FALSE, { TRUE, FALSE } }, 00289 { "Dialogs (Altar/NPC/Magic Ear)" , FALSE, { TRUE, FALSE } }, 00290 { "Message of the day", FALSE, { TRUE, FALSE } }, 00291 { "Administrative", FALSE, { TRUE, FALSE } }, 00292 { "Shops", TRUE, { TRUE, FALSE } }, 00293 { "Command responses", FALSE, { TRUE, FALSE } }, 00294 { "Changes to attributes", TRUE, { TRUE, TRUE } }, 00295 { "Skill-related messages", TRUE, { TRUE, FALSE } }, 00296 { "Apply results", TRUE, { TRUE, FALSE } }, 00297 { "Attack results", TRUE, { TRUE, FALSE } }, 00298 { "Player communication", FALSE, { TRUE, TRUE } }, 00299 { "Spell results", TRUE, { TRUE, FALSE } }, 00300 { "Item information", TRUE, { TRUE, FALSE } }, 00301 { "Miscellaneous", TRUE, { TRUE, FALSE } }, 00302 { "Victim notification", FALSE, { TRUE, TRUE } }, 00303 { "Client-generated messages", FALSE, { TRUE, FALSE } } 00304 }; 00323 void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, GtkStyle *base_style) 00324 { 00325 g_object_set(tag, "foreground-set", FALSE, NULL); 00326 g_object_set(tag, "background-set", FALSE, NULL); 00327 g_object_set(tag, "font-desc", NULL, NULL); 00328 00329 if (memcmp( 00330 &style->fg[GTK_STATE_NORMAL], 00331 &base_style->fg[GTK_STATE_NORMAL], 00332 sizeof(GdkColor))) 00333 00334 g_object_set(tag, "foreground-gdk", &style->fg[GTK_STATE_NORMAL], NULL); 00335 00336 if (memcmp( 00337 &style->bg[GTK_STATE_NORMAL], 00338 &base_style->bg[GTK_STATE_NORMAL], 00339 sizeof(GdkColor))) 00340 00341 g_object_set(tag, "background-gdk", &style->bg[GTK_STATE_NORMAL], NULL); 00342 00343 if (style->font_desc != base_style->font_desc) 00344 g_object_set(tag, "font-desc", style->font_desc, NULL); 00345 } 00346 00356 void add_tags_to_textbuffer(Info_Pane *pane, GtkTextBuffer *textbuf) 00357 { 00358 int i; 00359 00360 if (textbuf) pane->textbuffer = textbuf; 00361 00362 for (i = 0; i < MSG_TYPE_LAST; i++) 00363 pane->msg_type_tags[i] = 00364 calloc(max_subtype + 1, sizeof(GtkTextTag*)); 00365 00366 for (i = 0; i < NUM_FONTS; i++) 00367 pane->font_tags[i] = NULL; 00368 00369 for (i = 0; i < NUM_COLORS; i++) 00370 pane->color_tags[i] = NULL; 00371 /* 00372 * These tag definitions never change - we don't get them from the 00373 * settings file (maybe we should), so we only need to allocate them once. 00374 */ 00375 pane->bold_tag = 00376 gtk_text_buffer_create_tag(pane->textbuffer, 00377 "bold", "weight", PANGO_WEIGHT_BOLD, NULL); 00378 00379 pane->italic_tag = 00380 gtk_text_buffer_create_tag(pane->textbuffer, 00381 "italic", "style", PANGO_STYLE_ITALIC, NULL); 00382 00383 pane->underline_tag = 00384 gtk_text_buffer_create_tag(pane->textbuffer, 00385 "underline", "underline", PANGO_UNDERLINE_SINGLE, NULL); 00386 /* 00387 * This is really a convenience - so multiple tags may be passed when 00388 * drawing text, but once a NULL tag is found, that signifies no more 00389 * tags. Rather than having to set up an array to pass in, instead, an 00390 * empty tag is passed in so that calling semantics remain consistent, 00391 * just differing in what tags are passed in. 00392 */ 00393 if (!pane->default_tag) 00394 pane->default_tag = 00395 gtk_text_buffer_create_tag(pane->textbuffer, "default", NULL); 00396 } 00397 00406 void add_style_to_textbuffer(Info_Pane *pane, GtkStyle *base_style) 00407 { 00408 int i; 00409 char style_name[MAX_BUF]; 00410 GtkStyle *tmp_style; 00411 00412 if (base_style) { 00413 /* 00414 * Old message/color support. 00415 */ 00416 for (i = 0; i < NUM_COLORS; i++) { 00417 snprintf(style_name, MAX_BUF, "info_%s", usercolorname[i]); 00418 00419 tmp_style = 00420 gtk_rc_get_style_by_paths( 00421 gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE); 00422 00423 if (tmp_style) { 00424 if (!pane->color_tags[i]) { 00425 pane->color_tags[i] = 00426 gtk_text_buffer_create_tag( 00427 pane->textbuffer, NULL, NULL); 00428 } 00429 set_text_tag_from_style( 00430 pane->color_tags[i], tmp_style, base_style); 00431 } else { 00432 if (pane->color_tags[i]) { 00433 gtk_text_tag_table_remove( 00434 gtk_text_buffer_get_tag_table( 00435 pane->textbuffer), pane->color_tags[i]); 00436 pane->color_tags[i] = NULL; 00437 } 00438 } 00439 } 00440 00441 /* Font type support */ 00442 for (i = 0; i < NUM_FONTS; i++) { 00443 tmp_style = 00444 gtk_rc_get_style_by_paths( 00445 gtk_settings_get_default(), 00446 NULL, font_style_names[i], G_TYPE_NONE); 00447 00448 if (tmp_style) { 00449 if (!pane->font_tags[i]) { 00450 pane->font_tags[i] = 00451 gtk_text_buffer_create_tag( 00452 pane->textbuffer, NULL, NULL); 00453 } 00454 set_text_tag_from_style( 00455 pane->font_tags[i], tmp_style, base_style); 00456 } else { 00457 if (pane->font_tags[i]) { 00458 gtk_text_tag_table_remove( 00459 gtk_text_buffer_get_tag_table(pane->textbuffer), 00460 pane->font_tags[i]); 00461 pane->font_tags[i] = NULL; 00462 } 00463 } 00464 } 00465 } else { 00466 00467 for (i = 0; i < NUM_COLORS; i++) { 00468 if (pane->color_tags[i]) { 00469 gtk_text_tag_table_remove( 00470 gtk_text_buffer_get_tag_table( 00471 pane->textbuffer), pane->color_tags[i]); 00472 pane->color_tags[i] = NULL; 00473 } 00474 } 00475 /* Font type support */ 00476 for (i = 0; i < NUM_FONTS; i++) { 00477 if (pane->font_tags[i]) { 00478 gtk_text_tag_table_remove( 00479 gtk_text_buffer_get_tag_table( 00480 pane->textbuffer), pane->font_tags[i]); 00481 pane->font_tags[i] = NULL; 00482 } 00483 } 00484 } 00485 } 00486 00496 void info_get_styles(void) 00497 { 00498 unsigned int i, j; 00499 static int has_init=0; 00500 GtkStyle *tmp_style, *base_style[NUM_TEXT_VIEWS]; 00501 char style_name[MAX_BUF]; 00502 00503 if (!has_init) { 00504 /* 00505 * We want to set up a 2 dimensional array of msg_type_tags to 00506 * correspond to all the types/subtypes, so looking up any value is 00507 * really easy. We know the size of the types, but don't know the 00508 * number of subtypes - no single declared value. So we just parse 00509 * the msg_type_names to find that, then know how big to make the 00510 * other dimension. We could allocate different number of entries for 00511 * each type, but that makes processing a bit harder (no single value 00512 * on the number of subtypes), and this extra memory usage shouldn't 00513 * really be at all significant. 00514 */ 00515 for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) { 00516 if (msg_type_names[i].subtype > max_subtype) 00517 max_subtype = msg_type_names[i].subtype; 00518 } 00519 for (j = 0; j < NUM_TEXT_VIEWS; j++) { 00520 add_tags_to_textbuffer(&info_pane[j], NULL); 00521 00522 } 00523 has_init = 1; 00524 } 00525 for (i = 0; i < NUM_TEXT_VIEWS; i++) { 00526 base_style[i] = 00527 gtk_rc_get_style_by_paths( 00528 gtk_settings_get_default(), 00529 NULL, "info_default", G_TYPE_NONE); 00530 } 00531 if (!base_style[0]) { 00532 LOG(LOG_INFO, "info.c::info_get_styles", 00533 "Unable to find base style info_default" 00534 " - will not process most info tag styles!"); 00535 } 00536 00537 has_style = 0; 00538 00539 /* 00540 * If we don't have a base style tag, we can't process these other tags, 00541 * as we need to be able to do a difference, and doing a difference from 00542 * nothing (meaning, taking everything in style) still doesn't work really 00543 * well. 00544 */ 00545 if (base_style[0]) { 00546 /* 00547 * This processes the type/subtype styles. We look up the names in 00548 * the array to find what name goes to what number. 00549 */ 00550 for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) { 00551 int type, subtype; 00552 00553 snprintf(style_name, sizeof(style_name), 00554 "msg_%s", msg_type_names[i].style_name); 00555 type = msg_type_names[i].type; 00556 subtype = msg_type_names[i].subtype; 00557 00558 tmp_style = 00559 gtk_rc_get_style_by_paths( 00560 gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE); 00561 00562 for (j = 0; j < NUM_TEXT_VIEWS; j++) { 00563 /* 00564 * If we have a style for this, update the tag that goes along 00565 * with this. If we don't have a tag for this style, create 00566 * it. 00567 */ 00568 if (tmp_style) { 00569 if (!info_pane[j].msg_type_tags[type][subtype]) { 00570 info_pane[j].msg_type_tags[type][subtype] = 00571 gtk_text_buffer_create_tag( 00572 info_pane[j].textbuffer, NULL, NULL); 00573 } 00574 set_text_tag_from_style( 00575 info_pane[j].msg_type_tags[type][subtype], 00576 tmp_style, base_style[j]); 00577 has_style = 1; 00578 } else { 00579 /* 00580 * No setting for this type/subtype, so remove tag if 00581 * there is one. 00582 */ 00583 if (info_pane[j].msg_type_tags[type][subtype]) { 00584 gtk_text_tag_table_remove( 00585 gtk_text_buffer_get_tag_table( 00586 info_pane[j].textbuffer), 00587 info_pane[j].msg_type_tags[type][subtype]); 00588 info_pane[j].msg_type_tags[type][subtype] = NULL; 00589 } 00590 } 00591 add_style_to_textbuffer(&info_pane[j], base_style[j]); 00592 } 00593 } 00594 } else { 00595 /* 00596 * There is no base style - this should not normally be the case with 00597 * any real setting files, but certainly can be the case if the user 00598 * selected the 'None' setting. So in this case, we just free all the 00599 * text tags. 00600 */ 00601 has_style = 0; 00602 for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) { 00603 int type, subtype; 00604 00605 type = msg_type_names[i].type; 00606 subtype = msg_type_names[i].subtype; 00607 00608 for (j = 0; j < NUM_TEXT_VIEWS; j++) { 00609 if (info_pane[j].msg_type_tags[type][subtype]) { 00610 gtk_text_tag_table_remove( 00611 gtk_text_buffer_get_tag_table( 00612 info_pane[j].textbuffer), 00613 info_pane[j].msg_type_tags[type][subtype]); 00614 info_pane[j].msg_type_tags[type][subtype] = NULL; 00615 } 00616 add_style_to_textbuffer(&info_pane[j], NULL); 00617 } 00618 } 00619 } 00620 } 00621 00628 void info_init(GtkWidget *window_root) 00629 { 00630 int i; 00631 GtkTextIter end; 00632 char widget_name[MAX_BUF]; 00633 GladeXML *xml_tree; 00634 00635 xml_tree = glade_get_widget_tree(GTK_WIDGET(window_root)); 00636 for (i = 0; i < NUM_TEXT_VIEWS; i++) { 00637 snprintf(widget_name, MAX_BUF, "textview_info%d", i+1); 00638 info_pane[i].textview = glade_xml_get_widget(xml_tree, widget_name); 00639 00640 snprintf(widget_name, MAX_BUF, "scrolledwindow_textview%d", i+1); 00641 00642 info_pane[i].scrolled_window = 00643 glade_xml_get_widget(xml_tree, widget_name); 00644 00645 gtk_text_view_set_wrap_mode( 00646 GTK_TEXT_VIEW(info_pane[i].textview), GTK_WRAP_WORD); 00647 00648 info_pane[i].textbuffer = 00649 gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_pane[i].textview)); 00650 00651 info_pane[i].adjustment = 00652 gtk_scrolled_window_get_vadjustment( 00653 GTK_SCROLLED_WINDOW(info_pane[i].scrolled_window)); 00654 00655 gtk_text_buffer_get_end_iter(info_pane[i].textbuffer, &end); 00656 00657 info_pane[i].textmark = 00658 gtk_text_buffer_create_mark( 00659 info_pane[i].textbuffer, NULL, &end, FALSE); 00660 00661 gtk_widget_realize(info_pane[i].textview); 00662 } 00663 00664 /*info_get_styles();*/ 00665 info_buffer_init(); 00666 00667 /* Register callbacks for all message types */ 00668 for (i = 0; i < MSG_TYPE_LAST; i++) 00669 setTextManager(i, message_callback); 00670 } 00671 00689 static void add_to_textbuf(Info_Pane *pane, const char *message, 00690 int type, int subtype, 00691 int bold, int italic, 00692 int font, const char *color, int underline) 00693 { 00694 GtkTextIter end; 00695 GdkRectangle rect; 00696 int scroll_to_end=0, color_num; 00697 GtkTextTag *color_tag=NULL, *type_tag=NULL; 00698 00699 /* 00700 * Lets see if the defined color matches any of our defined colors. If we 00701 * get a match, set color_tag. If color_tag is null, we either don't have 00702 * a match, we don't have a defined tag for the color, or we don't have a 00703 * color, use the default tag. It would be nice to know if color is a sub 00704 * value set with [color] tag, or is part of the message itself - if we're 00705 * just being passed NDI_RED in the draw_ext_info from the server, we 00706 * really don't care about that - the type/subtype styles should really be 00707 * what determines what color to use. 00708 */ 00709 if (color) { 00710 for (color_num = 0; color_num < NUM_COLORS; color_num++) 00711 if (!strcasecmp(usercolorname[color_num], color)) 00712 break; 00713 if (color_num < NUM_COLORS) 00714 color_tag = pane->color_tags[color_num]; 00715 } 00716 00717 if (!color_tag) 00718 color_tag = pane->default_tag; 00719 00720 /* 00721 * Following block of code deals with the type/subtype. First, we check 00722 * and make sure the passed in values are legal. If so, first see if 00723 * there is a particular style for the type/subtype combo, if not, fall 00724 * back to one just for the type. 00725 */ 00726 type_tag = pane->default_tag; 00727 00728 /* Clear subtype on MSG_TYPE_CLIENT if max_subtype is not set 00729 * Errors are generated during initialization, before max_subtype 00730 * has been set, so we can not route to a specific pane. 00731 * We also want to make sure we do not hit the pane->msg_type_tags 00732 * code, as that is not initialzed yet. 00733 */ 00734 if (type == MSG_TYPE_CLIENT && !max_subtype) { 00735 subtype=0; 00736 } 00737 else if (type >= MSG_TYPE_LAST 00738 || subtype >= max_subtype 00739 || type < 0 || subtype < 0 ) { 00740 LOG(LOG_ERROR, "info.c::add_to_textbuf", 00741 "type (%d) >= MSG_TYPE_LAST (%d) or " 00742 "subtype (%d) >= max_subtype (%d)\n", 00743 type, MSG_TYPE_LAST, subtype, max_subtype); 00744 } else { 00745 if (pane->msg_type_tags[type][subtype]) 00746 type_tag = pane->msg_type_tags[type][subtype]; 00747 else if (pane->msg_type_tags[type][0]) 00748 type_tag = pane->msg_type_tags[type][0]; 00749 } 00750 00751 gtk_text_view_get_visible_rect( 00752 GTK_TEXT_VIEW(pane->textview), &rect); 00753 00754 /* Simple panes (like those of the login windows) don't have adjustments 00755 * set (and if they did, we wouldn't want to scroll to end in any case), 00756 * so check here on what to do. 00757 */ 00758 if (pane->adjustment && 00759 (pane->adjustment->value + rect.height) >= pane->adjustment->upper) 00760 scroll_to_end = 1; 00761 00762 gtk_text_buffer_get_end_iter(pane->textbuffer, &end); 00763 00764 gtk_text_buffer_insert_with_tags( 00765 pane->textbuffer, &end, message, strlen(message), 00766 bold ? pane->bold_tag : pane->default_tag, 00767 italic ? pane->italic_tag : pane->default_tag, 00768 underline ? pane->underline_tag : pane->default_tag, 00769 pane->font_tags[font] ? 00770 pane->font_tags[font] : pane->default_tag, 00771 color_tag, type_tag, NULL); 00772 00773 if (scroll_to_end) 00774 gtk_text_view_scroll_mark_onscreen( 00775 GTK_TEXT_VIEW(pane->textview), pane->textmark); 00776 } 00777 00793 void add_marked_text_to_pane(Info_Pane *pane, const char *message, int type, int subtype, int orig_color) 00794 { 00795 char *marker, *current, *original; 00796 int bold=0, italic=0, font=0, underline=0; 00797 const char *color=NULL; 00802 current = strdup(message); 00803 original = current; /* Just so we know what to free */ 00804 00805 /* 00806 * If there is no style information, or if a specific style has not been 00807 * set for the type/subtype of this message, allow orig_color to set the 00808 * color of the text. The orig_color handling here adds compatibility 00809 * with former draw_info() calls that gave a color hint. The color hint 00810 * still works now in the event that the theme has not set a style for the 00811 * message type. 00812 */ 00813 if (! has_style || pane->msg_type_tags[type][subtype] == 0) { 00814 if (orig_color <0 || orig_color>NUM_COLORS) { 00815 LOG(LOG_ERROR, "info.c::draw_ext_info", 00816 "Passed invalid color from server: %d, max allowed is %d\n", 00817 orig_color, NUM_COLORS); 00818 orig_color = 0; 00819 } else { 00820 /* 00821 * Not efficient - we have a number, but convert it to a string, 00822 * at which point add_to_textbuf() converts it back to a number. 00823 */ 00824 color = usercolorname[orig_color]; 00825 } 00826 } 00827 00828 while ((marker = strchr(current, '[')) != NULL) { 00829 *marker = 0; 00830 00831 if (strlen(current) > 0) 00832 add_to_textbuf(pane, current, type, subtype, 00833 bold, italic, font, color, underline); 00834 00835 current = marker + 1; 00836 00837 if ((marker = strchr(current, ']')) == NULL) { 00838 free(original); 00839 return; 00840 } 00841 00842 *marker = 0; 00843 if (!strcmp(current, "b")) bold = TRUE; 00844 else if (!strcmp(current, "/b")) bold = FALSE; 00845 else if (!strcmp(current, "i")) italic = TRUE; 00846 else if (!strcmp(current, "/i")) italic = FALSE; 00847 else if (!strcmp(current, "ul")) underline = TRUE; 00848 else if (!strcmp(current, "/ul")) underline = FALSE; 00849 else if (!strcmp(current, "fixed")) font = FONT_FIXED; 00850 else if (!strcmp(current, "arcane")) font = FONT_ARCANE; 00851 else if (!strcmp(current, "hand")) font = FONT_HAND; 00852 else if (!strcmp(current, "strange")) font = FONT_STRANGE; 00853 else if (!strcmp(current, "print")) font = FONT_NORMAL; 00854 else if (!strcmp(current, "/color")) color = NULL; 00855 else if (!strncmp(current, "color=", 6)) color = current + 6; 00856 else 00857 LOG(LOG_INFO, "info.c::message_callback", 00858 "unrecognized tag: [%s]\n", current); 00859 00860 current = marker + 1; 00861 } 00862 00863 add_to_textbuf( 00864 pane, current, type, subtype, 00865 bold, italic, font, color, underline); 00866 00867 add_to_textbuf( 00868 pane, "\n", type, subtype, 00869 bold, italic, font, color, underline); 00870 00871 free(original); 00872 00873 } 00874 00894 void draw_ext_info(int orig_color, int type, int subtype, const char *message) { 00895 int type_err=0; 00899 int pane; 00900 char *stamp = NULL; 00901 const char *draw = NULL; 00902 00903 if (want_config[CONFIG_TIMESTAMP]) { 00904 time_t curtime; 00905 struct tm *ltime; 00906 stamp = calloc(1, strlen(message) + 7); 00907 curtime = time(NULL); 00908 ltime = localtime(&curtime); 00909 strftime(stamp, 6, "%I:%M", ltime); 00910 strcat(stamp, " "); 00911 strcat(stamp, message); 00912 draw = stamp; 00913 } else { 00914 draw = message; 00915 } 00916 00917 /* 00918 * A valid message type is required to index into the msgctrl_widgets 00919 * array. If an invalid type is identified, log an error as any message 00920 * without a valid type should be hunted down and assigned an appropriate 00921 * type. 00922 */ 00923 if ((type < 1) || (type >= MSG_TYPE_LAST)) { 00924 LOG(LOG_ERROR, "info.c::draw_ext_info", 00925 "Invalid message type: %d", type); 00926 type_err = 1; 00927 } 00928 /* 00929 * Route messages to any one of the client information panels based on the 00930 * type of the message text. If a message with an invalid type comes in, 00931 * it goes to the main message panel (0). Messages can actually be sent 00932 * to more than one panel if the player so desires. 00933 */ 00934 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 00935 /* 00936 * If the message type is invalid, then the message must go to pane 0, 00937 * otherwise the msgctrl_widgets[].pane[pane].state setting determines 00938 * whether to send the message to a particular pane or not. The type 00939 * is one-based, so must be decremented when referencing 00940 * msgctrl_widgets[]; 00941 */ 00942 if (type_err != 0) { 00943 if (pane != 0) { 00944 break; 00945 } 00946 } else { 00947 if (msgctrl_widgets[type - 1].pane[pane].state == FALSE) 00948 continue; 00949 } 00950 add_marked_text_to_pane(&info_pane[pane], draw, type, subtype, orig_color); 00951 } 00952 00953 if (want_config[CONFIG_TIMESTAMP]) 00954 free(stamp); 00955 } 00956 00969 void info_buffer_init(void) { 00970 int loop; 00971 00972 for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) { 00973 info_buffer[loop].count = -1; 00974 info_buffer[loop].age = 0; 00975 info_buffer[loop].type = 0; 00976 info_buffer[loop].subtype = 0; 00977 info_buffer[loop].orig_color = 0; 00978 info_buffer[loop].message[0] = '\0'; 00979 }; 00980 } 00981 00995 void info_buffer_flush(const int id) { 00996 char output_buffer[MESSAGE_BUFFER_SIZE /* Buffer for output big enough */ 00997 +COUNT_BUFFER_SIZE]; /* to hold both count and text. */ 00998 /* 00999 * Messages are output with no output-count at the time they are first 01000 * placed in a buffer, so do not bother displaying it again if another 01001 * instance of the message was not seen after the initial buffering. 01002 */ 01003 if (info_buffer[id].count > 0) { 01004 /* 01005 * Report the number of times the message was seen only if it was seen 01006 * after having been initially buffered. 01007 */ 01008 if (info_buffer[id].count > 1) { 01009 snprintf(output_buffer, sizeof(output_buffer), "%u times %s", 01010 info_buffer[id].count, info_buffer[id].message); 01011 /* 01012 * Output the message count and message text. 01013 */ 01014 draw_ext_info( 01015 info_buffer[id].orig_color, 01016 info_buffer[id].type, 01017 info_buffer[id].subtype, 01018 output_buffer); 01019 } else 01020 /* 01021 * Output only the message text. 01022 */ 01023 draw_ext_info( 01024 info_buffer[id].orig_color, 01025 info_buffer[id].type, 01026 info_buffer[id].subtype, 01027 info_buffer[id].message); 01028 }; 01029 /* 01030 * Mark the buffer newly emptied. 01031 */ 01032 info_buffer[id].count = -1; 01033 } 01034 01043 void info_buffer_tick(void) { 01044 int loop; 01045 01046 for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) { 01047 if (info_buffer[loop].count > -1) { 01048 if ((info_buffer[loop].age < (int) buffer_control.timer.state) 01049 && (info_buffer[loop].count < (int) buffer_control.count.state)) { 01050 /* 01051 * The buffer has data in it, and has not reached maximum age, 01052 * so bump the age up a notch. 01053 */ 01054 info_buffer[loop].age += 1; 01055 } else { 01056 /* 01057 * The data has been in the buffer too long, so either display 01058 * it (and report how many times it was seen while in the 01059 * buffer) or simply expire the buffer content if duplicates 01060 * did not occur. 01061 */ 01062 info_buffer_flush(loop); 01063 } 01064 } else { 01065 /* 01066 * Overflow-protected aging of empty or inactive buffers. Aging 01067 * of inactive buffers is the reason overflow must be handled. 01068 */ 01069 if (info_buffer[loop].age < info_buffer[loop].age + 1) { 01070 info_buffer[loop].age += 1; 01071 } 01072 } 01073 } 01074 } 01075 01099 static void 01100 message_callback(int orig_color, int type, int subtype, char *message) { 01101 int search; /* Loop for searching the buffers. */ 01102 int found; /* Which buffer a message is in. */ 01103 int empty; /* The ID of an empty buffer. */ 01104 int oldest; /* Oldest non-empty buffer found. */ 01105 int empty_age; /* Age of oldest empty buffer. */ 01106 int oldest_age; /* Age of oldest non-empty buffer. */ 01107 01108 /* 01109 * Any message that has an invalid type cannot be buffered. An error is 01110 * not logged here as draw_ext_info() is where all messages pass through. 01111 * 01112 * A legacy switch to prevent message folding is to set the color of the 01113 * message to NDI_UNIQUE. This over-rides the player preferences. 01114 * 01115 * Usually msgctrl_widgets[] is used to determine whether or not messages 01116 * are buffered as it is where the player sets buffering preferences. The 01117 * type must be decremented when used to index into msgctrl_widgets[]. 01118 * 01119 * The system also declines to buffer messages over a set length as most 01120 * messages that need coalescing are short. Most messages that are long 01121 * are usually unique and should not be delayed. >= allows for the null 01122 * at the end of the string in the buffer. IE. If the buffer size is 40, 01123 * only 39 chars can be put into it to ensure room for a null character. 01124 */ 01125 if ((type < 1) 01126 || (type >= MSG_TYPE_LAST) 01127 || (orig_color == NDI_UNIQUE) 01128 || (msgctrl_widgets[type - 1].buffer.state == FALSE) 01129 || (strlen(message) >= MESSAGE_BUFFER_SIZE)) { 01130 /* 01131 * If the message buffering feature is off, simply pass the message on 01132 * to the parser that will determine the panel routing and style. 01133 */ 01134 draw_ext_info(orig_color, type, subtype, message); 01135 } else { 01136 empty = -1; /* Default: Buffers are empty until proven full */ 01137 found = -1; /* Default: Incoming message is not in a buffer */ 01138 oldest = -1; /* Default: Oldest active buffer ID is unknown */ 01139 empty_age= -1; /* Default: Oldest empty buffer age is unknown */ 01140 oldest_age= -1; /* Default: Oldest active buffer age is unknown */ 01141 01142 for (search = 0; search < MESSAGE_BUFFER_COUNT; search += 1) { 01143 /* 01144 * 1) Find the oldest empty or inactive buffer, if one exists. 01145 * 2) Find the oldest non-empty/active buffer in case we need to 01146 * eject a message to make room for a new message. 01147 * 3) Find a buffer that matches the incoming message, whether the 01148 * buffer is active or not. 01149 */ 01150 if (info_buffer[search].count < 0) { 01151 /* 01152 * We want to find the oldest empty buffer. If a new message 01153 * that is not already buffered comes in, this is the ideal 01154 * place to put it. 01155 */ 01156 if ((info_buffer[search].age > empty_age)) { 01157 empty_age = info_buffer[search].age; 01158 empty = search; 01159 } 01160 } else { 01161 /* 01162 * The buffer is not empty, so process it to find the oldest 01163 * buffered message. If a new message comes in that is not 01164 * already buffered, and if there are no empty buffers 01165 * available, the oldest message will be pushed out to make 01166 * room for the new one. 01167 */ 01168 if (info_buffer[search].age > oldest_age) { 01169 oldest_age = (info_buffer[search].age); 01170 oldest = search; 01171 } 01172 } 01173 /* 01174 * Check all buffers, inactive and active, to see if the incoming 01175 * message matches an existing buffer. Because empty buffers are 01176 * re-used if they match, it should not be possible for more than 01177 * one buffer to match, so do not bother searching after the first 01178 * match is found. 01179 */ 01180 if (found < 0) { 01181 if (! strcmp(message, info_buffer[search].message)) { 01182 found = search; 01183 } 01184 } 01185 } 01186 01187 #if 0 01188 LOG(LOG_DEBUG, "info.c::message_callback", "\n " 01189 "type: %d-%d empty: %d found: %d oldest: %d oldest_age: %d", 01190 type, subtype, empty, found, oldest, oldest_age); 01191 #endif 01192 01193 /* 01194 * If the incoming message is already buffered, then increment the 01195 * message count and exit, otherwise add the message to the buffer. 01196 */ 01197 if (found > -1) { 01198 /* 01199 * If the found buffer was inactive, this automatically activates 01200 * it, and sets the count to one to ensure printing of the message 01201 * occurance as messages are pre-printed only when they are 01202 * inserted into a buffer after not being found. 01203 */ 01204 if (info_buffer[found].count == -1) { 01205 info_buffer[found].count += 1; 01206 info_buffer[found].age = 0; 01207 } 01208 info_buffer[found].count += 1; 01209 } else { 01210 /* 01211 * The message was not found in a buffer, so check if there is an 01212 * available buffer. If not, dump the oldest buffer to make room, 01213 * then mark it empty. 01214 */ 01215 if (empty == -1) { 01216 if (oldest > -1) { 01217 /* 01218 * The oldest message is getting kicked out of the buffer 01219 * to make room for a new message coming in. 01220 */ 01221 info_buffer_flush(oldest); 01222 } else { 01223 LOG(LOG_ERROR, "info.c::message_callback", 01224 "Buffer full; oldest unknown", strlen(message)); 01225 } 01226 } 01227 /* 01228 * To avoid delaying player notification in cases where multiple 01229 * messages might not occur, or especially if a message is really 01230 * important to get right away, go ahead an output the message 01231 * without a count at the time it is first put into a buffer. As 01232 * this message has already been output, the buffer count is set 01233 * zero, so that info_buffer_flush() will not re-display it if a 01234 * duplicate does not occur while this message is in the buffer. 01235 */ 01236 draw_ext_info(orig_color, type, subtype, message); 01237 /* 01238 * There should always be an empty buffer at this point, but just 01239 * in case, recheck before putting the new message in the buffer. 01240 * Do not log another error as one was just logged, but instead 01241 * just output the message that came in without passing it through 01242 * the buffer system. 01243 */ 01244 if (empty > -1) { 01245 /* 01246 * Copy the incoming message to the empty buffer. 01247 */ 01248 info_buffer[empty].age = 0; 01249 info_buffer[empty].count = 0; 01250 info_buffer[empty].orig_color = orig_color; 01251 info_buffer[empty].type = type; 01252 info_buffer[empty].subtype = subtype; 01253 strcpy(info_buffer[empty].message, message); 01254 } 01255 } 01256 } 01257 } 01258 /* EndOf GTKv2OutputCountSync 01261 */ 01262 01267 void menu_clear(void) { 01268 int i; 01269 01270 for (i=0; i < NUM_TEXT_VIEWS; i++) { 01271 gtk_text_buffer_set_text(info_pane[i].textbuffer, "", 0); 01272 } 01273 } 01274 01284 void set_scroll(const char *s) 01285 { 01286 } 01287 01297 void set_autorepeat(const char *s) 01298 { 01299 } 01300 01309 int get_info_width(void) 01310 { 01315 return 40; 01316 } 01317 01328 void msgctrl_init(GtkWidget *window_root) 01329 { 01330 GtkTableChild* child; /* Used to get number of title rows */ 01331 GladeXML* xml_tree; /* Used to find the dialog widgets */ 01332 GtkWidget* widget; /* Used to connect widgets */ 01333 GtkTable* table; /* The table of checkbox controls */ 01334 GList* list; /* Iterator: table children */ 01335 guint pane; /* Iterator: client message panes */ 01336 guint type; /* Iterator: message types */ 01337 guint row; /* Attachement for current widget */ 01338 gint title_rows = -1; /* Title rows in msgctrl_table as 01339 * defined in glade designer. -1 01340 * means there are no title rows. 01341 */ 01342 /* 01343 * Get the window pointer and a pointer to the tree of widgets it contains 01344 */ 01345 msgctrl_window = glade_xml_get_widget(dialog_xml, "msgctrl_window"); 01346 xml_tree = glade_get_widget_tree(GTK_WIDGET(msgctrl_window)); 01347 01348 g_signal_connect((gpointer) msgctrl_window, "delete_event", 01349 G_CALLBACK(gtk_widget_hide_on_delete), NULL); 01350 /* 01351 * Initialize the spinbutton pointers. 01352 */ 01353 buffer_control.count.ptr = 01354 glade_xml_get_widget(xml_tree, "msgctrl_spinbutton_count"); 01355 01356 buffer_control.timer.ptr = 01357 glade_xml_get_widget(xml_tree, "msgctrl_spinbutton_timer"); 01358 01359 /* 01360 * Locate the table widget to fill with controls and its structure. 01361 */ 01362 msgctrl_table = glade_xml_get_widget(xml_tree, "msgctrl_table"); 01363 table = GTK_TABLE(msgctrl_table); 01364 /* 01365 * How many title rows were set up in the table? The title rows are the 01366 * non-empty rows. Row numbers are zero-based. IMPORTANT: It is assumed 01367 * any row with at least one widget has widgets in all columns. WARNING: 01368 * This assumption is unwise if client layouts begin to be implemented to 01369 * have fewer message panes than the code supports! 01370 */ 01371 for (list = table->children; list; list = list->next) { 01372 child = list->data; 01373 if ((child->widget != 0) && (child->top_attach > title_rows)) { 01374 title_rows = child->top_attach; 01375 } 01376 } 01377 01378 /* 01379 * The table is defined in the dialog created with the design tool, but 01380 * the dimensions of the table are not known at design time, so it must be 01381 * resized and built up at run-time. 01382 * 01383 * The table columns are: message type description, message buffer 01384 * enable, and one enable per message pane supported by the client code. 01385 * The client layout might not support all of the panes, but all of them 01386 * will be put into the table. 01387 * 01388 * The table rows are: the header rows + the number of message types that 01389 * the client and server support. We assume the XML file designer did 01390 * properly set up the header rows. Since MSG_TYPE_LAST is 1 more than 01391 * the actual number of types, and since title_rows is one less than the 01392 * actual number of header rows, they balance out when added together. 01393 */ 01394 gtk_table_resize(table, 01395 (guint)(MSG_TYPE_LAST + title_rows), (guint)(1 + 1 + NUM_TEXT_VIEWS)); 01396 /* 01397 * Now we need to put labels and checkboxes in each of the empty rows and 01398 * initialize the state of the checkboxes to match the default settings. 01399 * It helps if we change title_rows to a one-based number. Walk through 01400 * each message type and set the corresponding row of the table it needs 01401 * to go with. type is one-based. The msgctrl_defaults and _widget 01402 * arrays are zero based. 01403 */ 01404 title_rows += 1; 01405 for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) { 01406 row = type + title_rows; 01407 /* 01408 * The message type description. Just put the the message type name 01409 * in a label, left-justified with some padding to keep it away from 01410 * the dialog frame and perhaps the neighboring checkbox. 01411 */ 01412 widget = gtk_label_new(msgctrl_defaults[type].description); 01413 gtk_misc_set_alignment(GTK_MISC(widget), 0.0f, 0.5f); 01414 gtk_misc_set_padding(GTK_MISC(widget), 2, 0); 01415 gtk_table_attach_defaults(table, widget, 0, 1, row, row + 1); 01416 gtk_widget_show(widget); 01417 /* 01418 * The buffer enable/disable. Display a check box that is preset to 01419 * the built-in default setting. 01420 */ 01421 msgctrl_widgets[type].buffer.ptr = gtk_check_button_new(); 01422 gtk_table_attach_defaults( 01423 table, msgctrl_widgets[type].buffer.ptr, 1, 2, row, row + 1); 01424 gtk_widget_show(msgctrl_widgets[type].buffer.ptr); 01425 /* 01426 * The message pane routings. Display a check box that is preset to 01427 * the built in defaults. 01428 */ 01434 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01435 msgctrl_widgets[type].pane[pane].ptr = gtk_check_button_new(); 01436 gtk_table_attach_defaults( 01437 table, msgctrl_widgets[type].pane[pane].ptr, 01438 pane + 2, pane + 3, row, row + 1); 01439 gtk_widget_show(msgctrl_widgets[type].pane[pane].ptr); 01440 } 01441 } 01442 /* 01443 * Initialize the state variables for the checkbox and spinbutton controls 01444 * on the message control dialog and then set all the widgets to match the 01445 * client defautl settings. 01446 */ 01447 default_msgctrl_configuration(); 01448 load_msgctrl_configuration(); 01449 01450 /* 01451 * Connect the control's buttons to the appropriate handlers. 01452 */ 01453 widget = glade_xml_get_widget(xml_tree, "msgctrl_button_save"); 01454 g_signal_connect((gpointer) widget, "clicked", 01455 G_CALLBACK(on_msgctrl_button_save_clicked), NULL); 01456 01457 widget = glade_xml_get_widget(xml_tree, "msgctrl_button_load"); 01458 g_signal_connect((gpointer) widget, "clicked", 01459 G_CALLBACK(on_msgctrl_button_load_clicked), NULL); 01460 01461 widget = glade_xml_get_widget(xml_tree, "msgctrl_button_defaults"); 01462 g_signal_connect((gpointer) widget, "clicked", 01463 G_CALLBACK(on_msgctrl_button_defaults_clicked), NULL); 01464 01465 widget = glade_xml_get_widget(xml_tree, "msgctrl_button_apply"); 01466 g_signal_connect((gpointer) widget, "clicked", 01467 G_CALLBACK(on_msgctrl_button_apply_clicked), NULL); 01468 01469 widget = glade_xml_get_widget(xml_tree, "msgctrl_button_close"); 01470 g_signal_connect((gpointer) widget, "clicked", 01471 G_CALLBACK(on_msgctrl_button_close_clicked), NULL); 01472 } 01473 01480 void update_msgctrl_configuration(void) 01481 { 01482 guint pane; /* Client-supported message pane */ 01483 guint type; /* Message type */ 01484 01485 gtk_spin_button_set_value( 01486 GTK_SPIN_BUTTON(buffer_control.count.ptr), 01487 (gdouble) buffer_control.count.state); 01488 01489 gtk_spin_button_set_value( 01490 GTK_SPIN_BUTTON(buffer_control.timer.ptr), 01491 (gdouble) buffer_control.timer.state); 01492 01493 for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) { 01494 gtk_toggle_button_set_active( 01495 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr), 01496 msgctrl_widgets[type].buffer.state); 01497 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01498 gtk_toggle_button_set_active( 01499 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr), 01500 msgctrl_widgets[type].pane[pane].state); 01501 } 01502 } 01503 } 01504 01510 void save_msgctrl_configuration(void) 01511 { 01512 char pathbuf[MAX_BUF]; /* Buffer for a save file path name */ 01513 char textbuf[MAX_BUF]; /* Buffer for output to save file */ 01514 FILE* fptr; /* Message Control savefile pointer */ 01515 guint pane; /* Client-supported message pane */ 01516 guint type; /* Message type */ 01517 01518 read_msgctrl_configuration(); /* Apply the displayed settings 1st */ 01519 01520 snprintf(pathbuf, sizeof(pathbuf), "%s/.crossfire/msgs", getenv("HOME")); 01521 CONVERT_FILESPEC_TO_OS_FORMAT(pathbuf); 01522 01523 if (make_path_to_file(pathbuf) == -1) { 01524 LOG(LOG_WARNING, 01525 "gtk-v2::save_msgctrl_configuration","Error creating %s",pathbuf); 01526 snprintf(textbuf, sizeof(textbuf), 01527 "Error creating %s, Message Control settings not saved.",pathbuf); 01528 draw_ext_info( 01529 NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf); 01530 return; 01531 } 01532 if ((fptr = fopen(pathbuf, "w")) == NULL) { 01533 snprintf(textbuf, sizeof(textbuf), 01534 "Error opening %s, Message Control settings not saved.", pathbuf); 01535 draw_ext_info( 01536 NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf); 01537 return; 01538 } 01539 01540 /* 01541 * It might be best to check the status of all writes, but it is not done. 01542 */ 01543 fprintf(fptr, "# Message Control System Configuration\n"); 01544 fprintf(fptr, "#\n"); 01545 fprintf(fptr, "# Count: 1-96\n"); 01546 fprintf(fptr, "#\n"); 01547 fprintf(fptr, "C %u\n", buffer_control.count.state); 01548 fprintf(fptr, "#\n"); 01549 fprintf(fptr, "# Timer: 1-96 (8 ~= one second)\n"); 01550 fprintf(fptr, "#\n"); 01551 fprintf(fptr, "T %u\n", buffer_control.timer.state); 01552 fprintf(fptr, "#\n"); 01553 fprintf(fptr, "# type, buffer, pane[0], pane[1]...\n"); 01554 fprintf(fptr, "# Do not edit the 'type' field.\n"); 01555 fprintf(fptr, "# 0 == disable; 1 == enable.\n"); 01556 fprintf(fptr, "#\n"); 01557 for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) { 01558 fprintf( 01559 fptr, "M %02d %d ", type+1, msgctrl_widgets[type].buffer.state); 01560 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01561 fprintf(fptr, "%d ", msgctrl_widgets[type].pane[pane].state); 01562 } 01563 fprintf(fptr, "\n"); 01564 } 01565 fprintf(fptr, "#\n# End of Message Control System Configuration\n"); 01566 fclose(fptr); 01567 01568 snprintf(textbuf, sizeof(textbuf), 01569 "Message Control settings saved to %s.", pathbuf); 01570 draw_ext_info(NDI_BLUE, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, textbuf); 01571 } 01572 01577 void load_msgctrl_configuration(void) 01578 { 01579 char pathbuf[MAX_BUF]; /* Buffer for a save file path name */ 01580 char textbuf[MAX_BUF]; /* Buffer for input from save file */ 01581 char recordtype; /* Savefile data entry type found */ 01582 char* cptr; /* Pointer used when reading data */ 01583 FILE* fptr; /* Message Control savefile pointer */ 01584 guint pane; /* Client-supported message pane */ 01585 guint type; /* Message type */ 01586 guint error; /* Savefile parsing status */ 01587 message_control_t statebuf; /* Holding area for savefile values */ 01588 buffer_parameter_t countbuf; /* Holding area for savefile values */ 01589 buffer_parameter_t timerbuf; /* Holding area for savefile values */ 01590 guint cvalid, tvalid, mvalid; /* Counts the valid entries found */ 01591 01592 snprintf(pathbuf, sizeof(pathbuf), "%s/.crossfire/msgs", getenv("HOME")); 01593 CONVERT_FILESPEC_TO_OS_FORMAT(pathbuf); 01594 01595 if ((fptr = fopen(pathbuf, "r")) == NULL) { 01596 snprintf(textbuf, sizeof(textbuf), 01597 "Error opening %s, Message Control settings not loaded.",pathbuf); 01598 draw_ext_info( 01599 NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf); 01600 return; 01601 } 01602 /* 01603 * When we parse the file we buffer each entire record before any values 01604 * are applied to the client message control configuration. If any 01605 * problems are found at all, the entire record is skipped and the file 01606 * is reported as corrupt. Even if individual records are corrupt, the 01607 * rest of the file is processed. 01608 * 01609 * If more than one record for the same error type exists the last one is 01610 * used, but if too many records are found the file is reported as corrupt 01611 * even though it accepts all the data. 01612 */ 01613 error = 0; 01614 cvalid = 0; 01615 tvalid = 0; 01616 mvalid = 0; 01617 recordtype = '\0'; 01618 while(fgets(textbuf, MAX_BUF-1, fptr) != NULL) { 01619 if (textbuf[0] == '#' || textbuf[0] == '\n') { 01620 continue; 01621 } 01622 01623 /* 01624 * Identify the savefile record type found. 01625 */ 01626 cptr = strtok(textbuf, "\t "); 01627 if ((cptr == NULL) 01628 || ((*cptr != 'C') && (*cptr != 'T') && (*cptr != 'M'))) { 01629 error += 1; 01630 continue; 01631 } 01632 recordtype = *cptr; 01633 01634 /* 01635 * Process the following fields by record type 01636 */ 01637 if (recordtype == 'C') { 01638 cptr = strtok(NULL, "\n"); 01639 if ((cptr == NULL) 01640 || (sscanf(cptr, "%u", &countbuf.state) != 1) 01641 || (countbuf.state < 1) 01642 || (countbuf.state > 96)) { 01643 error += 1; 01644 continue; 01645 } 01646 } 01647 if (recordtype == 'T') { 01648 cptr = strtok(NULL, "\n"); 01649 if ((cptr == NULL) 01650 || (sscanf(cptr, "%u", &timerbuf.state) != 1) 01651 || (timerbuf.state < 1) 01652 || (timerbuf.state > 96)) { 01653 error += 1; 01654 continue; 01655 } 01656 } 01657 if (recordtype == 'M') { 01658 cptr = strtok(NULL, "\t "); 01659 if ((cptr == NULL) 01660 || (sscanf(cptr, "%d", &type) != 1) 01661 || (type < 1) 01662 || (type >= MSG_TYPE_LAST)) { 01663 error += 1; 01664 continue; 01665 } 01666 cptr = strtok(NULL, "\t "); 01667 if ((cptr == NULL) 01668 || (sscanf(cptr, "%d", &statebuf.buffer.state) != 1) 01669 || (statebuf.buffer.state < 0) 01670 || (statebuf.buffer.state > 1)) { 01671 error += 1; 01672 continue; 01673 } 01674 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01675 cptr = strtok(NULL, "\t "); 01676 if ((cptr == NULL) 01677 || (sscanf(cptr, "%d", &statebuf.pane[pane].state) != 1) 01678 || (statebuf.pane[pane].state < 0) 01679 || (statebuf.pane[pane].state > 1)) { 01680 error += 1; 01681 continue; 01682 } 01683 } 01684 /* 01685 * Ignore the record if it has too many fields. This might be a 01686 * bit strict, but it does help enforce the file integrity in the 01687 * event that the the number of supported panels increases in the 01688 * future. 01689 */ 01690 cptr = strtok(NULL, "\n"); 01691 if (cptr != NULL) { 01692 error += 1; 01693 continue; 01694 } 01695 } 01696 01697 /* 01698 * Remember, type is one-based, but the index into an array is zero- 01699 * based, so adjust type. Also, since the record parsed out fine, 01700 * increment the number of valid records found. Apply all the values 01701 * read to the buffer_control structure and msgctrl_widgets[] array so 01702 * the dialog can be updated when all data has been read. 01703 */ 01704 if (recordtype == 'C') { 01705 buffer_control.count.state = countbuf.state; 01706 cvalid += 1; 01707 } 01708 if (recordtype == 'T') { 01709 buffer_control.timer.state = timerbuf.state; 01710 tvalid += 1; 01711 } 01712 if (recordtype == 'M') { 01713 type -= 1; 01714 msgctrl_widgets[type].buffer.state = statebuf.buffer.state; 01715 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01716 msgctrl_widgets[type].pane[pane].state = 01717 statebuf.pane[pane].state; 01718 } 01719 mvalid += 1; 01720 } 01721 } 01722 fclose(fptr); 01723 /* 01724 * If there was any oddity with the data file, report it as corrupted even 01725 * if some of the values were used. A corrupted file can be uncorrupted 01726 * by loading it and saving it again. A found value is needed for count, 01727 * timer, and each message type. 01728 */ 01729 if ((error > 0) 01730 || (cvalid != 1) 01731 || (tvalid != 1) 01732 || (mvalid != MSG_TYPE_LAST - 1)) { 01733 snprintf(textbuf, sizeof(textbuf), 01734 "Corrupted Message Control settings in %s.", pathbuf); 01735 draw_ext_info( 01736 NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf); 01737 LOG(LOG_ERROR, "gtk-v2::load_msgctrl_configuration", 01738 "Error loading %s. %s\n", pathbuf, textbuf); 01739 } 01740 /* 01741 * If any data was accepted from the save file, report that settings were 01742 * loaded. Apply the loaded values to the Message Control dialog checkbox 01743 * widgets. so they reflect the states that were previously saved. 01744 */ 01745 if ((cvalid + tvalid + mvalid) > 0) { 01746 snprintf(textbuf, sizeof(textbuf), 01747 "Message Control settings loaded from %s", pathbuf); 01748 draw_ext_info( 01749 NDI_BLUE, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, textbuf); 01750 01751 update_msgctrl_configuration(); /* Update checkboxes w/ loaded data */ 01752 } 01753 } 01754 01763 void default_msgctrl_configuration(void) 01764 { 01765 guint pane; /* Client-supported message pane */ 01766 guint type; /* Message type */ 01767 01768 buffer_control.count.state = (guint) buffer_control.count.default_state; 01769 buffer_control.timer.state = (guint) buffer_control.timer.default_state; 01770 for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) { 01771 msgctrl_widgets[type].buffer.state = msgctrl_defaults[type].buffer; 01772 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01773 msgctrl_widgets[type].pane[pane].state = 01774 msgctrl_defaults[type].pane[pane]; 01775 } 01776 } 01777 update_msgctrl_configuration(); 01778 } 01779 01785 void read_msgctrl_configuration(void) 01786 { 01787 guint pane; /* Client-supported message pane */ 01788 guint type; /* Message type */ 01789 01790 buffer_control.count.state = 01791 gtk_spin_button_get_value_as_int( 01792 GTK_SPIN_BUTTON(buffer_control.count.ptr)); 01793 buffer_control.timer.state = 01794 gtk_spin_button_get_value_as_int( 01795 GTK_SPIN_BUTTON(buffer_control.timer.ptr)); 01796 /* 01797 * Iterate through each message type. For each, record the value of the 01798 * message duplicate suppression checkbox, and also obtain the routing 01799 * settings for all client supported panels (even if the layout does not 01800 * support them all. 01801 */ 01802 for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) { 01803 msgctrl_widgets[type].buffer.state = 01804 gtk_toggle_button_get_active( 01805 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr)); 01806 for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) { 01807 msgctrl_widgets[type].pane[pane].state = 01808 gtk_toggle_button_get_active( 01809 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr)); 01810 } 01811 } 01812 } 01813 01823 void 01824 on_msgctrl_button_save_clicked (GtkButton *button, 01825 gpointer user_data) 01826 { 01827 read_msgctrl_configuration(); 01828 save_msgctrl_configuration(); 01829 } 01830 01839 void 01840 on_msgctrl_button_load_clicked (GtkButton *button, 01841 gpointer user_data) 01842 { 01843 load_msgctrl_configuration(); 01844 } 01845 01853 void 01854 on_msgctrl_button_defaults_clicked (GtkButton *button, 01855 gpointer user_data) 01856 { 01857 default_msgctrl_configuration(); 01858 } 01859 01868 void 01869 on_msgctrl_button_apply_clicked (GtkButton *button, 01870 gpointer user_data) 01871 { 01872 read_msgctrl_configuration(); 01873 } 01874 01882 void 01883 on_msgctrl_button_close_clicked (GtkButton *button, 01884 gpointer user_data) 01885 { 01886 read_msgctrl_configuration(); 01887 gtk_widget_hide(msgctrl_window); 01888 } 01889 01898 void 01899 on_msgctrl_activate (GtkMenuItem *menuitem, 01900 gpointer user_data) 01901 { 01902 gtk_widget_show(msgctrl_window); 01903 } 01904
1.7.6.1