Crossfire Client, Trunk  R18666
/home/leaf/crossfire/client/trunk/gtk-v2/src/info.c
Go to the documentation of this file.
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